added assigned org to project
This commit is contained in:
parent
133024bc5c
commit
005fdb3490
@ -1,11 +1,12 @@
|
||||
import React from 'react'
|
||||
import ManageOrganization from './components/Organization/ManageOrganization'
|
||||
import React, { useEffect } from 'react'
|
||||
// import ManageOrganization from './components/Organization/ManageOrganization'
|
||||
import { useOrganizationModal } from './hooks/useOrganization';
|
||||
import OrganizationModal from './components/Organization/OrganizationModal';
|
||||
|
||||
const ModalProvider = () => {
|
||||
const { isOpen } = useOrganizationModal();
|
||||
|
||||
return <>{isOpen && <ManageOrganization />}</>;
|
||||
const { isOpen,onClose } = useOrganizationModal();
|
||||
|
||||
return <>{isOpen && <OrganizationModal />}</>;
|
||||
};
|
||||
|
||||
|
||||
|
@ -1,9 +1,208 @@
|
||||
import React from 'react'
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { useProjectAssignedServices } from "../../hooks/useProjects";
|
||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||
import {
|
||||
useOrganizationType,
|
||||
useServices,
|
||||
} from "../../hooks/masterHook/useMaster";
|
||||
import Label from "../common/Label";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { assignedOrgToProject } from "./OrganizationSchema";
|
||||
import {
|
||||
useAssignOrgToProject,
|
||||
useOrganizationModal,
|
||||
} from "../../hooks/useOrganization";
|
||||
|
||||
const AssignOrg = () => {
|
||||
const AssignOrg = ({ setStep }) => {
|
||||
const { isOpen, orgData, startStep, onOpen, onClose, prevStep } =
|
||||
useOrganizationModal();
|
||||
const selectedProject = useSelectedProject();
|
||||
const { data: masterService, isLoading: isMasterserviceLoading } =
|
||||
useServices();
|
||||
const { data: projectServices, isLoading } =
|
||||
useProjectAssignedServices(selectedProject);
|
||||
const { data: orgType, isLoading: orgLoading } = useOrganizationType();
|
||||
|
||||
const { mutate: AssignToProject, isPending } = useAssignOrgToProject(
|
||||
() => {}
|
||||
);
|
||||
|
||||
const mergedServices = useMemo(() => {
|
||||
if (!masterService || !projectServices) return [];
|
||||
|
||||
const combined = [...masterService?.data, ...projectServices];
|
||||
|
||||
const unique = combined.filter(
|
||||
(item, index, self) => index === self.findIndex((s) => s.id === item.id)
|
||||
);
|
||||
|
||||
return unique;
|
||||
}, [masterService, projectServices]);
|
||||
|
||||
const {
|
||||
register,
|
||||
setValue,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm({
|
||||
resolver: zodResolver(assignedOrgToProject),
|
||||
});
|
||||
|
||||
const onSubmit = (formData) => {
|
||||
const payload = {
|
||||
...formData,
|
||||
projectId: selectedProject,
|
||||
organizationId: orgData.id,
|
||||
parentOrganizationId: null,
|
||||
};
|
||||
AssignToProject(payload);
|
||||
};
|
||||
const handleEdit = () => {
|
||||
onOpen({ startStep: 4 , orgData:orgData});
|
||||
};
|
||||
if (isMasterserviceLoading || isLoading)
|
||||
return <div className="text-center">Loading....</div>;
|
||||
return (
|
||||
<div>AssignOrg</div>
|
||||
)
|
||||
}
|
||||
<div className="row text-black mb-3">
|
||||
<div className="col-12 mb-3">
|
||||
<div className="d-flex justify-content-between align-items-center text-start mb-2">
|
||||
<div className="fw-semibold text-wrap">{orgData.name}</div>
|
||||
<div className="text-end">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleEdit}
|
||||
className="btn btn-link p-0"
|
||||
>
|
||||
<i className="bx bx-edit text-secondary"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
export default AssignOrg
|
||||
<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" }}
|
||||
>
|
||||
Constact Person :
|
||||
</label>
|
||||
<div className="text-muted">{orgData.name}</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" }}
|
||||
>
|
||||
Contact Number :
|
||||
</label>
|
||||
<div className="text-muted">{orgData.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">{orgData.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">{orgData.sprid}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-12 mb-3">
|
||||
<div className="d-flex">
|
||||
<label
|
||||
className="form-label me-1 mb-0 fw-semibold text-start"
|
||||
style={{ minWidth: "130px" }}
|
||||
>
|
||||
Address :
|
||||
</label>
|
||||
<div className="text-muted text-start">{orgData.address}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-black text-start">
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="mb-1 text-start">
|
||||
<Label htmlFor="organizationTypeId" required>
|
||||
Organization Type
|
||||
</Label>
|
||||
<select
|
||||
{...register("organizationTypeId")}
|
||||
className="form-select form-select-sm"
|
||||
>
|
||||
<option value="">Select type</option>
|
||||
{orgType?.data.map((type) => (
|
||||
<option key={type.id} value={type.id}>
|
||||
{type.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{errors.organizationTypeId && (
|
||||
<span className="danger-text">
|
||||
{errors.organizationTypeId.message}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Select Services</label>
|
||||
{mergedServices?.map((service) => (
|
||||
<div key={service.id} className="form-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
value={service.id}
|
||||
{...register("serviceIds")}
|
||||
className="form-check-input"
|
||||
/>
|
||||
<label className="form-check-label">{service.name}</label>
|
||||
</div>
|
||||
))}
|
||||
{errors.serviceIds && (
|
||||
<div className="text-danger small">
|
||||
{errors.serviceIds.message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Buttons */}
|
||||
<div className="d-flex justify-content-between mt-3">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-xs btn-outline-secondary"
|
||||
onClick={() => onOpen({ startStep: prevStep })}
|
||||
disabled={isPending}
|
||||
>
|
||||
<i className="bx bx-left-arrow-alt"></i> Back
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-sm btn-primary"
|
||||
disabled={isPending}
|
||||
>
|
||||
{isPending ? "Please wait..." : "Add"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AssignOrg;
|
||||
|
142
src/components/Organization/ManagOrg.jsx
Normal file
142
src/components/Organization/ManagOrg.jsx
Normal file
@ -0,0 +1,142 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
import {
|
||||
useCreateOrganization,
|
||||
useOrganizationModal,
|
||||
useUpdateOrganization,
|
||||
} from "../../hooks/useOrganization";
|
||||
import {
|
||||
defaultOrganizationValues,
|
||||
organizationSchema,
|
||||
} from "./OrganizationSchema";
|
||||
import Label from "../common/Label";
|
||||
import { useGlobalServices } from "../../hooks/masterHook/useMaster";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import SelectMultiple from "../common/SelectMultiple";
|
||||
|
||||
const ManagOrg = () => {
|
||||
const { data: service, isLoading } = useGlobalServices();
|
||||
const { isOpen, orgData, startStep, onOpen, onClose, prevStep } =
|
||||
useOrganizationModal();
|
||||
|
||||
const method = useForm({
|
||||
resolver: zodResolver(organizationSchema),
|
||||
defaultValues: defaultOrganizationValues,
|
||||
});
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
register,
|
||||
reset,
|
||||
formState: { errors },
|
||||
} = method;
|
||||
|
||||
// Create & Update mutations
|
||||
const { mutate: createOrganization, isPending: isCreating } = useCreateOrganization(() => {
|
||||
reset(defaultOrganizationValues);
|
||||
onOpen({ startStep: 1 });
|
||||
onClose();
|
||||
});
|
||||
|
||||
const { mutate: updateOrganization, isPending: isUpdating } = useUpdateOrganization(() => {
|
||||
reset(defaultOrganizationValues);
|
||||
onOpen({ startStep: 1 });
|
||||
onClose();
|
||||
});
|
||||
|
||||
// Prefill form if editing
|
||||
useEffect(() => {
|
||||
if (orgData) {
|
||||
console.log(orgData)
|
||||
reset({
|
||||
name: orgData.name || "",
|
||||
contactPerson: orgData.contactPerson || "",
|
||||
contactNumber: orgData.contactNumber || "",
|
||||
email: orgData.email || "",
|
||||
serviceIds: orgData.services?.map(s => s.id) || [],
|
||||
address: orgData.address || "",
|
||||
});
|
||||
}
|
||||
}, [orgData, reset]);
|
||||
|
||||
const onSubmit = (payload) => {
|
||||
if (orgData?.id) {
|
||||
updateOrganization({ id: orgData.id, ...payload });
|
||||
} else {
|
||||
createOrganization(payload);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<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={service?.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={() => onOpen({ startStep: prevStep })}
|
||||
>
|
||||
← --Back
|
||||
</button>
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-sm btn-primary"
|
||||
disabled={isCreating || isUpdating || isLoading}
|
||||
>
|
||||
{isCreating || isUpdating ? "Please Wait..." : orgData ? "Update" : "Submit"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</FormProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default ManagOrg;
|
@ -15,14 +15,15 @@ import {
|
||||
import Label from "../common/Label";
|
||||
import SelectMultiple from "../common/SelectMultiple";
|
||||
import { useServices } from "../../hooks/masterHook/useMaster";
|
||||
import AssignOrg from "./AssignOrg";
|
||||
|
||||
const ManageOrganization = ({
|
||||
projectOrganizations = ["ee"],
|
||||
projectOrganizations ,
|
||||
organizationId = null,
|
||||
}) => {
|
||||
const [step, setStep] = useState(1);
|
||||
const orgModal = useOrganizationModal();
|
||||
const { data: masterService, isLoading } = useServices();
|
||||
const { data: masterService, isLoading } = useServices();
|
||||
const [searchText, setSearchText] = useState();
|
||||
const [SPRID, setSPRID] = useState("");
|
||||
const { data: orgList, isLoading: orgLoading } = useOrganizationsList(
|
||||
@ -39,7 +40,7 @@ const ManageOrganization = ({
|
||||
resolver: zodResolver(organizationSchema),
|
||||
defaultValues: defaultOrganizationValues,
|
||||
});
|
||||
console.log(masterService);
|
||||
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
@ -76,7 +77,7 @@ const ManageOrganization = ({
|
||||
}
|
||||
|
||||
if (step === 3) {
|
||||
return "Create Organization";
|
||||
return "Assign Organization";
|
||||
}
|
||||
|
||||
return "Manage Organization"; // fallback
|
||||
@ -238,105 +239,11 @@ const ManageOrganization = ({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{/* ---------- STEP 2: Existing Organization Details ---------- */}
|
||||
{step === 3 && (
|
||||
<div className="row text-black mb-3">
|
||||
<div className="col-12 mb-3"></div>
|
||||
<div className="text-start mb-2">
|
||||
<div className="text-muted">{Organization.name}</div>
|
||||
</div>
|
||||
{/* Row 1 */}
|
||||
<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" }}
|
||||
>
|
||||
Constact Person :
|
||||
</label>
|
||||
<div className="text-muted">{Organization.name}</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" }}
|
||||
>
|
||||
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 className="col-12 mb-3">
|
||||
<div className="d-flex">
|
||||
<label
|
||||
className="form-label me-1 mb-0 fw-semibold text-start"
|
||||
style={{ minWidth: "130px" }}
|
||||
>
|
||||
Address :
|
||||
</label>
|
||||
<div className="text-muted text-start">
|
||||
{Organization.address}
|
||||
</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>
|
||||
<AssignOrg Organization={Organization} setStep={setStep}/>
|
||||
)}
|
||||
|
||||
{/* ---------- STEP 3: Add New Organization ---------- */}
|
||||
@ -405,7 +312,7 @@ const ManageOrganization = ({
|
||||
label="Services"
|
||||
required
|
||||
valueKey="id"
|
||||
options={services?.data || []}
|
||||
options={masterService?.data || []}
|
||||
/>
|
||||
{errors.serviceIds && (
|
||||
<span className="danger-text">{errors.serviceIds.message}</span>
|
||||
@ -435,14 +342,7 @@ const ManageOrganization = ({
|
||||
← 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"
|
||||
|
110
src/components/Organization/OrgPicker.jsx
Normal file
110
src/components/Organization/OrgPicker.jsx
Normal file
@ -0,0 +1,110 @@
|
||||
import { useOrganizationModal } from "../../hooks/useOrganization";
|
||||
import Label from "../common/Label";
|
||||
|
||||
const OrgPicker = ({
|
||||
title,
|
||||
placeholder,
|
||||
orgs = [],
|
||||
searchValue,
|
||||
setSearchValue,
|
||||
}) => {
|
||||
const { isOpen, orgData, startStep, onOpen, onClose, prevStep } =
|
||||
useOrganizationModal();
|
||||
|
||||
const handleBack = () => {
|
||||
if (startStep === prevStep) {
|
||||
onOpen({ startStep: 2,prevStep:1 });
|
||||
} else {
|
||||
onOpen({ startStep: 2 });
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="d-block">
|
||||
<div className="text-start mb-1">
|
||||
<Label className="text-secondary">{title}1</Label>
|
||||
<input
|
||||
type="text"
|
||||
value={searchValue}
|
||||
onChange={(e) => setSearchValue?.(e.target.value)}
|
||||
className="form-control form-control-sm w-auto"
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* ---- Organization list ---- */}
|
||||
<div className="py-2 text-tiny text-center">
|
||||
<div className="d-flex flex-column gap-2 border-0 bg-none">
|
||||
{orgs?.map((org) => (
|
||||
<div
|
||||
key={org.id}
|
||||
className="list-group-item list-group-item-action d-flex align-items-center cursor-pointer border-0 hover-overlay border-bottom rounded-none pb-1 shadow-1-strong rounded"
|
||||
>
|
||||
<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 cursor-pointer">
|
||||
<div className="user-info text-start">
|
||||
<h6 className="mb-1 fw-normal">{org.name}</h6>
|
||||
</div>
|
||||
<div className="add-btn">
|
||||
<button
|
||||
className="btn btn-primary btn-xs"
|
||||
onClick={onOpen.bind(null, { startStep: 3, orgData: org })}
|
||||
>
|
||||
select
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{startStep !== 2 && (
|
||||
<div className="mt-4">
|
||||
<p className="text-secondary">
|
||||
Don't have required organization, Please find using{" "}
|
||||
<span
|
||||
className="text-mutes cursor-pointer text-decoration-underline"
|
||||
onClick={() => {
|
||||
onOpen({ startStep: 2, });
|
||||
}}
|
||||
>
|
||||
SPRID
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* ---- Footer buttons ---- */}
|
||||
<div
|
||||
className={`d-flex justify-content-end
|
||||
text-secondary mt-3`}
|
||||
>
|
||||
{startStep > 1 && prevStep < startStep && (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-xs btn-outline-secondary"
|
||||
onClick={handleBack}
|
||||
>
|
||||
<i className="bx bx-left-arrow-alt"></i> Back
|
||||
</button>
|
||||
)}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-xs btn-secondary"
|
||||
onClick={() => onOpen({ startStep: 4 })}
|
||||
>
|
||||
<i className="bx bx-plus-circle me-2"></i>
|
||||
Add New Organization
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default OrgPicker;
|
145
src/components/Organization/OrgPicker2.jsx
Normal file
145
src/components/Organization/OrgPicker2.jsx
Normal file
@ -0,0 +1,145 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
useOrganizationBySPRID,
|
||||
useOrganizationModal,
|
||||
} from "../../hooks/useOrganization";
|
||||
import Label from "../common/Label";
|
||||
import { useDebounce } from "../../utils/appUtils";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { spridSchema } from "./OrganizationSchema";
|
||||
import { OrgCardSkeleton } from "./OrganizationSkeleton";
|
||||
|
||||
// Zod schema: only allow exactly 4 digits
|
||||
|
||||
const OrgPicker2 = ({ title, placeholder }) => {
|
||||
const { startStep, prevStep, onOpen } = useOrganizationModal();
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
watch,
|
||||
} = useForm({
|
||||
resolver: zodResolver(spridSchema),
|
||||
defaultValues: { spridSearchText: "" },
|
||||
});
|
||||
|
||||
const [SPRID, setSPRID] = useState("");
|
||||
|
||||
const { data, isLoading, isError, error, refetch } =
|
||||
useOrganizationBySPRID(SPRID);
|
||||
|
||||
const onSubmit = (formdata) => {
|
||||
setSPRID(formdata.spridSearchText);
|
||||
};
|
||||
console.log(data)
|
||||
const SP = watch("spridSearchText")
|
||||
return (
|
||||
<div className="d-block">
|
||||
<form
|
||||
className="d-flex flex-row gap-6 text-start align-items-center"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
>
|
||||
<div className="d-flex flex-column">
|
||||
<Label className="text-secondary">{title}</Label>
|
||||
<input
|
||||
type="search"
|
||||
|
||||
{...register("spridSearchText")}
|
||||
className="form-control form-control-sm w-auto"
|
||||
placeholder={placeholder || "Enter Service Provider ID"}
|
||||
maxLength={4}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-4">
|
||||
<button type="submit" className="btn btn-sm btn-primary">
|
||||
<i className="bx bx-sm bx-search-alt-2"></i> Search
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div className="text-start danger-text">
|
||||
{" "}
|
||||
{errors.spridSearchText && (
|
||||
<p className="text-danger small mt-1">
|
||||
{errors.spridSearchText.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* ---- Organization list ---- */}
|
||||
{isLoading ? (
|
||||
<OrgCardSkeleton />
|
||||
) : data && data.length > 0 ? (
|
||||
<div className="py-2 text-tiny text-center">
|
||||
<div className="d-flex flex-column gap-2 border-0 bg-none">
|
||||
{data.map((org) => (
|
||||
<div
|
||||
key={org.id}
|
||||
className="list-group-item list-group-item-action d-flex align-items-center cursor-pointer border-0 hover-overlay shadow-1-strong rounded"
|
||||
>
|
||||
<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 cursor-pointer">
|
||||
<div className="user-info text-start">
|
||||
<h6 className="mb-1 fw-normal">{org.name}</h6>
|
||||
</div>
|
||||
<div className="add-btn">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary btn-sm"
|
||||
onClick={() => onOpen({ startStep: 3, orgData: org })}
|
||||
>
|
||||
select
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : SPRID ? (
|
||||
<div className="py-3 text-center text-secondary">
|
||||
No organization found for "{SPRID}"
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-12 text-center text-tiny text-black">
|
||||
Type a Service Provider ID to find an organization.
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
|
||||
{/* ---- Footer buttons ---- */}
|
||||
{/* ---- Footer buttons ---- */}
|
||||
<div className="d-flex justify-content-between text-secondary mt-3">
|
||||
{startStep > 1 && prevStep !== startStep && (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-xs btn-outline-secondary"
|
||||
onClick={() => onOpen({ startStep: prevStep })}
|
||||
>
|
||||
<i className="bx bx-left-arrow-alt"></i> Back
|
||||
</button>
|
||||
)}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-xs btn-secondary"
|
||||
onClick={() => onOpen({ startStep: 4 })}
|
||||
>
|
||||
<i className="bx bx-plus-circle me-2"></i>
|
||||
Add New Organization
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default OrgPicker2;
|
130
src/components/Organization/OrganizationModal.jsx
Normal file
130
src/components/Organization/OrganizationModal.jsx
Normal file
@ -0,0 +1,130 @@
|
||||
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";
|
||||
import ManagOrg from "./ManagOrg";
|
||||
import OrgPicker from "./OrgPicker";
|
||||
import OrgPicker2 from "./OrgPicker2";
|
||||
|
||||
const OrganizationModal = () => {
|
||||
const { isOpen, orgData, startStep, onOpen, onClose, onToggle } =
|
||||
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 [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);
|
||||
onClose();
|
||||
}
|
||||
);
|
||||
|
||||
const onSubmit = (OrgPayload) => {
|
||||
CreateOrganization(OrgPayload);
|
||||
};
|
||||
|
||||
const RenderTitle = useMemo(() => {
|
||||
if (orgData) {
|
||||
return "Assign Organization";
|
||||
}
|
||||
|
||||
if (startStep === 1) {
|
||||
return orgData && orgData !== null
|
||||
? "Add Organization"
|
||||
: "Choose Organization";
|
||||
}
|
||||
|
||||
if (startStep === 2) {
|
||||
return "Choose Organization";
|
||||
}
|
||||
|
||||
if (startStep === 3) {
|
||||
return "Assign Organization";
|
||||
}
|
||||
|
||||
return "Manage Organization";
|
||||
}, [startStep, orgData]);
|
||||
|
||||
const contentBody = (
|
||||
<div>
|
||||
{/* ---------- STEP 1: Service Provider- Form Own Tenant list ---------- */}
|
||||
{startStep === 1 && (
|
||||
<OrgPicker
|
||||
title="Find Organization"
|
||||
placeholder="Enter Organization"
|
||||
orgs={orgList}
|
||||
projectOrganizations={orgData}
|
||||
searchValue={SPRID}
|
||||
setSearchValue={setSPRID}
|
||||
|
||||
/>
|
||||
)}
|
||||
|
||||
{startStep === 2 && (
|
||||
<OrgPicker2
|
||||
title="Find Organization"
|
||||
placeholder="Enter Service Provider Id"
|
||||
projectOrganizations={orgData}
|
||||
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* ---------- STEP 2: Existing Organization Details ---------- */}
|
||||
{startStep === 3 && Organization && (
|
||||
<AssignOrg Organization={Organization} />
|
||||
)}
|
||||
|
||||
{/* ---------- STEP 3: Add New Organization ---------- */}
|
||||
{startStep === 4 && (
|
||||
|
||||
<ManagOrg />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
title={RenderTitle}
|
||||
body={contentBody}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default OrganizationModal;
|
@ -30,3 +30,28 @@ export const defaultOrganizationValues = {
|
||||
email: "",
|
||||
serviceIds: [],
|
||||
};
|
||||
|
||||
|
||||
export const assignedOrgToProject = z.object({
|
||||
// projectId: z.string().uuid({ message: "Invalid projectId format" }),
|
||||
// organizationId: z.string().string({ message: "Invalid organizationId format" }),
|
||||
// parentOrganizationId: z
|
||||
// .string()
|
||||
// .nullable()
|
||||
// .optional(),
|
||||
serviceIds: z.preprocess(
|
||||
(val) => (Array.isArray(val) ? val : []),
|
||||
z
|
||||
.array(z.string().uuid({ message: "Invalid serviceId format" }))
|
||||
.nonempty({ message: "At least one service must be selected" })
|
||||
),
|
||||
|
||||
|
||||
organizationTypeId: z.string().min(1,{ message: "Organization is required" }),
|
||||
});
|
||||
|
||||
export const spridSchema = z.object({
|
||||
spridSearchText: z
|
||||
.string()
|
||||
.regex(/^\d{4}$/, { message: "SPRID must be exactly 4 digits" }),
|
||||
})
|
||||
|
39
src/components/Organization/OrganizationSkeleton.jsx
Normal file
39
src/components/Organization/OrganizationSkeleton.jsx
Normal file
@ -0,0 +1,39 @@
|
||||
const SkeletonLine = ({ height = 20, width = "100%", className = "" }) => (
|
||||
<div
|
||||
className={`skeleton mb-2 ${className}`}
|
||||
style={{
|
||||
height,
|
||||
width,
|
||||
}}
|
||||
></div>
|
||||
);
|
||||
export const OrgCardSkeleton = () => {
|
||||
return (
|
||||
<div className="row p-5">
|
||||
|
||||
{[...Array(1)].map((_, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="list-group-item d-flex align-items-center border-0 shadow-sm rounded mb-3"
|
||||
>
|
||||
<div className="d-flex align-items-center justify-content-center me-3 p-1">
|
||||
<SkeletonLine height={40} width={40} className="rounded-circle" />
|
||||
</div>
|
||||
|
||||
|
||||
<div className="w-100 px-1">
|
||||
<div className="d-flex justify-content-between px-">
|
||||
<div className="user-info text-start">
|
||||
<SkeletonLine height={16} width="120px" className="mb-1" />
|
||||
<SkeletonLine height={12} width="80px" />
|
||||
</div>
|
||||
<div className="add-btn px-2">
|
||||
<SkeletonLine height={30} width="60px" className="rounded" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
@ -3,7 +3,7 @@ import { useOrganizationModal } from "../../hooks/useOrganization";
|
||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||
|
||||
const ProjectOrganizations = () => {
|
||||
const orgModal = useOrganizationModal();
|
||||
const {onOpen,startStep} = useOrganizationModal();
|
||||
const selectedProject = useSelectedProject()
|
||||
return (
|
||||
<div className="card">
|
||||
@ -12,7 +12,7 @@ const ProjectOrganizations = () => {
|
||||
<button
|
||||
type="button"
|
||||
className="link-button btn btn-xs rounded-md link-button-sm m-1 btn-primary"
|
||||
onClick={() => orgModal.onOpen(selectedProject)}
|
||||
onClick={() => onOpen({startStep:1})}
|
||||
>
|
||||
<i className="bx bx-plus-circle me-2"></i>
|
||||
Add Organization
|
||||
|
@ -12,7 +12,13 @@ import showToast from "../../services/toastService";
|
||||
export const useServices = ()=>{
|
||||
return useQuery({
|
||||
queryKey:["services"],
|
||||
queryFn:async()=> await MasterRespository.getServices()
|
||||
queryFn:async()=> await MasterRespository.getMasterServices()
|
||||
})
|
||||
}
|
||||
export const useGlobalServices = ()=>{
|
||||
return useQuery({
|
||||
queryKey:["globalServices"],
|
||||
queryFn:async()=> await MasterRespository.getGlobalServices()
|
||||
})
|
||||
}
|
||||
|
||||
@ -226,6 +232,12 @@ const {
|
||||
|
||||
return { DocumentCategories, isError, isLoading, error };
|
||||
}
|
||||
export const useOrganizationType =()=>{
|
||||
return useQuery({
|
||||
queryKey:["orgType"],
|
||||
queryFn:async()=>await MasterRespository.getOrganizationType()
|
||||
})
|
||||
}
|
||||
// ===Application Masters Query=================================================
|
||||
|
||||
const fetchMasterData = async (masterType) => {
|
||||
|
@ -10,24 +10,40 @@ import showToast from "../services/toastService";
|
||||
|
||||
export const useOrganizationModal = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { isOpen, orgData } = useSelector(
|
||||
const { isOpen, orgData, startStep, prevStep } = useSelector(
|
||||
(state) => state.localVariables.OrganizationModal
|
||||
);
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
orgData,
|
||||
onOpen: (dat) =>
|
||||
dispatch(openOrgModal({ isOpen: true, orgData: dat || null })),
|
||||
startStep,
|
||||
prevStep,
|
||||
onOpen: (options = {}) =>
|
||||
dispatch(
|
||||
openOrgModal({
|
||||
isOpen: true,
|
||||
orgData: options.orgData ?? orgData ?? null,
|
||||
startStep: options.startStep ?? startStep ?? 1,
|
||||
// only override prevStep if explicitly passed
|
||||
prevStep: options.prevStep ?? prevStep ?? 1,
|
||||
})
|
||||
),
|
||||
onClose: () => dispatch(closeOrgModal()),
|
||||
onToggle: () => dispatch(toggleOrgModal()),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
export const useOrganizationBySPRID =(sprid)=>{
|
||||
return useQuery({
|
||||
queryKey:["organization by",sprid],
|
||||
queryFn:async()=>await OrganizationRepository.getOrganizationBySPRID(sprid),
|
||||
queryFn:async()=>{
|
||||
|
||||
const resp = await OrganizationRepository.getOrganizationBySPRID(sprid);
|
||||
return resp.data;
|
||||
},
|
||||
enabled:!!sprid
|
||||
})
|
||||
}
|
||||
@ -83,3 +99,45 @@ export const useCreateOrganization = (onSuccessCallback) => {
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useAssignOrgToProject=()=>{
|
||||
const useClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async (payload) =>
|
||||
await OrganizationRepository.assignOrganizationToProject(payload),
|
||||
onSuccess: (_, variables) => {
|
||||
// useClient.invalidateQueries({ queryKey: ["organizationList"] });
|
||||
showToast("Organization successfully", "success");
|
||||
if (onSuccessCallback) onSuccessCallback();
|
||||
},
|
||||
onError: (error) => {
|
||||
showToast(
|
||||
error.response.data.message ||
|
||||
error.message ||
|
||||
"Something went wrong please try again !",
|
||||
"error"
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export const useUpdateOrganization=()=>{
|
||||
const useClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async (payload) =>
|
||||
await OrganizationRepository.assignOrganizationToProject(payload),
|
||||
onSuccess: (_, variables) => {
|
||||
// useClient.invalidateQueries({ queryKey: ["organizationList"] });
|
||||
showToast("Organization successfully", "success");
|
||||
if (onSuccessCallback) onSuccessCallback();
|
||||
},
|
||||
onError: (error) => {
|
||||
showToast(
|
||||
error.response.data.message ||
|
||||
error.message ||
|
||||
"Something went wrong please try again !",
|
||||
"error"
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
@ -4,7 +4,8 @@ import { useOrganizationModal } from "../../hooks/useOrganization";
|
||||
import OrganizationsList from "../../components/Organization/OrganizationsList";
|
||||
|
||||
const OrganizationPage = () => {
|
||||
const orgModal = useOrganizationModal()
|
||||
const { isOpen, orgData, startStep, onOpen, } =
|
||||
useOrganizationModal();
|
||||
const [searchText,setSearchText] = useState("")
|
||||
|
||||
return (
|
||||
@ -33,7 +34,7 @@ const OrganizationPage = () => {
|
||||
type="button"
|
||||
className="p-1 me-1 m-sm-0 bg-primary rounded-circle"
|
||||
title="Add New Organization"
|
||||
onClick={()=>orgModal.onOpen()}
|
||||
onClick={()=>onOpen({ startStep: 1, })}
|
||||
>
|
||||
<i className="bx bx-plus fs-4 text-white"></i>
|
||||
</button>
|
||||
|
@ -109,5 +109,8 @@ export const MasterRespository = {
|
||||
|
||||
|
||||
|
||||
getServices:()=>api.get("/api/Master/global-service/list")
|
||||
getGlobalServices:()=>api.get("/api/Master/global-service/list"),
|
||||
getMasterServices:()=>api.get("/api/Master/service/list"),
|
||||
|
||||
getOrganizationType:()=>api.get('/api/Master/organization-type/list')
|
||||
};
|
||||
|
@ -10,7 +10,7 @@ const OrganizationRepository = {
|
||||
);
|
||||
},
|
||||
|
||||
getOrganizationBySPRID :()=>api.get(`/api/Organization/list?sprid=${sprid}`),
|
||||
getOrganizationBySPRID :(sprid)=>api.get(`/api/Organization/list?sprid=${sprid}`),
|
||||
|
||||
assignOrganizationToProject:(data)=>api.post(`/api/Organization/assign/project`,data)
|
||||
};
|
||||
|
@ -13,8 +13,10 @@ const localVariablesSlice = createSlice({
|
||||
reload:false,
|
||||
|
||||
OrganizationModal:{
|
||||
isOpen:false,
|
||||
orgData:null,
|
||||
isOpen: false,
|
||||
orgData: null,
|
||||
prevStep:null,
|
||||
startStep: 1,
|
||||
}
|
||||
|
||||
},
|
||||
@ -39,31 +41,24 @@ const localVariablesSlice = createSlice({
|
||||
},
|
||||
|
||||
openOrgModal: (state, action) => {
|
||||
debugger;
|
||||
state.OrganizationModal.isOpen = true;
|
||||
state.OrganizationModal.orgData = action.payload?.orgData || null;
|
||||
|
||||
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;
|
||||
if (state.OrganizationModal.startStep) {
|
||||
state.OrganizationModal.prevStep = state.OrganizationModal.startStep;
|
||||
}
|
||||
}
|
||||
,
|
||||
|
||||
state.OrganizationModal.startStep = action.payload?.startStep || 1;
|
||||
},
|
||||
closeOrgModal: (state) => {
|
||||
state.OrganizationModal.isOpen = false;
|
||||
state.OrganizationModal.orgData = null;
|
||||
state.OrganizationModal.startStep = 1;
|
||||
state.OrganizationModal.prevStep = null;
|
||||
},
|
||||
toggleOrgModal: (state) => {
|
||||
state.OrganizationModal.isOpen = !state.OrganizationModal.isOpen;
|
||||
},
|
||||
addedOrgModal:(state,action)=>{
|
||||
state.OrganizationModal.orgData = action.payload;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user