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

This commit is contained in:
Kartik Sharma 2025-09-20 19:32:41 +05:30
commit a0f7e5c57b
14 changed files with 399 additions and 1026 deletions

775
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="120" height="120" fill="#EFF1F3"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M33.2503 38.4816C33.2603 37.0472 34.4199 35.8864 35.8543 35.875H83.1463C84.5848 35.875 85.7503 37.0431 85.7503 38.4816V80.5184C85.7403 81.9528 84.5807 83.1136 83.1463 83.125H35.8543C34.4158 83.1236 33.2503 81.957 33.2503 80.5184V38.4816ZM80.5006 41.1251H38.5006V77.8751L62.8921 53.4783C63.9172 52.4536 65.5788 52.4536 66.6039 53.4783L80.5006 67.4013V41.1251ZM43.75 51.6249C43.75 54.5244 46.1005 56.8749 49 56.8749C51.8995 56.8749 54.25 54.5244 54.25 51.6249C54.25 48.7254 51.8995 46.3749 49 46.3749C46.1005 46.3749 43.75 48.7254 43.75 51.6249Z" fill="#687787"/>
</svg>

After

Width:  |  Height:  |  Size: 888 B

View File

@ -11,6 +11,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { assignedOrgToProject } from "./OrganizationSchema"; import { assignedOrgToProject } from "./OrganizationSchema";
import { import {
useAssignOrgToProject, useAssignOrgToProject,
useAssignOrgToTenant,
useOrganizationModal, useOrganizationModal,
} from "../../hooks/useOrganization"; } from "../../hooks/useOrganization";
@ -24,56 +25,73 @@ const AssignOrg = ({ setStep }) => {
useProjectAssignedServices(selectedProject); useProjectAssignedServices(selectedProject);
const { data: orgType, isLoading: orgLoading } = useOrganizationType(); const { data: orgType, isLoading: orgLoading } = useOrganizationType();
const { mutate: AssignToProject, isPending } = useAssignOrgToProject(() => const { mutate: AssignToProject, isPending: isPendingProject } =
onClose() useAssignOrgToProject(() => onClose());
); const { mutate: AssignToTenant, isPending: isPendingTenat } =
useAssignOrgToTenant(() => {
onClose();
});
const isPending = isPendingProject || isPendingTenat;
const mergedServices = useMemo(() => { const mergedServices = useMemo(() => {
if (!masterService || !projectServices) return []; if (!masterService || !projectServices) return [];
const combined = [...masterService?.data, ...projectServices]; const combined = [...masterService?.data, ...projectServices];
return combined.filter(
const unique = combined.filter(
(item, index, self) => index === self.findIndex((s) => s.id === item.id) (item, index, self) => index === self.findIndex((s) => s.id === item.id)
); );
return unique;
}, [masterService, projectServices]); }, [masterService, projectServices]);
const resolver =
flowType === "default" ? undefined : zodResolver(assignedOrgToProject);
const { const {
register, register,
setValue,
handleSubmit, handleSubmit,
setValue,
formState: { errors }, formState: { errors },
} = useForm({ } = useForm({
resolver: zodResolver(assignedOrgToProject), resolver,
defaultValues: {
organizationTypeId: "",
serviceIds: [],
},
}); });
const onSubmit = (formData) => { const onSubmit = (formData) => {
const payload = { if (flowType === "default") {
...formData, const payload = orgData.id;
projectId: selectedProject,
organizationId: orgData.id, AssignToTenant(payload);
parentOrganizationId: null, } else {
}; const payload = {
AssignToProject(payload); ...formData,
projectId: selectedProject,
organizationId: orgData.id,
parentOrganizationId: null,
};
AssignToProject(payload);
}
}; };
const handleEdit = () => { const handleEdit = () => {
onOpen({ startStep: 4, orgData: orgData }); onOpen({ startStep: 4, 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 (
<div className="row text-black mb-3"> <div className="row text-black text-start mb-3">
{/* Organization Info Display */}
<div className="col-12 mb-3"> <div className="col-12 mb-3">
<div className="d-flex justify-content-between align-items-center text-start mb-2"> <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="fw-semibold text-wrap">{orgData.name}</div>
@ -89,21 +107,22 @@ const AssignOrg = ({ setStep }) => {
</div> </div>
</div> </div>
{/* Contact Info */}
<div className="col-md-6 mb-3"> <div className="col-md-6 mb-3">
<div className="d-flex"> <div className="d-flex">
<label <label
className="form-label me-2 mb-0 fw-semibold text-start" className="form-label me-2 mb-0 fw-semibold"
style={{ minWidth: "130px" }} style={{ minWidth: "130px" }}
> >
Constact Person : Contact Person :
</label> </label>
<div className="text-muted">{orgData.name}</div> <div className="text-muted">{orgData.contactPerson}</div>
</div> </div>
</div> </div>
<div className="col-md-6 mb-3"> <div className="col-md-6 mb-3">
<div className="d-flex"> <div className="d-flex">
<label <label
className="form-label me-2 mb-0 fw-semibold text-start" className="form-label me-2 mb-0 fw-semibold"
style={{ minWidth: "130px" }} style={{ minWidth: "130px" }}
> >
Contact Number : Contact Number :
@ -114,7 +133,7 @@ const AssignOrg = ({ setStep }) => {
<div className="col-md-6 mb-3"> <div className="col-md-6 mb-3">
<div className="d-flex"> <div className="d-flex">
<label <label
className="form-label me-2 mb-0 fw-semibold text-start" className="form-label me-2 mb-0 fw-semibold"
style={{ minWidth: "130px" }} style={{ minWidth: "130px" }}
> >
Email Address : Email Address :
@ -125,7 +144,7 @@ const AssignOrg = ({ setStep }) => {
<div className="col-12 mb-3"> <div className="col-12 mb-3">
<div className="d-flex"> <div className="d-flex">
<label <label
className="form-label me-2 mb-0 fw-semibold text-start text-wrap" className="form-label me-2 mb-0 fw-semibold"
style={{ maxWidth: "130px" }} style={{ maxWidth: "130px" }}
> >
Service provider Id (SPRID) : Service provider Id (SPRID) :
@ -133,11 +152,10 @@ const AssignOrg = ({ setStep }) => {
<div className="text-muted">{orgData.sprid}</div> <div className="text-muted">{orgData.sprid}</div>
</div> </div>
</div> </div>
<div className="col-12 mb-3"> <div className="col-12 mb-3">
<div className="d-flex"> <div className="d-flex">
<label <label
className="form-label me-1 mb-0 fw-semibold text-start" className="form-label me-1 mb-0 fw-semibold"
style={{ minWidth: "130px" }} style={{ minWidth: "130px" }}
> >
Address : Address :
@ -146,68 +164,72 @@ const AssignOrg = ({ setStep }) => {
</div> </div>
</div> </div>
{/* Form */}
<div className="text-black text-start"> <div className="text-black text-start">
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<div className="mb-1 text-start"> {/* Show fields only if flowType is NOT default */}
<Label htmlFor="organizationTypeId" required> {flowType !== "default" && (
Organization Type <>
</Label> {/* Organization Type */}
<div className="mb-1 text-start">
<div className="d-flex flex-wrap gap-3 mt-1"> <Label htmlFor="organizationTypeId" required>
{orgType?.data.map((type) => ( Organization Type
<div </Label>
key={type.id} <div className="d-flex flex-wrap gap-3 mt-1">
className="form-check d-flex align-items-center gap-2 p-0 m-0" {orgType?.data.map((type) => (
> <div
<input key={type.id}
type="radio" className="form-check d-flex align-items-center gap-2 p-0 m-0"
id={`organizationType-${type.id}`} >
value={type.id} <input
{...register("organizationTypeId", { type="radio"
required: "Please select an organization type", id={`organizationType-${type.id}`}
})} value={type.id}
className="form-check-input m-0" {...register("organizationTypeId")}
/> className="form-check-input m-0"
<label />
className="form-check-label m-0" <label
htmlFor={`organizationType-${type.id}`} className="form-check-label m-0"
> htmlFor={`organizationType-${type.id}`}
{type.name} >
</label> {type.name}
</label>
</div>
))}
</div> </div>
))} {errors.organizationTypeId && (
</div> <span className="text-danger">
{errors.organizationTypeId.message}
{errors.organizationTypeId && ( </span>
<span className="danger-text"> )}
{errors.organizationTypeId.message}
</span>
)}
</div>
<div className="mb-3">
<Label htmlFor="serviceId" required>
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> </div>
))}
{errors.serviceIds && (
<div className="text-danger small">
{errors.serviceIds.message}
</div>
)}
</div>
{/* Buttons */} {/* Services */}
<div className="mb-3">
<Label htmlFor="serviceIds" required>
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: Always visible */}
<div className="d-flex justify-content-between mt-3"> <div className="d-flex justify-content-between mt-3">
<button <button
type="button" type="button"
@ -222,7 +244,11 @@ const AssignOrg = ({ setStep }) => {
className="btn btn-sm btn-primary" className="btn btn-sm btn-primary"
disabled={isPending} disabled={isPending}
> >
{isPending ? "Please wait..." : "Add"} {isPending
? "Please wait..."
: flowType === "default"
? "Assign Organization"
: "Add"}
</button> </button>
</div> </div>
</form> </form>

View File

@ -14,7 +14,8 @@ import { OrgCardSkeleton } from "./OrganizationSkeleton";
// Zod schema: only allow exactly 4 digits // Zod schema: only allow exactly 4 digits
const OrgPickerFromSPId = ({ title, placeholder }) => { const OrgPickerFromSPId = ({ title, placeholder }) => {
const {onClose, startStep, flowType, onOpen,prevStep } = useOrganizationModal(); const { onClose, startStep, flowType, onOpen, prevStep } =
useOrganizationModal();
const { const {
register, register,
@ -35,18 +36,7 @@ const OrgPickerFromSPId = ({ title, placeholder }) => {
setSPRID(formdata.spridSearchText); setSPRID(formdata.spridSearchText);
}; };
const {mutate:AssignToTenant,isPending} = useAssignOrgToTenant(()=>{ const handleOrg = (orgId) => {};
onClose()
})
const handleOrg=(orgId)=>{
if(flowType == "default"){
AssignToTenant(orgId)
}else{
debugger
onOpen({ startStep: 3, orgData: org })
}
}
const SP = watch("spridSearchText"); const SP = watch("spridSearchText");
return ( return (
<div className="d-block"> <div className="d-block">
@ -54,22 +44,20 @@ const handleOrg=(orgId)=>{
className="d-flex flex-row gap-6 text-start align-items-center" className="d-flex flex-row gap-6 text-start align-items-center"
onSubmit={handleSubmit(onSubmit)} onSubmit={handleSubmit(onSubmit)}
> >
<div className="d-flex flex-column"> <div className="d-flex flex-row align-items-center gap-2">
<Label className="text-secondary">{title}</Label> <Label className="text-secondary">Search by SPRID</Label>
<input <input
type="search" type="search"
{...register("spridSearchText")} {...register("spridSearchText")}
className="form-control form-control-sm w-auto" className="form-control form-control-sm w-auto"
placeholder={placeholder || "Enter Service Provider ID"} placeholder="Enter SPRID"
maxLength={4} maxLength={4}
/> />
</div> </div>
<div className="mt-4"> <button type="submit" className="btn btn-sm btn-primary">
<button type="submit" className="btn btn-sm btn-primary"> <i className="bx bx-sm bx-search-alt-2"></i> Search
<i className="bx bx-sm bx-search-alt-2"></i> Search </button>
</button>
</div>
</form> </form>
<div className="text-start danger-text"> <div className="text-start danger-text">
{" "} {" "}
@ -87,28 +75,32 @@ const handleOrg=(orgId)=>{
<div className="py-2 text-tiny text-center"> <div className="py-2 text-tiny text-center">
<div className="d-flex flex-column gap-2 border-0 bg-none"> <div className="d-flex flex-column gap-2 border-0 bg-none">
{data.data.map((org) => ( {data.data.map((org) => (
<div <div className="d-flex flex-row gap-2 text-start text-black ">
key={org.id} <div className="mt-1">
className="list-group-item list-group-item-action d-flex align-items-center cursor-pointer border-0 hover-overlay shadow-1-strong rounded" <img
> src="/public/assets/img/SP-Placeholdeer.svg"
<div className="d-flex align-items-center justify-content-center me-3"> alt="logo"
<i className="bx bx-building-house bx-md text-primary"></i> width={50}
height={50}
/>
</div> </div>
<div
<div className="w-100"> className="d-flex flex-column p-0 m-0 cursor-pointer"
<div className="d-flex justify-content-between cursor-pointer"> onClick={() => onOpen({ startStep: 3, orgData: org })}
<div className="user-info text-start"> >
<h6 className="mb-1 fw-normal">{org.name}</h6> <span className="fs-6 fw-semibold">{org.name}</span>
</div> <div className="d-flex gap-2">
<div className="add-btn"> <small
<button className=" fw-semibold text-uppercase"
type="button" style={{ letterSpacing: "1px" }}
className="btn btn-primary btn-sm" >
onClick={()=>handleOrg(org.id)} SPRID :{" "}
> </small>
{isPending ? "Please Wait..." : flowType === "assign" ? "Select":`Add`} <small className="fs-6">{org.sprid}</small>
</button> </div>
</div> <div className="d-flex flex-row gap-2">
<small className="text-small fw-semibold">Address:</small>
<div className="d-flex text-wrap">{org.address}</div>
</div> </div>
</div> </div>
</div> </div>
@ -119,38 +111,32 @@ const handleOrg=(orgId)=>{
<div className="py-3 text-center text-secondary"> <div className="py-3 text-center text-secondary">
No organization found for "{SPRID}" No organization found for "{SPRID}"
</div> </div>
) : ( ) : null}
<div className="py-12 text-center text-tiny text-black"> <div className="py-12 text-center text-tiny text-black">
Type a Service Provider ID to find an organization. <small className="d-block text-secondary">
</div> Do not have SPRID or could not find organization ?
)} </small>
<button
type="button"
className="btn btn-sm btn-primary mt-3"
onClick={() => onOpen({ startStep: 4 })}
>
<i className="bx bx-plus-circle me-2"></i>
Create New Organization
</button>
</div>
{/* ---- Footer buttons ---- */} {/* ---- Footer buttons ---- */}
<div <div className={`d-flex text-secondary mt-3`}>
className={`d-flex ${
flowType !== "default"
? "justify-content-between"
: "justify-content-end"
} text-secondary mt-3`}
>
{flowType !== "default" && ( {flowType !== "default" && (
<button <button
type="button" type="button"
className="btn btn-xs btn-outline-secondary" className="btn btn-xs btn-outline-secondary"
onClick={() => onOpen({ startStep: prevStep })} onClick={() => onOpen({ startStep: prevStep })}
> >
<i className='bx bx-chevron-left'></i> Back <i className="bx bx-chevron-left"></i> Back
</button> </button>
)} )}
<button
type="button"
className="btn btn-sm btn-secondary"
onClick={() => onOpen({ startStep: 4 })}
>
<i className="bx bx-plus-circle me-2"></i>
Add New Organization
</button>
</div> </div>
</div> </div>
); );

View File

@ -11,7 +11,7 @@ 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(
ITEMS_PER_PAGE-10, ITEMS_PER_PAGE - 10,
1, 1,
true, true,
null, null,
@ -36,6 +36,38 @@ const OrgPickerfromTenant = ({ title }) => {
} }
}; };
const contactList = [
{
key: "name",
label: "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: "SPRID",
getValue: (org) => (
<span
className="text-truncate d-inline-block"
style={{ maxWidth: "200px" }}
>
{org?.sprid || "N/A"}
</span>
),
align: "text-center",
},
];
return ( return (
<div className="d-block"> <div className="d-block">
<div className="text-start mb-1"> <div className="text-start mb-1">
@ -50,72 +82,60 @@ const OrgPickerfromTenant = ({ title }) => {
</div> </div>
{/* ---- Organization list ---- */} {/* ---- Organization list ---- */}
{isLoading ? ( {isLoading ? (
<div>Loading....</div> <div>Loading....</div>
) : data && data?.data?.length > 0 ? ( ) : data && data?.data?.length > 0 ? (
<div className="py-2 text-tiny text-center"> <div className="dataTables_wrapper no-footer pb-2">
<div className="d-flex flex-column gap-2 border-0 bg-none"> <table className="table dataTable text-nowrap">
{data?.data?.map((org) => ( <thead>
<div <tr className="table_header_border">
key={org.id} {contactList.map((col) => (
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" <th key={col.key} className={col.align}>
> {col.label}
<div className="d-flex align-items-center justify-content-center me-3"> </th>
<i className="bx bx-building-house bx-md text-primary"></i> ))}
</div> <th className="sticky-action-column bg-white text-center">
Action
<div className="w-100"> </th>
<div className="d-flex justify-content-between cursor-pointer"> </tr>
<div className="user-info text-start"> </thead>
<h6 className="mb-1 fw-normal">{org.name}</h6> <tbody>
</div> {Array.isArray(data.data) && data.data.length > 0
<div className="add-btn"> ? data.data.map((row, i) => (
<button <tr key={i}>
className="btn btn-primary btn-sm" {contactList.map((col) => (
onClick={() => onOpen({ startStep: 3, orgData: org })} <td key={col.key} className={col.align}>
> {col.getValue(row)}
select </td>
</button> ))}
</div> <td className="sticky-action-column bg-white">
</div> <button
</div> className="btn btn-sm btn-primary"
onClick={() => onOpen({ startStep: 3, orgData: row })}
>
Select
</button>
</td>
</tr>
))
: null}
</tbody>
</table>
</div> </div>
))} ) : null}
<div className="d-flex flex-column align-items-center text-center text-wrap text-black gap-2">
<div className="d-flex text-end"> <small className="mb-1">
{data?.data?.length > 0 && ( Could not find organization in your database? Please search within the
<Pagination global database.
currentPage={currentPage} </small>
totalPages={data.totalPages} <button
onPageChange={paginate} type="button"
/> className="btn btn-sm btn-primary w-auto"
)} onClick={() => onOpen({ startStep: 2 })}
>
Search Using SPRID
</button>
</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>
) : (
<div>Not found Organization</div>
)}
{/* ---- Footer buttons ---- */} {/* ---- Footer buttons ---- */}
<div <div
className={`d-flex justify-content-end className={`d-flex justify-content-end
@ -131,14 +151,14 @@ const OrgPickerfromTenant = ({ title }) => {
</button> </button>
)} )}
<button {/* <button
type="button" type="button"
className="btn btn-sm btn-secondary" className="btn btn-sm btn-secondary"
onClick={() => onOpen({ startStep: 4 })} onClick={() => onOpen({ startStep: 4 })}
> >
<i className="bx bx-plus-circle me-2"></i> <i className="bx bx-plus-circle me-2"></i>
Add New Organization Add New Organization
</button> </button> */}
</div> </div>
</div> </div>
); );

View File

@ -26,8 +26,7 @@ const OrganizationModal = () => {
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 [Organization, setOrganization] = useState({}); const [Organization, setOrganization] = useState({});
const method = useForm({ const method = useForm({
@ -61,11 +60,11 @@ const OrganizationModal = () => {
if (startStep === 1) { if (startStep === 1) {
return orgData && orgData !== null return orgData && orgData !== null
? "Add Organization" ? "Add Organization"
: "Choose Organization"; : "Choose Organization1";
} }
if (startStep === 2) { if (startStep === 2) {
return "Choose Organization"; return "Add Organization";
} }
if (startStep === 3) { if (startStep === 3) {
@ -78,20 +77,13 @@ const OrganizationModal = () => {
const contentBody = ( const contentBody = (
<div> <div>
{/* ---------- STEP 1: Service Provider- Form Own Tenant list ---------- */} {/* ---------- STEP 1: Service Provider- Form Own Tenant list ---------- */}
{startStep === 1 && ( {startStep === 1 && <OrgPickerfromTenant title="Find Organization" />}
<OrgPickerfromTenant
title="Find Organization"
/>
)}
{startStep === 2 && ( {startStep === 2 && (
<OrgPickerFromSPId <OrgPickerFromSPId
title="Find Organization" title="Find Organization"
placeholder="Enter Service Provider Id" placeholder="Enter Service Provider Id"
projectOrganizations={orgData} projectOrganizations={orgData}
/> />
)} )}
@ -101,10 +93,7 @@ const OrganizationModal = () => {
)} )}
{/* ---------- STEP 3: Add New Organization ---------- */} {/* ---------- STEP 3: Add New Organization ---------- */}
{startStep === 4 && ( {startStep === 4 && <ManagOrg />}
<ManagOrg />
)}
</div> </div>
); );

View File

@ -7,8 +7,8 @@ export const organizationSchema = z.object({
contactNumber: z contactNumber: z
.string() .string()
.trim() .trim()
.min(7, { message: "Contact number must be at least 7 digits" }) .min(7, { message: "Contact number must be at least 7 digits" })
.max(20, { message: "Contact number cannot exceed 12 digits" }) .max(20, { message: "Contact number cannot exceed 12 digits" })
.regex(phoneRegex, { message: "Invalid phone number" }), .regex(phoneRegex, { message: "Invalid phone number" }),
contactPerson: z.string().min(1, { message: "Person name required" }), contactPerson: z.string().min(1, { message: "Person name required" }),
@ -31,7 +31,6 @@ export const defaultOrganizationValues = {
serviceIds: [], serviceIds: [],
}; };
export const assignedOrgToProject = z.object({ export const assignedOrgToProject = z.object({
// projectId: z.string().uuid({ message: "Invalid projectId format" }), // projectId: z.string().uuid({ message: "Invalid projectId format" }),
// organizationId: z.string().string({ message: "Invalid organizationId format" }), // organizationId: z.string().string({ message: "Invalid organizationId format" }),
@ -39,19 +38,20 @@ export const assignedOrgToProject = z.object({
// .string() // .string()
// .nullable() // .nullable()
// .optional(), // .optional(),
serviceIds: z.preprocess( serviceIds: z.preprocess(
(val) => (Array.isArray(val) ? val : []), (val) => (Array.isArray(val) ? val : []),
z z
.array(z.string().uuid({ message: "Invalid serviceId format" })) .array(z.string().uuid({ message: "Invalid serviceId format" }))
.nonempty({ message: "At least one service must be selected" }) .nonempty({ message: "At least one service must be selected" })
), ),
organizationTypeId: z
organizationTypeId: z.string().min(1,{ message: "Organization is required" }), .string()
.min(1, { message: "Organization is required" }),
}); });
export const spridSchema = z.object({ export const spridSchema = z.object({
spridSearchText: z spridSearchText: z
.string() .string()
.regex(/^\d{4}$/, { message: "SPRID must be exactly 4 digits" }), .regex(/^\d{4}$/, { message: "SPRID must be exactly 4 digits" }),
}) });

View File

@ -9,27 +9,32 @@ const SkeletonLine = ({ height = 20, width = "100%", className = "" }) => (
); );
export const OrgCardSkeleton = () => { export const OrgCardSkeleton = () => {
return ( return (
<div className="row p-5"> <div className="row p-3">
{[...Array(1)].map((_, idx) => ( {[...Array(1)].map((_, idx) => (
<div <div
key={idx} key={idx}
className="list-group-item d-flex align-items-center border-0 shadow-sm rounded mb-3" className="list-group-item d-flex flex-row gap-2 align-items-start border-0 shadow-sm rounded mb-3 p-3"
> >
<div className="d-flex align-items-center justify-content-center me-3 p-1"> {/* Left: Logo/avatar placeholder */}
<SkeletonLine height={40} width={40} className="rounded-circle" /> <div className="mt-1">
<SkeletonLine height={50} width={50} className="rounded-circle" />
</div> </div>
{/* Right: Info section */}
<div className="w-100 px-1"> <div className="d-flex flex-column flex-grow-1 text-start">
<div className="d-flex justify-content-between px-"> {/* Org name */}
<div className="user-info text-start"> <SkeletonLine height={18} width="160px" className="mb-2" />
<SkeletonLine height={16} width="120px" className="mb-1" />
<SkeletonLine height={12} width="80px" /> {/* SPRID */}
</div> <div className="d-flex gap-2 mb-2">
<div className="add-btn px-2"> <SkeletonLine height={14} width="60px" />
<SkeletonLine height={30} width="60px" className="rounded" /> <SkeletonLine height={14} width="100px" />
</div> </div>
{/* Address */}
<div className="d-flex gap-2">
<SkeletonLine height={14} width="70px" />
<SkeletonLine height={14} width="100%" />
</div> </div>
</div> </div>
</div> </div>

View File

@ -0,0 +1 @@
useAssignOrgToTenant

View File

@ -1,4 +1,3 @@
import { useCallback } from "react"; import { useCallback } from "react";
const Modal = ({ const Modal = ({
@ -7,16 +6,14 @@ const Modal = ({
title, title,
body, body,
disabled, disabled,
size="md", size = "md",
position="top", position = "top",
}) => { }) => {
const handleClose = useCallback(() => { const handleClose = useCallback(() => {
if (disabled) return; if (disabled) return;
onClose(); onClose();
}, [disabled, onClose]); }, [disabled, onClose]);
if (!isOpen) return null; if (!isOpen) return null;
return ( return (
@ -26,7 +23,10 @@ const Modal = ({
tabIndex="-1" tabIndex="-1"
role="dialog" role="dialog"
> >
<div className={`modal-dialog modal-${size} modal-dialog-${position}`} role="document"> <div
className={`modal-dialog modal-${size} modal-dialog-${position}`}
role="document"
>
<div className="modal-content text-white shadow-lg"> <div className="modal-content text-white shadow-lg">
{/* Header */} {/* Header */}
<div className="modal-header pb-2 border-0"> <div className="modal-header pb-2 border-0">
@ -36,16 +36,11 @@ const Modal = ({
className="btn-close btn-close-white" className="btn-close btn-close-white"
onClick={handleClose} onClick={handleClose}
aria-label="Close" aria-label="Close"
> ></button>
</button>
</div> </div>
{/* Body */} {/* Body */}
<div className="modal-body pt-0">{body}</div> <div className="modal-body pt-0">{body}</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -27,7 +27,7 @@ export const useOrganizationModal = () => {
orgData: options.orgData ?? orgData ?? null, orgData: options.orgData ?? orgData ?? null,
startStep: options.startStep ?? startStep ?? 1, startStep: options.startStep ?? startStep ?? 1,
prevStep: options.prevStep ?? prevStep ?? 1, prevStep: options.prevStep ?? prevStep ?? 1,
flowType: options.flowType ?? flowType ?? "default", flowType: options.flowType ?? flowType ?? "default",
}) })
), ),
onClose: () => dispatch(closeOrgModal()), onClose: () => dispatch(closeOrgModal()),
@ -35,21 +35,16 @@ export const useOrganizationModal = () => {
}; };
}; };
export const useOrganizationBySPRID = (sprid) => {
export const useOrganizationBySPRID =(sprid)=>{
return useQuery({ return useQuery({
queryKey:["organization by",sprid], queryKey: ["organization by", sprid],
queryFn:async()=>{ queryFn: async () => {
const resp = await OrganizationRepository.getOrganizationBySPRID(sprid); const resp = await OrganizationRepository.getOrganizationBySPRID(sprid);
return resp.data; return resp.data;
}, },
enabled:!!sprid enabled: !!sprid,
}) });
} };
export const useOrganizationsList = ( export const useOrganizationsList = (
pageSize, pageSize,
@ -102,13 +97,15 @@ export const useCreateOrganization = (onSuccessCallback) => {
}); });
}; };
export const useAssignOrgToProject=(onSuccessCallback)=>{ export const useAssignOrgToProject = (onSuccessCallback) => {
const useClient = useQueryClient(); const useClient = useQueryClient();
return useMutation({ return useMutation({
mutationFn: async (payload) => mutationFn: async (payload) =>
await OrganizationRepository.assignOrganizationToProject(payload), await OrganizationRepository.assignOrganizationToProject(payload),
onSuccess: (_, variables) => { onSuccess: (_, variables) => {
// useClient.invalidateQueries({ queryKey: ["organizationList"] }); useClient.invalidateQueries({
queryKey: ["projectAssignedOrganiztions"],
});
showToast("Organization successfully", "success"); showToast("Organization successfully", "success");
if (onSuccessCallback) onSuccessCallback(); if (onSuccessCallback) onSuccessCallback();
}, },
@ -121,9 +118,9 @@ export const useAssignOrgToProject=(onSuccessCallback)=>{
); );
}, },
}); });
} };
export const useAssignOrgToTenant =(onSuccessCallback)=>{ export const useAssignOrgToTenant = (onSuccessCallback) => {
const useClient = useQueryClient(); const useClient = useQueryClient();
return useMutation({ return useMutation({
mutationFn: async (payload) => mutationFn: async (payload) =>
await OrganizationRepository.assignOrganizationToTenanat(payload), await OrganizationRepository.assignOrganizationToTenanat(payload),
@ -141,9 +138,9 @@ export const useAssignOrgToTenant =(onSuccessCallback)=>{
); );
}, },
}); });
} };
export const useUpdateOrganization=()=>{ export const useUpdateOrganization = () => {
const useClient = useQueryClient(); const useClient = useQueryClient();
return useMutation({ return useMutation({
mutationFn: async (payload) => mutationFn: async (payload) =>
await OrganizationRepository.assignOrganizationToProject(payload), await OrganizationRepository.assignOrganizationToProject(payload),
@ -161,4 +158,4 @@ export const useUpdateOrganization=()=>{
); );
}, },
}); });
} };

View File

@ -185,7 +185,7 @@ export const useProjectInfra = (projectId) => {
data: projectInfra, data: projectInfra,
isLoading, isLoading,
error, error,
isFetched isFetched,
} = useQuery({ } = useQuery({
queryKey: ["ProjectInfra", projectId], queryKey: ["ProjectInfra", projectId],
queryFn: async () => { queryFn: async () => {
@ -199,7 +199,7 @@ export const useProjectInfra = (projectId) => {
}, },
}); });
return { projectInfra, isLoading, error,isFetched }; return { projectInfra, isLoading, error, isFetched };
}; };
export const useProjectTasks = (workAreaId, IsExpandedArea = false) => { export const useProjectTasks = (workAreaId, IsExpandedArea = false) => {
@ -276,26 +276,30 @@ export const useProjectLevelEmployeePermission = (employeeId, projectId) => {
}); });
}; };
export const useProjectAssignedOrganizations =(projectId)=>{ export const useProjectAssignedOrganizations = (projectId) => {
return useQuery({
queryKey: ["projectAssignedOrganiztions", projectId],
queryFn: async () => {
const resp = await ProjectRepository.getProjectAssignedOrganizations(
projectId
);
return resp.data;
},
enabled: !!projectId,
});
};
export const useProjectAssignedServices = (projectId) => {
return useQuery({ return useQuery({
queryKey: ["projectAssignedOrganization", projectId], queryKey: ["projectAssignedOrganization", projectId],
queryFn: async () => { queryFn: async () => {
const resp = await ProjectRepository.getProjectAssignedOrganizations(projectId); const resp = await ProjectRepository.getProjectAssignedServices(
projectId
);
return resp.data; return resp.data;
}, },
enabled:!!projectId, enabled: !!projectId,
}); });
} };
export const useProjectAssignedServices =(projectId)=>{
return useQuery({
queryKey: ["projectAssignedServices", projectId],
queryFn: async () => {
const resp = await ProjectRepository.getProjectAssignedServices(projectId);
return resp.data;
},
enabled:!!projectId,
});
}
// -- -------------Mutation------------------------------- // -- -------------Mutation-------------------------------

View File

@ -0,0 +1 @@
login

View File

@ -2,7 +2,7 @@ import { api } from "../utils/axiosClient";
const AuthRepository = { const AuthRepository = {
// Public routes (no auth token required) // Public routes (no auth token required)
login: (data) => api.postPublic("/api/auth/login", data), login: (data) => api.postPublic("/api/auth/login/v1", data),
refreshToken: (data) => api.postPublic("/api/auth/refresh-token", data), refreshToken: (data) => api.postPublic("/api/auth/refresh-token", data),
forgotPassword: (data) => api.postPublic("/api/auth/forgot-password", data), forgotPassword: (data) => api.postPublic("/api/auth/forgot-password", data),
resetPassword: (data) => api.postPublic("/api/auth/reset-password", data), resetPassword: (data) => api.postPublic("/api/auth/reset-password", data),
@ -15,8 +15,7 @@ const AuthRepository = {
logout: (data) => api.post("/api/auth/logout", data), logout: (data) => api.post("/api/auth/logout", data),
profile: () => api.get("/api/user/profile"), profile: () => api.get("/api/user/profile"),
changepassword: (data) => api.post("/api/auth/change-password", data), changepassword: (data) => api.post("/api/auth/change-password", data),
appmenu:()=>api.get('/api/appmenu/get/menu') appmenu: () => api.get("/api/appmenu/get/menu"),
}; };
export default AuthRepository; export default AuthRepository;