added assigned org to project

This commit is contained in:
pramod mahajan 2025-09-19 23:46:06 +05:30
parent 133024bc5c
commit 005fdb3490
16 changed files with 910 additions and 150 deletions

View File

@ -1,11 +1,12 @@
import React from 'react' import React, { useEffect } from 'react'
import ManageOrganization from './components/Organization/ManageOrganization' // import ManageOrganization from './components/Organization/ManageOrganization'
import { useOrganizationModal } from './hooks/useOrganization'; import { useOrganizationModal } from './hooks/useOrganization';
import OrganizationModal from './components/Organization/OrganizationModal';
const ModalProvider = () => { const ModalProvider = () => {
const { isOpen } = useOrganizationModal(); const { isOpen,onClose } = useOrganizationModal();
return <>{isOpen && <ManageOrganization />}</>; return <>{isOpen && <OrganizationModal />}</>;
}; };

View File

@ -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 ( 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;

View 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;

View File

@ -15,14 +15,15 @@ import {
import Label from "../common/Label"; import Label from "../common/Label";
import SelectMultiple from "../common/SelectMultiple"; import SelectMultiple from "../common/SelectMultiple";
import { useServices } from "../../hooks/masterHook/useMaster"; import { useServices } from "../../hooks/masterHook/useMaster";
import AssignOrg from "./AssignOrg";
const ManageOrganization = ({ const ManageOrganization = ({
projectOrganizations = ["ee"], projectOrganizations ,
organizationId = null, organizationId = null,
}) => { }) => {
const [step, setStep] = useState(1); const [step, setStep] = useState(1);
const orgModal = useOrganizationModal(); const orgModal = useOrganizationModal();
const { data: masterService, isLoading } = useServices(); const { data: masterService, isLoading } = useServices();
const [searchText, setSearchText] = useState(); const [searchText, setSearchText] = useState();
const [SPRID, setSPRID] = useState(""); const [SPRID, setSPRID] = useState("");
const { data: orgList, isLoading: orgLoading } = useOrganizationsList( const { data: orgList, isLoading: orgLoading } = useOrganizationsList(
@ -39,7 +40,7 @@ const ManageOrganization = ({
resolver: zodResolver(organizationSchema), resolver: zodResolver(organizationSchema),
defaultValues: defaultOrganizationValues, defaultValues: defaultOrganizationValues,
}); });
console.log(masterService);
const { const {
handleSubmit, handleSubmit,
@ -76,7 +77,7 @@ const ManageOrganization = ({
} }
if (step === 3) { if (step === 3) {
return "Create Organization"; return "Assign Organization";
} }
return "Manage Organization"; // fallback return "Manage Organization"; // fallback
@ -238,105 +239,11 @@ const ManageOrganization = ({
</div> </div>
</div> </div>
)} )}
{/* ---------- STEP 2: Existing Organization Details ---------- */} {/* ---------- STEP 2: Existing Organization Details ---------- */}
{step === 3 && ( {step === 3 && (
<div className="row text-black mb-3"> <AssignOrg Organization={Organization} setStep={setStep}/>
<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>
)} )}
{/* ---------- STEP 3: Add New Organization ---------- */} {/* ---------- STEP 3: Add New Organization ---------- */}
@ -405,7 +312,7 @@ const ManageOrganization = ({
label="Services" label="Services"
required required
valueKey="id" valueKey="id"
options={services?.data || []} options={masterService?.data || []}
/> />
{errors.serviceIds && ( {errors.serviceIds && (
<span className="danger-text">{errors.serviceIds.message}</span> <span className="danger-text">{errors.serviceIds.message}</span>
@ -435,14 +342,7 @@ const ManageOrganization = ({
Back Back
</button> </button>
<div> <div>
<button
type="button"
className="btn btn-sm btn-secondary me-2"
onClick={orgModal.onClose}
disabled={isPending || isLoading}
>
Cancel
</button>
<button <button
type="submit" type="submit"
className="btn btn-sm btn-primary" className="btn btn-sm btn-primary"

View 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;

View 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;

View 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;

View File

@ -30,3 +30,28 @@ export const defaultOrganizationValues = {
email: "", email: "",
serviceIds: [], 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" }),
})

View 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>
);
};

View File

@ -3,7 +3,7 @@ import { useOrganizationModal } from "../../hooks/useOrganization";
import { useSelectedProject } from "../../slices/apiDataManager"; import { useSelectedProject } from "../../slices/apiDataManager";
const ProjectOrganizations = () => { const ProjectOrganizations = () => {
const orgModal = useOrganizationModal(); const {onOpen,startStep} = useOrganizationModal();
const selectedProject = useSelectedProject() const selectedProject = useSelectedProject()
return ( return (
<div className="card"> <div className="card">
@ -12,7 +12,7 @@ const ProjectOrganizations = () => {
<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={() => orgModal.onOpen(selectedProject)} onClick={() => onOpen({startStep:1})}
> >
<i className="bx bx-plus-circle me-2"></i> <i className="bx bx-plus-circle me-2"></i>
Add Organization Add Organization

View File

@ -12,7 +12,13 @@ import showToast from "../../services/toastService";
export const useServices = ()=>{ export const useServices = ()=>{
return useQuery({ return useQuery({
queryKey:["services"], 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 }; return { DocumentCategories, isError, isLoading, error };
} }
export const useOrganizationType =()=>{
return useQuery({
queryKey:["orgType"],
queryFn:async()=>await MasterRespository.getOrganizationType()
})
}
// ===Application Masters Query================================================= // ===Application Masters Query=================================================
const fetchMasterData = async (masterType) => { const fetchMasterData = async (masterType) => {

View File

@ -10,24 +10,40 @@ import showToast from "../services/toastService";
export const useOrganizationModal = () => { export const useOrganizationModal = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { isOpen, orgData } = useSelector( const { isOpen, orgData, startStep, prevStep } = useSelector(
(state) => state.localVariables.OrganizationModal (state) => state.localVariables.OrganizationModal
); );
return { return {
isOpen, isOpen,
orgData, orgData,
onOpen: (dat) => startStep,
dispatch(openOrgModal({ isOpen: true, orgData: dat || null })), 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()), onClose: () => dispatch(closeOrgModal()),
onToggle: () => dispatch(toggleOrgModal()), onToggle: () => dispatch(toggleOrgModal()),
}; };
}; };
export const useOrganizationBySPRID =(sprid)=>{ export const useOrganizationBySPRID =(sprid)=>{
return useQuery({ return useQuery({
queryKey:["organization by",sprid], queryKey:["organization by",sprid],
queryFn:async()=>await OrganizationRepository.getOrganizationBySPRID(sprid), queryFn:async()=>{
const resp = await OrganizationRepository.getOrganizationBySPRID(sprid);
return resp.data;
},
enabled:!!sprid 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"
);
},
});
}

View File

@ -4,7 +4,8 @@ import { useOrganizationModal } from "../../hooks/useOrganization";
import OrganizationsList from "../../components/Organization/OrganizationsList"; import OrganizationsList from "../../components/Organization/OrganizationsList";
const OrganizationPage = () => { const OrganizationPage = () => {
const orgModal = useOrganizationModal() const { isOpen, orgData, startStep, onOpen, } =
useOrganizationModal();
const [searchText,setSearchText] = useState("") const [searchText,setSearchText] = useState("")
return ( return (
@ -33,7 +34,7 @@ const OrganizationPage = () => {
type="button" type="button"
className="p-1 me-1 m-sm-0 bg-primary rounded-circle" className="p-1 me-1 m-sm-0 bg-primary rounded-circle"
title="Add New Organization" title="Add New Organization"
onClick={()=>orgModal.onOpen()} onClick={()=>onOpen({ startStep: 1, })}
> >
<i className="bx bx-plus fs-4 text-white"></i> <i className="bx bx-plus fs-4 text-white"></i>
</button> </button>

View File

@ -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')
}; };

View File

@ -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) assignOrganizationToProject:(data)=>api.post(`/api/Organization/assign/project`,data)
}; };

View File

@ -13,8 +13,10 @@ const localVariablesSlice = createSlice({
reload:false, reload:false,
OrganizationModal:{ OrganizationModal:{
isOpen:false, isOpen: false,
orgData:null, orgData: null,
prevStep:null,
startStep: 1,
} }
}, },
@ -39,31 +41,24 @@ const localVariablesSlice = createSlice({
}, },
openOrgModal: (state, action) => { openOrgModal: (state, action) => {
debugger; state.OrganizationModal.isOpen = true;
state.OrganizationModal.orgData = action.payload?.orgData || null;
if (typeof action.payload === "boolean") { if (state.OrganizationModal.startStep) {
state.OrganizationModal.isOpen = action.payload; state.OrganizationModal.prevStep = state.OrganizationModal.startStep;
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;
} }
}
, state.OrganizationModal.startStep = action.payload?.startStep || 1;
},
closeOrgModal: (state) => { closeOrgModal: (state) => {
state.OrganizationModal.isOpen = false; state.OrganizationModal.isOpen = false;
state.OrganizationModal.orgData = null;
state.OrganizationModal.startStep = 1;
state.OrganizationModal.prevStep = null;
}, },
toggleOrgModal: (state) => { toggleOrgModal: (state) => {
state.OrganizationModal.isOpen = !state.OrganizationModal.isOpen; state.OrganizationModal.isOpen = !state.OrganizationModal.isOpen;
}, },
addedOrgModal:(state,action)=>{
state.OrganizationModal.orgData = action.payload;
}
}, },
}); });