Compare commits
33 Commits
01a5766074
...
1452e77bc5
| Author | SHA1 | Date | |
|---|---|---|---|
| 1452e77bc5 | |||
| 7fa2ca9227 | |||
| 0d9ef7f248 | |||
| 9a3488c92b | |||
| 1b144aab8a | |||
| 2e65007f26 | |||
| 3c4c25b449 | |||
| 979293ad90 | |||
| 99eaf92e3f | |||
| 28b0541894 | |||
| a48fc1d989 | |||
| 78a0ecebf1 | |||
| 8eb8e27f89 | |||
| 012a89b3ea | |||
| 18698a67e3 | |||
| 701d1adc0b | |||
| b2c68824dd | |||
| b3b7297bc3 | |||
| 9311f41f56 | |||
| 9ba2ecfb1f | |||
| a27b8571b5 | |||
| fd36298543 | |||
| 09bb58e50e | |||
| 1c0e8655c4 | |||
| 6211f52e3a | |||
| df9107f0d8 | |||
| eea7252b96 | |||
| 442ecff926 | |||
| 0abd77dab7 | |||
| 4b0ea3a0db | |||
| d3218eb77a | |||
| 4ad87af7f4 | |||
| 7b15309dbf |
@ -1,8 +1,12 @@
|
|||||||
:root,
|
:root,
|
||||||
[data-bs-theme="light"] {
|
[data-bs-theme="light"] {
|
||||||
--bs-nav-link-font-size: 0.7375rem;
|
--bs-nav-link-font-size: 0.7375rem;
|
||||||
|
--bg-border-color :#f8f6f6
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
padding: 0.5rem var(--bs-card-cap-padding-x);
|
padding: 0.5rem var(--bs-card-cap-padding-x);
|
||||||
}
|
}
|
||||||
|
.table_header_border {
|
||||||
|
border-bottom:2px solid var(--bs-table-border-color) ;
|
||||||
|
}
|
||||||
@ -19,54 +19,60 @@ import { useSelectedProject } from "../../slices/apiDataManager";
|
|||||||
import Loader from "../common/Loader";
|
import Loader from "../common/Loader";
|
||||||
|
|
||||||
|
|
||||||
const InfraPlanning = () =>
|
|
||||||
{
|
|
||||||
const {profile: LoggedUser, refetch : fetchData} = useProfile()
|
const InfraPlanning = () => {
|
||||||
const dispatch = useDispatch()
|
const { profile: LoggedUser, refetch: fetchData } = useProfile();
|
||||||
// const selectedProject = useSelector((store)=>store.localVariables.projectId)
|
const dispatch = useDispatch();
|
||||||
const selectedProject = useSelectedProject();
|
const selectedProject = useSelectedProject();
|
||||||
const {projectInfra, isLoading, error} = useProjectInfra( selectedProject )
|
|
||||||
|
|
||||||
|
const { projectInfra, isLoading, isError, error, isFetched } = useProjectInfra(selectedProject);
|
||||||
|
|
||||||
|
const canManageInfra = useHasUserPermission(MANAGE_PROJECT_INFRA);
|
||||||
|
const canApproveTask = useHasUserPermission(APPROVE_TASK);
|
||||||
|
const canReportTask = useHasUserPermission(ASSIGN_REPORT_TASK);
|
||||||
|
|
||||||
const ManageInfra = useHasUserPermission( MANAGE_PROJECT_INFRA )
|
const reloadedData = useSelector((store) => store.localVariables.reload);
|
||||||
const ApprovedTaskRights = useHasUserPermission(APPROVE_TASK)
|
|
||||||
const ReportTaskRights = useHasUserPermission(ASSIGN_REPORT_TASK)
|
|
||||||
const reloadedData = useSelector( ( store ) => store.localVariables.reload )
|
|
||||||
|
|
||||||
|
const hasAccess = canManageInfra || canApproveTask || canReportTask;
|
||||||
|
|
||||||
// useEffect( () =>
|
if (isError) {
|
||||||
// {
|
return <div>{error?.response?.data?.message || error?.message}</div>;
|
||||||
// if (reloadedData)
|
}
|
||||||
// {
|
|
||||||
// refetch()
|
|
||||||
// dispatch( refreshData( false ) )
|
|
||||||
// }
|
|
||||||
|
|
||||||
// },[reloadedData])
|
if (!hasAccess && !isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="text-center">
|
||||||
|
<i className="fa-solid fa-triangle-exclamation fs-5"></i>
|
||||||
|
<p>Access Denied: You don't have permission to perform this action.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <Loader />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFetched && (!projectInfra || projectInfra.length === 0)) {
|
||||||
|
return (
|
||||||
|
<div className="card text-center">
|
||||||
|
<p className="my-3">No Result Found</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="col-md-12 col-lg-12 col-xl-12 order-0 mb-4">
|
<div className="col-md-12 col-lg-12 col-xl-12 order-0 mb-4">
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<div className="card-body" style={{ padding: "0.5rem" }}>
|
<div className="card-body" style={{ padding: "0.5rem" }}>
|
||||||
{(ApprovedTaskRights || ReportTaskRights) ? (
|
<div className="row">
|
||||||
<div className="align-items-center">
|
<InfraTable buildings={projectInfra} projectId={selectedProject} />
|
||||||
<div className="row ">
|
|
||||||
{isLoading && (<Loader/> )}
|
|
||||||
{( !isLoading && projectInfra?.length === 0 ) && ( <p>No Result Found</p> )}
|
|
||||||
{(!isLoading && projectInfra?.length > 0) && (<InfraTable buildings={projectInfra} projectId={selectedProject}/>)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
|
||||||
<div className="text-center">
|
|
||||||
<i className="fa-solid fa-triangle-exclamation fs-5"></i>
|
|
||||||
<p>Access Denied: You don't have permission to perform this action. !</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default InfraPlanning;
|
export default InfraPlanning;
|
||||||
|
|
||||||
|
|||||||
@ -105,7 +105,7 @@ const ListViewContact = ({ data, Pagination }) => {
|
|||||||
<div className="dataTables_wrapper no-footer mx-5 pb-2">
|
<div className="dataTables_wrapper no-footer mx-5 pb-2">
|
||||||
<table className="table dataTable text-nowrap">
|
<table className="table dataTable text-nowrap">
|
||||||
<thead>
|
<thead>
|
||||||
<tr style={{ borderBottom: "2px solid var(--bs-table-border-color)"}}>
|
<tr className="table_header_border">
|
||||||
{contactList?.map((col) => (
|
{contactList?.map((col) => (
|
||||||
<th key={col.key} className={col.align}>
|
<th key={col.key} className={col.align}>
|
||||||
{col.label}
|
{col.label}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import React from "react";
|
import React, { useMemo, useState } from "react";
|
||||||
import { FormProvider, useForm } from "react-hook-form";
|
import { FormProvider, useForm } from "react-hook-form";
|
||||||
import {
|
import {
|
||||||
defaultOrganizationValues,
|
defaultOrganizationValues,
|
||||||
@ -14,7 +14,11 @@ 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";
|
||||||
|
|
||||||
const ManageOrganization = () => {
|
const ManageOrganization = ({
|
||||||
|
projectOrganizations = ["ee"],
|
||||||
|
organizationId = null,
|
||||||
|
}) => {
|
||||||
|
const [step, setStep] = useState(1); // 1 = Service Provider, 2 = Org Details, 3 = Add New Org
|
||||||
const orgModal = useOrganizationModal();
|
const orgModal = useOrganizationModal();
|
||||||
const { data: services, isLoading } = useServices();
|
const { data: services, isLoading } = useServices();
|
||||||
|
|
||||||
@ -34,123 +38,319 @@ const ManageOrganization = () => {
|
|||||||
() => {
|
() => {
|
||||||
reset(defaultOrganizationValues);
|
reset(defaultOrganizationValues);
|
||||||
orgModal.onClose();
|
orgModal.onClose();
|
||||||
|
setStep(1); // reset to first step
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const onSubmit = (OrgPayload) => {
|
const onSubmit = (OrgPayload) => {
|
||||||
CreateOrganization(OrgPayload);
|
CreateOrganization(OrgPayload);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const RenderTitle = useMemo(() => {
|
||||||
|
if (organizationId) {
|
||||||
|
return "Update Organization";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step === 1) {
|
||||||
|
return projectOrganizations && projectOrganizations.length > 0
|
||||||
|
? "Add Organization"
|
||||||
|
: "Find Organization";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step === 2) {
|
||||||
|
return "Organization Details";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step === 3) {
|
||||||
|
return "Create Organization";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Manage Organization"; // fallback
|
||||||
|
}, [step, orgModal?.orgData, organizationId]);
|
||||||
|
|
||||||
const contentBody = (
|
const contentBody = (
|
||||||
<FormProvider {...method}>
|
<div>
|
||||||
<form className="form" onSubmit={handleSubmit(onSubmit)}>
|
{/* ---------- STEP 1: Service Provider- Form Own Tenant list ---------- */}
|
||||||
<div className="mb-1 text-start">
|
{step === 1 && (
|
||||||
<Label htmlFor="name" required>
|
<div className="d-block">
|
||||||
Organization Name
|
<div className="text-start mb-1">
|
||||||
</Label>
|
<Label className="text-secondary">Enter Service Provider ID</Label>
|
||||||
<input
|
<input
|
||||||
className="form-control form-control-sm"
|
type="text"
|
||||||
{...register("name")}
|
className="form-control form-control-sm w-auto"
|
||||||
/>
|
placeholder="SPR - ID"
|
||||||
{errors.name && (
|
aria-describedby="search-label"
|
||||||
<span className="danger-text">{errors.name.message}</span>
|
/>
|
||||||
)}
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mb-1 text-start">
|
<div className="py-2 text-start">
|
||||||
<Label htmlFor="contactPerson" required>
|
{orgModal.orgData && (<p className="text-secondary">Don't have Service provder id, Select Service Provider <span className="text-primary text-decoration-underline cursor-pointer" onClick={()=>orgModal.orgData ? setStep(2):setStep(3)}>Choose Provider</span></p>
|
||||||
Contact Person
|
)}
|
||||||
</Label>
|
</div>
|
||||||
<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">
|
{/* ======== org list ======*/}
|
||||||
<Label htmlFor="contactNumber" required>
|
{/* <div className="list-group mt-3">
|
||||||
Contact Number
|
<div
|
||||||
</Label>
|
className="list-group-item list-group-item-action cursor-pointer"
|
||||||
<input
|
onClick={() => setStep(2)}
|
||||||
className="form-control form-control-sm"
|
>
|
||||||
{...register("contactNumber")}
|
<i className="bx bx-building-house me-2"></i>
|
||||||
/>
|
Sample Organization 1
|
||||||
{errors.contactNumber && (
|
</div>
|
||||||
<span className="danger-text">{errors.contactNumber.message}</span>
|
</div> */}
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mb-1 text-start">
|
<div className="d-flex justify-content-center text-secondary mt-3">
|
||||||
<Label htmlFor="email" required>
|
<button
|
||||||
Email Address
|
type="button"
|
||||||
</Label>
|
className="btn btn-xs btn-secondary"
|
||||||
<input
|
onClick={() => setStep(4)}
|
||||||
className="form-control form-control-sm"
|
>
|
||||||
{...register("email")}
|
<i className="bx bx-plus-circle me-2"></i>
|
||||||
/>
|
Add New Organization
|
||||||
{errors.email && (
|
</button>
|
||||||
<span className="danger-text">{errors.email.message}</span>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="mb-1 text-start">
|
{/* ---------- STEP 1: Service Provider From Own Other Tenant ---------- */}
|
||||||
<SelectMultiple
|
{step === 2 && (
|
||||||
name="serviceIds"
|
<div className="d-block">
|
||||||
label="Services"
|
{/* Optional: dropdown if projectOrganizations exist */}
|
||||||
required={true}
|
{/* Optional: dropdown if projectOrganizations exist */}
|
||||||
valueKey="id"
|
<p className="text-secondary">Select Tags</p>
|
||||||
options={services?.data || []}
|
{/* ======== org list ======*/}
|
||||||
/>
|
<div className="list-group mt-3">
|
||||||
{errors.serviceIds && (
|
<div
|
||||||
<span className="danger-text">{errors.serviceIds.message}</span>
|
className="list-group-item list-group-item-action cursor-pointer"
|
||||||
)}
|
onClick={() => setStep(3)}
|
||||||
</div>
|
>
|
||||||
|
<i className="bx bx-building-house me-2"></i>
|
||||||
|
Sample Organization 1
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="mb-1 text-start">
|
<div className="d-flex justify-content-center text-secondary mt-3">
|
||||||
<Label htmlFor="address" required>
|
<button
|
||||||
Address
|
type="button"
|
||||||
</Label>
|
className="btn btn-xs btn-secondary"
|
||||||
<textarea
|
onClick={() => setStep(4)}
|
||||||
className="form-control form-control-sm"
|
>
|
||||||
{...register("address")}
|
<i className="bx bx-plus-circle me-2"></i>
|
||||||
rows={2}
|
Add New Organization
|
||||||
/>
|
</button>
|
||||||
{errors.address && (
|
</div>
|
||||||
<span className="danger-text">{errors.address.message}</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="d-flex justify-content-end gap-2 my-2">
|
{/* ---------- STEP 2: Existing Organization Details ---------- */}
|
||||||
<button
|
{step === 3 && (
|
||||||
type="button"
|
<div>
|
||||||
className="btn btn-sm btn-secondary"
|
<p className="text-muted small">
|
||||||
onClick={orgModal.onClose}
|
Show organization details here (from SPR list). User selects
|
||||||
disabled={isPending || isLoading}
|
services and clicks Add.
|
||||||
>
|
</p>
|
||||||
Cancel
|
|
||||||
</button>
|
<div className="mb-2">
|
||||||
<button
|
<Label>Services Offered</Label>
|
||||||
type="submit"
|
<ul className="list-group">
|
||||||
className="btn btn-sm btn-primary"
|
<li className="list-group-item">
|
||||||
disabled={isPending || isLoading}
|
<input type="checkbox" className="form-check-input me-2" />
|
||||||
>
|
Service 1
|
||||||
{isPending ? "Please Wait..." : "Submit"}
|
</li>
|
||||||
</button>
|
<li className="list-group-item">
|
||||||
|
<input type="checkbox" className="form-check-input me-2" />
|
||||||
|
Service 2
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="d-flex justify-content-between mt-3">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-sm btn-outline-secondary"
|
||||||
|
onClick={() => setStep(1)}
|
||||||
|
>
|
||||||
|
← Back
|
||||||
|
</button>
|
||||||
|
<button type="button" className="btn btn-sm btn-primary">
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
)}
|
||||||
</FormProvider>
|
|
||||||
|
{/* ---------- STEP 3: Add New Organization ---------- */}
|
||||||
|
{step === 4 && (
|
||||||
|
<FormProvider {...method}>
|
||||||
|
<form className="form" onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<div className="mb-1 text-start">
|
||||||
|
<Label htmlFor="name" required>
|
||||||
|
Organization Name
|
||||||
|
</Label>
|
||||||
|
<input
|
||||||
|
className="form-control form-control-sm"
|
||||||
|
{...register("name")}
|
||||||
|
/>
|
||||||
|
{errors.name && (
|
||||||
|
<span className="danger-text">{errors.name.message}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-1 text-start">
|
||||||
|
<Label htmlFor="contactPerson" required>
|
||||||
|
Contact Person
|
||||||
|
</Label>
|
||||||
|
<input
|
||||||
|
className="form-control form-control-sm"
|
||||||
|
{...register("contactPerson")}
|
||||||
|
/>
|
||||||
|
{errors.contactPerson && (
|
||||||
|
<span className="danger-text">
|
||||||
|
{errors.contactPerson.message}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-1 text-start">
|
||||||
|
<Label htmlFor="contactNumber" required>
|
||||||
|
Contact Number
|
||||||
|
</Label>
|
||||||
|
<input
|
||||||
|
className="form-control form-control-sm"
|
||||||
|
{...register("contactNumber")}
|
||||||
|
/>
|
||||||
|
{errors.contactNumber && (
|
||||||
|
<span className="danger-text">
|
||||||
|
{errors.contactNumber.message}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-1 text-start">
|
||||||
|
<Label htmlFor="email" required>
|
||||||
|
Email Address
|
||||||
|
</Label>
|
||||||
|
<input
|
||||||
|
className="form-control form-control-sm"
|
||||||
|
{...register("email")}
|
||||||
|
/>
|
||||||
|
{errors.email && (
|
||||||
|
<span className="danger-text">{errors.email.message}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-1 text-start">
|
||||||
|
<SelectMultiple
|
||||||
|
name="serviceIds"
|
||||||
|
label="Services"
|
||||||
|
required
|
||||||
|
valueKey="id"
|
||||||
|
options={services?.data || []}
|
||||||
|
/>
|
||||||
|
{errors.serviceIds && (
|
||||||
|
<span className="danger-text">{errors.serviceIds.message}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-1 text-start">
|
||||||
|
<Label htmlFor="address" required>
|
||||||
|
Address
|
||||||
|
</Label>
|
||||||
|
<textarea
|
||||||
|
className="form-control form-control-sm"
|
||||||
|
{...register("address")}
|
||||||
|
rows={2}
|
||||||
|
/>
|
||||||
|
{errors.address && (
|
||||||
|
<span className="danger-text">{errors.address.message}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="d-flex justify-content-between gap-2 my-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-sm btn-outline-secondary"
|
||||||
|
onClick={() => setStep(1)}
|
||||||
|
>
|
||||||
|
← Back
|
||||||
|
</button>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-sm btn-secondary me-2"
|
||||||
|
onClick={orgModal.onClose}
|
||||||
|
disabled={isPending || isLoading}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-sm btn-primary"
|
||||||
|
disabled={isPending || isLoading}
|
||||||
|
>
|
||||||
|
{isPending ? "Please Wait..." : "Submit"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</FormProvider>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={orgModal.isOpen}
|
isOpen={orgModal.isOpen}
|
||||||
onClose={orgModal.onClose}
|
onClose={orgModal.onClose}
|
||||||
title="Manage Organization"
|
title={RenderTitle}
|
||||||
body={contentBody}
|
body={contentBody}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ManageOrganization;
|
export default ManageOrganization;
|
||||||
|
|
||||||
|
// <div className="d-flex flex-column gap-2 border-0 bg-none">
|
||||||
|
// <div className="list-group-item list-group-item-action d-flex align-items-center cursor-pointer border-0">
|
||||||
|
// <div className="d-flex align-items-center justify-content-center me-3">
|
||||||
|
// <i className="bx bx-building-house bx-md text-primary"></i>
|
||||||
|
// </div>
|
||||||
|
|
||||||
|
// <div className="w-100">
|
||||||
|
// <div className="d-flex justify-content-between">
|
||||||
|
// <div className="user-info text-start">
|
||||||
|
// <h6 className="mb-1 fw-normal">Icing sweet gummies</h6>
|
||||||
|
// <small className="text-body-secondary">15 minutes</small>
|
||||||
|
// <div className="user-status">
|
||||||
|
// <small>In Meeting</small>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// <div className="add-btn">
|
||||||
|
// <button className="btn btn-primary btn-sm">Add</button>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
|
||||||
|
// {/* Icon item */}
|
||||||
|
// <div className="list-group-item list-group-item-action d-flex align-items-center cursor-pointer border-0">
|
||||||
|
// <div className="d-flex align-items-center justify-content-center me-3">
|
||||||
|
// <i className="bx bx-building-house bx-md text-primary"></i>
|
||||||
|
// </div>
|
||||||
|
|
||||||
|
// <div className="w-100">
|
||||||
|
// <div className="d-flex justify-content-between">
|
||||||
|
// <div className="user-info text-start">
|
||||||
|
// <h6 className="mb-1 fw-normal">Icing sweet gummies</h6>
|
||||||
|
// <small className="text-body-secondary">15 minutes</small>
|
||||||
|
// <div className="user-status">
|
||||||
|
// <small>In Meeting</small>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// <div className="add-btn">
|
||||||
|
// <button className="btn btn-primary btn-sm">Add</button>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
|||||||
149
src/components/Organization/OrganizationsList.jsx
Normal file
149
src/components/Organization/OrganizationsList.jsx
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useOrganizationsList } from "../../hooks/useOrganization";
|
||||||
|
import { ITEMS_PER_PAGE } from "../../utils/constants";
|
||||||
|
import Avatar from "../common/Avatar";
|
||||||
|
import { useDebounce } from "../../utils/appUtils";
|
||||||
|
|
||||||
|
const OrganizationsList = ({searchText}) => {
|
||||||
|
const searchString = useDebounce(searchText,500)
|
||||||
|
const {
|
||||||
|
data = [],
|
||||||
|
isLoading,
|
||||||
|
isFetching,
|
||||||
|
isError,
|
||||||
|
error,
|
||||||
|
} = useOrganizationsList(ITEMS_PER_PAGE, 1, true,null,searchString);
|
||||||
|
|
||||||
|
const organizationsColumns = [
|
||||||
|
{
|
||||||
|
key: "name",
|
||||||
|
label: "Organization Name",
|
||||||
|
getValue: (org) => (
|
||||||
|
<div className="d-flex gap-2 py-1 ">
|
||||||
|
<i class="bx bx-buildings"></i>
|
||||||
|
<span
|
||||||
|
className="text-truncate d-inline-block "
|
||||||
|
style={{ maxWidth: "150px" }}
|
||||||
|
>
|
||||||
|
{org?.name || "N/A"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
align: "text-start",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "contactPerson",
|
||||||
|
label: "Contact Person",
|
||||||
|
getValue: (org) => (
|
||||||
|
(
|
||||||
|
<div className="d-flex align-items-center ps-1">
|
||||||
|
<Avatar
|
||||||
|
size="xs"
|
||||||
|
classAvatar="m-0"
|
||||||
|
firstName={(org?.name || "").trim().split(" ")[0] || ""}
|
||||||
|
lastName={(org?.name || "").trim().split(" ")[1] || ""}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className="text-truncate d-inline-block "
|
||||||
|
style={{ maxWidth: "150px" }}
|
||||||
|
>
|
||||||
|
{org?.name || "N/A"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
),
|
||||||
|
align: "text-start",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "contactNumber",
|
||||||
|
label: "Phone Number",
|
||||||
|
getValue: (org) => org.contactNumber || "N/A",
|
||||||
|
align: "text-start",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "email",
|
||||||
|
label: "Email",
|
||||||
|
getValue: (org) => (
|
||||||
|
<span
|
||||||
|
className="text-truncate d-inline-block"
|
||||||
|
style={{ maxWidth: "200px" }}
|
||||||
|
>
|
||||||
|
{org?.email || "N/A"}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
align: "text-start",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "sprid",
|
||||||
|
label: "SPRID Id",
|
||||||
|
getValue: (org) => org.sprid || "N/A",
|
||||||
|
align: "text-center",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (isFetching && !isFetching) return <div>Loading...</div>;
|
||||||
|
if (isError) return <div>{error?.message || "Something went wrong"}</div>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="card px-0 px-sm-4">
|
||||||
|
<div className="card-datatable table-responsive" id="horizontal-example">
|
||||||
|
<div className="dataTables_wrapper no-footer px-2">
|
||||||
|
<table className="table border-top dataTable text-nowrap">
|
||||||
|
<thead>
|
||||||
|
<tr className="table_header_border">
|
||||||
|
{organizationsColumns.map((col) => (
|
||||||
|
<th
|
||||||
|
key={col.key}
|
||||||
|
className="sorting d-table-cell"
|
||||||
|
aria-sort="descending"
|
||||||
|
>
|
||||||
|
<div className={`${col.align}`}>{col.label}</div>
|
||||||
|
</th>
|
||||||
|
))}
|
||||||
|
<th className="sticky-action-column bg-white text-center">
|
||||||
|
Action
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{data.length > 0 ? (
|
||||||
|
data.map((org) => (
|
||||||
|
<tr key={org.id}>
|
||||||
|
{organizationsColumns.map((col) => (
|
||||||
|
<td
|
||||||
|
key={col.key}
|
||||||
|
className={`d-table-cell ${col.align ?? ""}`}
|
||||||
|
>
|
||||||
|
{col.customRender
|
||||||
|
? col.customRender(org)
|
||||||
|
: col.getValue(org)}
|
||||||
|
</td>
|
||||||
|
))}
|
||||||
|
<td className="sticky-action-column ">
|
||||||
|
<div className="d-flex justify-content-center gap-2 ">
|
||||||
|
<i className="bx bx-show text-primary cursor-pointer"></i>
|
||||||
|
<i className="bx bx-edit text-secondary cursor-pointer"></i>
|
||||||
|
<i className="bx bx-trash text-danger cursor-pointer"></i>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
colSpan={organizationsColumns.length + 1}
|
||||||
|
className="text-center"
|
||||||
|
>
|
||||||
|
<p className="fw-semibold">Not Found</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OrganizationsList;
|
||||||
@ -43,6 +43,7 @@ const ProjectNav = ({ onPillClick, activePill }) => {
|
|||||||
},
|
},
|
||||||
{ key: "documents", icon: "bx bx-folder-open", label: "Documents",hidden:!(isViewDocuments || isModifyDocument || isUploadDocument) },
|
{ key: "documents", icon: "bx bx-folder-open", label: "Documents",hidden:!(isViewDocuments || isModifyDocument || isUploadDocument) },
|
||||||
{ key: "setting", icon: "bx bxs-cog", label: "Setting",hidden:!isManageTeam },
|
{ key: "setting", icon: "bx bxs-cog", label: "Setting",hidden:!isManageTeam },
|
||||||
|
{ key: "organization", icon: "bx bx-buildings", label: "Organization"},
|
||||||
];
|
];
|
||||||
return (
|
return (
|
||||||
<div className="nav-align-top">
|
<div className="nav-align-top">
|
||||||
|
|||||||
30
src/components/Project/ProjectOrganizations.jsx
Normal file
30
src/components/Project/ProjectOrganizations.jsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useOrganizationModal } from "../../hooks/useOrganization";
|
||||||
|
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||||
|
|
||||||
|
const ProjectOrganizations = () => {
|
||||||
|
const orgModal = useOrganizationModal();
|
||||||
|
const selectedProject = useSelectedProject()
|
||||||
|
return (
|
||||||
|
<div className="card">
|
||||||
|
<div className="d-flex justify-content-end px-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="link-button btn btn-xs rounded-md link-button-sm m-1 btn-primary"
|
||||||
|
onClick={() => orgModal.onOpen(selectedProject)}
|
||||||
|
>
|
||||||
|
<i className="bx bx-plus-circle me-2"></i>
|
||||||
|
Add Organization
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="card-body">
|
||||||
|
<p className="text-secondary">
|
||||||
|
Not found Organization connected with current Project
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProjectOrganizations;
|
||||||
@ -28,6 +28,7 @@ const ProjectPermission = () => {
|
|||||||
handleSubmit,
|
handleSubmit,
|
||||||
reset,
|
reset,
|
||||||
control,
|
control,
|
||||||
|
setValue,
|
||||||
formState: { errors, isDirty },
|
formState: { errors, isDirty },
|
||||||
} = useForm({
|
} = useForm({
|
||||||
resolver: zodResolver(ProjectPermissionSchema),
|
resolver: zodResolver(ProjectPermissionSchema),
|
||||||
@ -45,18 +46,26 @@ const ProjectPermission = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selectedEmployee) return;
|
if (!selectedEmployee) return;
|
||||||
|
|
||||||
const enabledPerms =
|
const enabledPerms =
|
||||||
selectedEmpPermissions?.permissions
|
selectedEmpPermissions?.permissions
|
||||||
?.filter((perm) => perm.isEnabled)
|
?.filter((perm) => perm.isEnabled)
|
||||||
?.map((perm) => perm.id) || [];
|
?.map((perm) => perm.id) || [];
|
||||||
|
|
||||||
reset((prev) => ({
|
setValue("selectedPermissions", enabledPerms, { shouldValidate: true });
|
||||||
...prev,
|
}, [selectedEmpPermissions, setValue, selectedEmployee]);
|
||||||
selectedPermissions: enabledPerms,
|
|
||||||
}));
|
const selectedPermissions = watch("selectedPermissions") || [];
|
||||||
}, [selectedEmpPermissions, reset, selectedEmployee]);
|
|
||||||
|
const existingEnabledIds =
|
||||||
|
selectedEmpPermissions?.permissions
|
||||||
|
?.filter((p) => p.isEnabled)
|
||||||
|
?.map((p) => p.id) || [];
|
||||||
|
|
||||||
|
const hasChanges =
|
||||||
|
selectedPermissions.length !== existingEnabledIds.length ||
|
||||||
|
selectedPermissions.some((id) => !existingEnabledIds.includes(id));
|
||||||
|
|
||||||
const { mutate: updatePermission, isPending } =
|
const { mutate: updatePermission, isPending } =
|
||||||
useUpdateProjectLevelEmployeePermission();
|
useUpdateProjectLevelEmployeePermission();
|
||||||
@ -145,7 +154,7 @@ const ProjectPermission = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-3 text-end">
|
<div className="mt-3 text-end">
|
||||||
{isDirty && (
|
{hasChanges && (
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="btn btn-sm btn-primary"
|
className="btn btn-sm btn-primary"
|
||||||
|
|||||||
@ -1,16 +1,26 @@
|
|||||||
|
|
||||||
import { useSelectedProject } from "../slices/apiDataManager";
|
import { useSelectedProject } from "../slices/apiDataManager";
|
||||||
import { useAllProjectLevelPermissions, useProfile } from "./useProfile";
|
import { useAllProjectLevelPermissions, useProfile } from "./useProfile";
|
||||||
|
|
||||||
export const useHasUserPermission = (permission) => {
|
export const useHasUserPermission = (permission) => {
|
||||||
const selectedProject = useSelectedProject();
|
const selectedProject = useSelectedProject();
|
||||||
const { profile } = useProfile();
|
const { profile } = useProfile();
|
||||||
const { data: projectPermissions = [], isLoading, isError } = useAllProjectLevelPermissions(selectedProject);
|
const {
|
||||||
|
data: projectPermissions = [],
|
||||||
|
isLoading,
|
||||||
|
isError,
|
||||||
|
} = useAllProjectLevelPermissions(selectedProject);
|
||||||
|
|
||||||
if (isLoading || !permission) return false;
|
if (isLoading || !permission) return false;
|
||||||
|
|
||||||
const globalPerms = profile?.featurePermissions ?? [];
|
const globalPerms = profile?.featurePermissions ?? [];
|
||||||
const projectPerms = projectPermissions ?? [];
|
const projectPerms = projectPermissions ?? [];
|
||||||
|
if (selectedProject) {
|
||||||
return globalPerms.includes(permission) || projectPerms.includes(permission);
|
if (projectPerms.length === 0) {
|
||||||
|
return projectPerms.includes(permission);
|
||||||
|
} else {
|
||||||
|
return projectPerms.includes(permission);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return globalPerms.includes(permission);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,29 +4,63 @@ import {
|
|||||||
openOrgModal,
|
openOrgModal,
|
||||||
closeOrgModal,
|
closeOrgModal,
|
||||||
} from "../slices/localVariablesSlice";
|
} from "../slices/localVariablesSlice";
|
||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import OrganizationRepository from "../repositories/OrganizationRespository";
|
import OrganizationRepository from "../repositories/OrganizationRespository";
|
||||||
import showToast from "../services/toastService";
|
import showToast from "../services/toastService";
|
||||||
|
|
||||||
export const useOrganizationModal = () => {
|
export const useOrganizationModal = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const isOpen = useSelector(
|
const { isOpen, orgData } = useSelector(
|
||||||
(state) => state.localVariables.OrganizationModal.isOpen
|
(state) => state.localVariables.OrganizationModal
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isOpen,
|
isOpen,
|
||||||
onOpen: () => dispatch(openOrgModal()),
|
orgData,
|
||||||
|
onOpen: (dat) =>
|
||||||
|
dispatch(openOrgModal({ isOpen: true, orgData: dat || null })),
|
||||||
onClose: () => dispatch(closeOrgModal()),
|
onClose: () => dispatch(closeOrgModal()),
|
||||||
Togggle: () => dispatch(toggleOrgModal(isOpen)),
|
onToggle: () => dispatch(toggleOrgModal()),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useOrganizationsList = (
|
||||||
|
pageSize,
|
||||||
|
pageNumber,
|
||||||
|
active,
|
||||||
|
sprid,
|
||||||
|
searchString = ""
|
||||||
|
) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: [
|
||||||
|
"organizationList",
|
||||||
|
pageSize,
|
||||||
|
pageNumber,
|
||||||
|
active,
|
||||||
|
sprid,
|
||||||
|
searchString,
|
||||||
|
],
|
||||||
|
queryFn: async () => {
|
||||||
|
const resp = await OrganizationRepository.getOrganizationList(
|
||||||
|
pageSize,
|
||||||
|
pageNumber,
|
||||||
|
active,
|
||||||
|
sprid,
|
||||||
|
searchString
|
||||||
|
);
|
||||||
|
return resp.data;
|
||||||
|
},
|
||||||
|
keepPreviousData: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const useCreateOrganization = (onSuccessCallback) => {
|
export const useCreateOrganization = (onSuccessCallback) => {
|
||||||
|
const useClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async (OrgPayload) =>
|
mutationFn: async (OrgPayload) =>
|
||||||
await OrganizationRepository.createOrganization(OrgPayload),
|
await OrganizationRepository.createOrganization(OrgPayload),
|
||||||
onSuccess: (_, variables) => {
|
onSuccess: (_, variables) => {
|
||||||
|
useClient.invalidateQueries({ queryKey: ["organizationList"] });
|
||||||
showToast("Organization created successfully", "success");
|
showToast("Organization created successfully", "success");
|
||||||
if (onSuccessCallback) onSuccessCallback();
|
if (onSuccessCallback) onSuccessCallback();
|
||||||
},
|
},
|
||||||
|
|||||||
26
src/hooks/useProjectAccess.js
Normal file
26
src/hooks/useProjectAccess.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useHasUserPermission } from "./useHasUserPermission";
|
||||||
|
import { useAllProjectLevelPermissions } from "./useProfile";
|
||||||
|
import { VIEW_PROJECTS } from "../utils/constants";
|
||||||
|
import showToast from "../services/toastService";
|
||||||
|
|
||||||
|
export const useProjectAccess = (projectId) => {
|
||||||
|
const { data: projectPermissions, isLoading, isFetched } =
|
||||||
|
useAllProjectLevelPermissions(projectId);
|
||||||
|
|
||||||
|
const canView = useHasUserPermission(VIEW_PROJECTS);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (projectId && isFetched && !isLoading && !canView) {
|
||||||
|
showToast("You don't have permission to view project details", "warning");
|
||||||
|
navigate("/projects");
|
||||||
|
}
|
||||||
|
}, [projectId, isFetched, isLoading, canView, navigate]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
canView,
|
||||||
|
loading: isLoading || !isFetched,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -177,6 +177,7 @@ export const useProjectInfra = (projectId) => {
|
|||||||
data: projectInfra,
|
data: projectInfra,
|
||||||
isLoading,
|
isLoading,
|
||||||
error,
|
error,
|
||||||
|
isFetched
|
||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: ["ProjectInfra", projectId],
|
queryKey: ["ProjectInfra", projectId],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
@ -190,7 +191,7 @@ export const useProjectInfra = (projectId) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return { projectInfra, isLoading, error };
|
return { projectInfra, isLoading, error,isFetched };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useProjectTasks = (workAreaId, IsExpandedArea = false) => {
|
export const useProjectTasks = (workAreaId, IsExpandedArea = false) => {
|
||||||
|
|||||||
@ -69,7 +69,7 @@ const ContactsPage = ({ projectId, searchText, onExport }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (isError) return <div>{error.message}</div>;
|
if (isError) return <div>{error.message}</div>;
|
||||||
if (isLoading) return gridView ? <CardViewContactSkeleton /> : <ListViewContactSkeleton />;
|
// if (isLoading) return gridView ? <CardViewContactSkeleton /> : <ListViewContactSkeleton />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="row mt-5">
|
<div className="row mt-5">
|
||||||
@ -94,7 +94,7 @@ const ContactsPage = ({ projectId, searchText, onExport }) => {
|
|||||||
) : (
|
) : (
|
||||||
<div className="col-12">
|
<div className="col-12">
|
||||||
<ListViewContact
|
<ListViewContact
|
||||||
data={data.data}
|
data={data?.data}
|
||||||
Pagination={
|
Pagination={
|
||||||
<Pagination
|
<Pagination
|
||||||
currentPage={currentPage}
|
currentPage={currentPage}
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import ContactProfile from "../../components/Directory/ContactProfile";
|
|||||||
import GlobalModel from "../../components/common/GlobalModel";
|
import GlobalModel from "../../components/common/GlobalModel";
|
||||||
import { exportToCSV } from "../../utils/exportUtils";
|
import { exportToCSV } from "../../utils/exportUtils";
|
||||||
import ConfirmModal from "../../components/common/ConfirmModal";
|
import ConfirmModal from "../../components/common/ConfirmModal";
|
||||||
|
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||||
|
|
||||||
const NotesPage = lazy(() => import("./NotesPage"));
|
const NotesPage = lazy(() => import("./NotesPage"));
|
||||||
const ContactsPage = lazy(() => import("./ContactsPage"));
|
const ContactsPage = lazy(() => import("./ContactsPage"));
|
||||||
|
|||||||
@ -299,7 +299,7 @@ nav.layout-navbar.navbar-active::after {
|
|||||||
color: #d3d4dc;
|
color: #d3d4dc;
|
||||||
}
|
}
|
||||||
.landing-footer .footer-bottom {
|
.landing-footer .footer-bottom {
|
||||||
background-color: #282c3e;
|
background-color: #f44336;
|
||||||
}
|
}
|
||||||
.landing-footer .footer-link {
|
.landing-footer .footer-link {
|
||||||
transition: all 0.2s ease-in-out;
|
transition: all 0.2s ease-in-out;
|
||||||
@ -312,6 +312,7 @@ nav.layout-navbar.navbar-active::after {
|
|||||||
padding-bottom: 1.3rem;
|
padding-bottom: 1.3rem;
|
||||||
border-top-left-radius: 1.75rem;
|
border-top-left-radius: 1.75rem;
|
||||||
border-top-right-radius: 1.75rem;
|
border-top-right-radius: 1.75rem;
|
||||||
|
background-color: #f44336;
|
||||||
}
|
}
|
||||||
@media (max-width: 767.98px) {
|
@media (max-width: 767.98px) {
|
||||||
.landing-footer .footer-top {
|
.landing-footer .footer-top {
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import "swiper/css";
|
|||||||
import "swiper/css/navigation";
|
import "swiper/css/navigation";
|
||||||
import SwaperSlideContent from "./SwaperSlideContent";
|
import SwaperSlideContent from "./SwaperSlideContent";
|
||||||
import SwaperBlogContent from "./SwaperBlogContent";
|
import SwaperBlogContent from "./SwaperBlogContent";
|
||||||
|
import SubscriptionPlans from "./SubscriptionPlans";
|
||||||
|
|
||||||
const swiperConfig = {
|
const swiperConfig = {
|
||||||
spaceBetween: 30,
|
spaceBetween: 30,
|
||||||
@ -496,341 +497,8 @@ const LandingPage = () => {
|
|||||||
No matter which plan you choose, you’ll get access to powerful
|
No matter which plan you choose, you’ll get access to powerful
|
||||||
features. <strong>Choose the best plan to fit your needs.</strong>
|
features. <strong>Choose the best plan to fit your needs.</strong>
|
||||||
</p>
|
</p>
|
||||||
<div className="text-center mb-8">
|
{/* <SubscriptionPlans/> */}
|
||||||
<div className="position-relative d-inline-block pt-3 pt-md-0">
|
<SubscriptionPlans />
|
||||||
<div class="btn-group" role="group" aria-label="Basic example">
|
|
||||||
<button type="button" class="btn btn-outline-secondary">
|
|
||||||
Basic
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-primary">
|
|
||||||
Team
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-outline-secondary">
|
|
||||||
Enterprise
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{/* <label className="switch switch-sm switch-primary me-0">
|
|
||||||
<span className="switch-label fs-6 text-body me-3">
|
|
||||||
Pay Monthly
|
|
||||||
</span>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
className="switch-input price-duration-toggler"
|
|
||||||
checked
|
|
||||||
/>
|
|
||||||
<span className="switch-toggle-slider">
|
|
||||||
<span className="switch-on"></span>
|
|
||||||
<span className="switch-off"></span>
|
|
||||||
</span>
|
|
||||||
<span className="switch-label fs-6 text-body ms-3">
|
|
||||||
Pay Annual
|
|
||||||
</span>
|
|
||||||
</label> */}
|
|
||||||
{/* <div className="pricing-plans-item position-absolute d-flex">
|
|
||||||
<img
|
|
||||||
src="./../../public/img/icons/pricing-plans-arrow.png"
|
|
||||||
alt="pricing plans arrow"
|
|
||||||
className="scaleX-n1-rtl"
|
|
||||||
/>
|
|
||||||
<span className="fw-medium mt-2 ms-1"> Save 25%</span>
|
|
||||||
</div> */}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="row g-6 pt-0">
|
|
||||||
{/* Basic Plan: Start */}
|
|
||||||
<div className="col-xl-4 col-lg-6 col-md-4">
|
|
||||||
<div className="card">
|
|
||||||
<div className="card-header">
|
|
||||||
<div className="text-center">
|
|
||||||
<img
|
|
||||||
src="/img/icons/paper-airplane.png"
|
|
||||||
alt="paper airplane icon"
|
|
||||||
className="mb-8 pb-2"
|
|
||||||
/>
|
|
||||||
<h4 className="mb-0">Basic</h4>
|
|
||||||
<div className="d-flex align-items-center justify-content-center">
|
|
||||||
<span className="price-monthly h2 text-primary fw-extrabold mb-0">
|
|
||||||
$19
|
|
||||||
</span>
|
|
||||||
<span className="price-yearly h2 text-primary fw-extrabold mb-0 d-none">
|
|
||||||
$14
|
|
||||||
</span>
|
|
||||||
<sub className="h6 text-muted mb-n1 ms-1">/mo</sub>
|
|
||||||
</div>
|
|
||||||
<div className="position-relative pt-2">
|
|
||||||
<div className="price-yearly text-muted price-yearly-toggle d-none">
|
|
||||||
$ 168 / year
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="card-body">
|
|
||||||
<ul className="text start list-unstyled pricing-list">
|
|
||||||
<li>
|
|
||||||
<h6 className="d-flex align-items-center mb-3">
|
|
||||||
<span className="badge badge-center rounded-pill bg-label-primary p-0 me-3">
|
|
||||||
<i className="bx bx-check bx-12px"></i>
|
|
||||||
</span>
|
|
||||||
Timeline
|
|
||||||
</h6>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h6 className="d-flex align-items-center mb-3">
|
|
||||||
<span className="badge badge-center rounded-pill bg-label-primary p-0 me-3">
|
|
||||||
<i className="bx bx-check bx-12px"></i>
|
|
||||||
</span>
|
|
||||||
Basic search
|
|
||||||
</h6>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h6 className="d-flex align-items-center mb-3">
|
|
||||||
<span className="badge badge-center rounded-pill bg-label-primary p-0 me-3">
|
|
||||||
<i className="bx bx-check bx-12px"></i>
|
|
||||||
</span>
|
|
||||||
Live chat widget
|
|
||||||
</h6>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h6 className="d-flex align-items-center mb-3">
|
|
||||||
<span className="badge badge-center rounded-pill bg-label-primary p-0 me-3">
|
|
||||||
<i className="bx bx-check bx-12px"></i>
|
|
||||||
</span>
|
|
||||||
Email marketing
|
|
||||||
</h6>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h6 className="d-flex align-items-center mb-3">
|
|
||||||
<span className="badge badge-center rounded-pill bg-label-primary p-0 me-3">
|
|
||||||
<i className="bx bx-check bx-12px"></i>
|
|
||||||
</span>
|
|
||||||
Custom Forms
|
|
||||||
</h6>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h6 className="d-flex align-items-center mb-3">
|
|
||||||
<span className="badge badge-center rounded-pill bg-label-primary p-0 me-3">
|
|
||||||
<i className="bx bx-check bx-12px"></i>
|
|
||||||
</span>
|
|
||||||
Traffic analytics
|
|
||||||
</h6>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h6 className="d-flex align-items-center mb-3">
|
|
||||||
<span className="badge badge-center rounded-pill bg-label-primary p-0 me-3">
|
|
||||||
<i className="bx bx-check bx-12px"></i>
|
|
||||||
</span>
|
|
||||||
Basic Support
|
|
||||||
</h6>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div className="d-grid mt-8">
|
|
||||||
<a
|
|
||||||
href="#landingPricing"
|
|
||||||
className="btn btn-label-primary"
|
|
||||||
>
|
|
||||||
Get Started
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/* Basic Plan: End */}
|
|
||||||
|
|
||||||
{/* Favourite Plan: Start */}
|
|
||||||
<div className="col-xl-4 col-lg-6 col-md-4">
|
|
||||||
<div className="card border border-primary shadow-xl">
|
|
||||||
<div className="card-header">
|
|
||||||
<div className="text-center">
|
|
||||||
<img
|
|
||||||
src="/img/icons/plane.png"
|
|
||||||
alt="plane icon"
|
|
||||||
className="mb-8 pb-2"
|
|
||||||
/>
|
|
||||||
<h4 className="mb-0">Team</h4>
|
|
||||||
<div className="d-flex align-items-center justify-content-center">
|
|
||||||
<span className="price-monthly h2 text-primary fw-extrabold mb-0">
|
|
||||||
$29
|
|
||||||
</span>
|
|
||||||
<span className="price-yearly h2 text-primary fw-extrabold mb-0 d-none">
|
|
||||||
$22
|
|
||||||
</span>
|
|
||||||
<sub className="h6 text-muted mb-n1 ms-1">/mo</sub>
|
|
||||||
</div>
|
|
||||||
<div className="position-relative pt-2">
|
|
||||||
<div className="price-yearly text-muted price-yearly-toggle d-none">
|
|
||||||
$ 264 / year
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="card-body">
|
|
||||||
<ul className="text start list-unstyled pricing-list">
|
|
||||||
<li>
|
|
||||||
<h6 className="d-flex align-items-center mb-3">
|
|
||||||
<span className="badge badge-center rounded-pill bg-primary p-0 me-3">
|
|
||||||
<i className="bx bx-check bx-12px"></i>
|
|
||||||
</span>
|
|
||||||
Everything in basic
|
|
||||||
</h6>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h6 className="d-flex align-items-center mb-3">
|
|
||||||
<span className="badge badge-center rounded-pill bg-primary p-0 me-3">
|
|
||||||
<i className="bx bx-check bx-12px"></i>
|
|
||||||
</span>
|
|
||||||
Timeline with database
|
|
||||||
</h6>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h6 className="d-flex align-items-center mb-3">
|
|
||||||
<span className="badge badge-center rounded-pill bg-primary p-0 me-3">
|
|
||||||
<i className="bx bx-check bx-12px"></i>
|
|
||||||
</span>
|
|
||||||
Advanced search
|
|
||||||
</h6>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h6 className="d-flex align-items-center mb-3">
|
|
||||||
<span className="badge badge-center rounded-pill bg-primary p-0 me-3">
|
|
||||||
<i className="bx bx-check bx-12px"></i>
|
|
||||||
</span>
|
|
||||||
Marketing automation
|
|
||||||
</h6>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h6 className="d-flex align-items-center mb-3">
|
|
||||||
<span className="badge badge-center rounded-pill bg-primary p-0 me-3">
|
|
||||||
<i className="bx bx-check bx-12px"></i>
|
|
||||||
</span>
|
|
||||||
Advanced chatbot
|
|
||||||
</h6>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h6 className="d-flex align-items-center mb-3">
|
|
||||||
<span className="badge badge-center rounded-pill bg-primary p-0 me-3">
|
|
||||||
<i className="bx bx-check bx-12px"></i>
|
|
||||||
</span>
|
|
||||||
Campaign management
|
|
||||||
</h6>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h6 className="d-flex align-items-center mb-3">
|
|
||||||
<span className="badge badge-center rounded-pill bg-primary p-0 me-3">
|
|
||||||
<i className="bx bx-check bx-12px"></i>
|
|
||||||
</span>
|
|
||||||
Collaboration tools
|
|
||||||
</h6>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div className="d-grid mt-8">
|
|
||||||
<a href="payment-page.html" className="btn btn-primary">
|
|
||||||
Get Started
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/* Favourite Plan: End */}
|
|
||||||
|
|
||||||
{/* Standard Plan: Start */}
|
|
||||||
<div className="col-xl-4 col-lg-6 col-md-4">
|
|
||||||
<div className="card">
|
|
||||||
<div className="card-header">
|
|
||||||
<div className="text-center">
|
|
||||||
<img
|
|
||||||
src="/img/icons/shuttle-rocket.png"
|
|
||||||
alt="shuttle rocket icon"
|
|
||||||
className="mb-8 pb-2"
|
|
||||||
/>
|
|
||||||
<h4 className="mb-0">Enterprise</h4>
|
|
||||||
<div className="d-flex align-items-center justify-content-center">
|
|
||||||
<span className="price-monthly h2 text-primary fw-extrabold mb-0">
|
|
||||||
$49
|
|
||||||
</span>
|
|
||||||
<span className="price-yearly h2 text-primary fw-extrabold mb-0 d-none">
|
|
||||||
$37
|
|
||||||
</span>
|
|
||||||
<sub className="h6 text-muted mb-n1 ms-1">/mo</sub>
|
|
||||||
</div>
|
|
||||||
<div className="position-relative pt-2">
|
|
||||||
<div className="price-yearly text-muted price-yearly-toggle d-none">
|
|
||||||
$ 444 / year
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="card-body">
|
|
||||||
<ul className="text start list-unstyled pricing-list">
|
|
||||||
<li>
|
|
||||||
<h6 className="d-flex align-items-center mb-3">
|
|
||||||
<span className="badge badge-center rounded-pill bg-label-primary p-0 me-3">
|
|
||||||
<i className="bx bx-check bx-12px"></i>
|
|
||||||
</span>
|
|
||||||
Everything in premium
|
|
||||||
</h6>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h6 className="d-flex align-items-center mb-3">
|
|
||||||
<span className="badge badge-center rounded-pill bg-label-primary p-0 me-3">
|
|
||||||
<i className="bx bx-check bx-12px"></i>
|
|
||||||
</span>
|
|
||||||
Timeline with database
|
|
||||||
</h6>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h6 className="d-flex align-items-center mb-3">
|
|
||||||
<span className="badge badge-center rounded-pill bg-label-primary p-0 me-3">
|
|
||||||
<i className="bx bx-check bx-12px"></i>
|
|
||||||
</span>
|
|
||||||
Fuzzy search
|
|
||||||
</h6>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h6 className="d-flex align-items-center mb-3">
|
|
||||||
<span className="badge badge-center rounded-pill bg-label-primary p-0 me-3">
|
|
||||||
<i className="bx bx-check bx-12px"></i>
|
|
||||||
</span>
|
|
||||||
A/B testing sanbox
|
|
||||||
</h6>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h6 className="d-flex align-items-center mb-3">
|
|
||||||
<span className="badge badge-center rounded-pill bg-label-primary p-0 me-3">
|
|
||||||
<i className="bx bx-check bx-12px"></i>
|
|
||||||
</span>
|
|
||||||
Custom permissions
|
|
||||||
</h6>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h6 className="d-flex align-items-center mb-3">
|
|
||||||
<span className="badge badge-center rounded-pill bg-label-primary p-0 me-3">
|
|
||||||
<i className="bx bx-check bx-12px"></i>
|
|
||||||
</span>
|
|
||||||
Social media automation
|
|
||||||
</h6>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h6 className="d-flex align-items-center mb-3">
|
|
||||||
<span className="badge badge-center rounded-pill bg-label-primary p-0 me-3">
|
|
||||||
<i className="bx bx-check bx-12px"></i>
|
|
||||||
</span>
|
|
||||||
Sales automation tools
|
|
||||||
</h6>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div className="d-grid mt-8">
|
|
||||||
<a
|
|
||||||
href="payment-page.html"
|
|
||||||
className="btn btn-label-primary"
|
|
||||||
>
|
|
||||||
Get Started
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/* Standard Plan: End */}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{/* Pricing plans: End */}
|
{/* Pricing plans: End */}
|
||||||
@ -923,14 +591,14 @@ const LandingPage = () => {
|
|||||||
<span className="badge bg-label-primary heading">FAQ</span>
|
<span className="badge bg-label-primary heading">FAQ</span>
|
||||||
</div>
|
</div>
|
||||||
<h4 className="text-center mb-1">
|
<h4 className="text-center mb-1">
|
||||||
Frequently asked
|
Frequently Asked
|
||||||
<span className="position-relative fw-extrabold z-1">
|
<span className="position-relative fw-extrabold z-1 ms-2">
|
||||||
questions
|
Questions
|
||||||
<img
|
{/* <img
|
||||||
src="/img/icons/section-title-icon.png"
|
src="/img/icons/section-title-icon.png"
|
||||||
alt="laptop charging"
|
alt="laptop charging"
|
||||||
className="section-title-img position-absolute object-fit-contain bottom-0 z-n1"
|
className="section-title-img position-absolute object-fit-contain bottom-0 z-n1"
|
||||||
/>
|
/> */}
|
||||||
</span>
|
</span>
|
||||||
</h4>
|
</h4>
|
||||||
<p className="text-center mb-12 pb-md-4">
|
<p className="text-center mb-12 pb-md-4">
|
||||||
@ -968,12 +636,12 @@ const LandingPage = () => {
|
|||||||
className="accordion-collapse collapse"
|
className="accordion-collapse collapse"
|
||||||
data-bs-parent="#accordionExample"
|
data-bs-parent="#accordionExample"
|
||||||
>
|
>
|
||||||
<div className="accordion-body">
|
<div className="accordion-body text-start">
|
||||||
Lemon drops chocolate cake gummies carrot cake chupa
|
A smart Project Management System designed to bring
|
||||||
chups muffin topping. Sesame snaps icing marzipan gummi
|
teams, tasks, and timelines together in one place. With
|
||||||
bears macaroon dragée danish caramels powder. Bear claw
|
AI-driven insights, role-based access, and seamless
|
||||||
dragée pastry topping soufflé. Wafer gummi bears
|
reporting, it empowers organizations to deliver projects
|
||||||
marshmallow pastry pie.
|
faster and smarter.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -996,12 +664,13 @@ const LandingPage = () => {
|
|||||||
aria-labelledby="headingTwo"
|
aria-labelledby="headingTwo"
|
||||||
data-bs-parent="#accordionExample"
|
data-bs-parent="#accordionExample"
|
||||||
>
|
>
|
||||||
<div className="accordion-body">
|
<div className="accordion-body text-start">
|
||||||
Dessert ice cream donut oat cake jelly-o pie sugar plum
|
Yes, you have full flexibility to manage your
|
||||||
cheesecake. Bear claw dragée oat cake dragée ice cream
|
subscription. You can upgrade to a higher plan to unlock
|
||||||
halvah tootsie roll. Danish cake oat cake pie macaroon
|
more features, downgrade to a smaller plan if your needs
|
||||||
tart donut gummies. Jelly beans candy canes carrot cake.
|
change, or cancel your subscription anytime. Plan
|
||||||
Fruitcake chocolate chupa chups.
|
changes take effect instantly, and billing adjustments
|
||||||
|
are applied on a pro-rated basis.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1024,17 +693,16 @@ const LandingPage = () => {
|
|||||||
aria-labelledby="headingThree"
|
aria-labelledby="headingThree"
|
||||||
data-bs-parent="#accordionExample"
|
data-bs-parent="#accordionExample"
|
||||||
>
|
>
|
||||||
<div className="accordion-body">
|
<div className="accordion-body text-start">
|
||||||
Regular license can be used for end products that do not
|
Security is at the core of Marco PMS. We use
|
||||||
charge users for access or service(access is free and
|
industry-standard encryption (SSL/TLS) to protect data
|
||||||
there will be no monthly subscription fee). Single
|
in transit and advanced encryption to safeguard data at
|
||||||
regular license can be used for single end product and
|
rest. Role-based access controls ensure that only
|
||||||
end product can be used by you or your client. If you
|
authorized users can access sensitive information. Our
|
||||||
want to sell end product to multiple clients then you
|
system is hosted on secure, cloud-ready infrastructure
|
||||||
will need to purchase separate license for each client.
|
with regular backups, monitoring, and compliance with
|
||||||
The same rule applies if you want to use the same end
|
best practices to keep your data safe and available at
|
||||||
product on multiple domains(unique setup). For more info
|
all times.
|
||||||
on regular license you can check official description.
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1057,12 +725,12 @@ const LandingPage = () => {
|
|||||||
aria-labelledby="headingFour"
|
aria-labelledby="headingFour"
|
||||||
data-bs-parent="#accordionExample"
|
data-bs-parent="#accordionExample"
|
||||||
>
|
>
|
||||||
<div className="accordion-body">
|
<div className="accordion-body text-start">
|
||||||
Lorem ipsum dolor sit amet consectetur adipisicing elit.
|
You can reach our support team anytime through the
|
||||||
Nobis et aliquid quaerat possimus maxime! Mollitia
|
in-app help center, email, or live chat. We also provide
|
||||||
reprehenderit neque repellat deleniti delectus
|
a detailed knowledge base and FAQs to guide you through
|
||||||
architecto dolorum maxime, blanditiis earum ea, incidunt
|
common queries. For personalized assistance, our support
|
||||||
quam possimus cumque.
|
specialists are always ready to help you.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1085,15 +753,47 @@ const LandingPage = () => {
|
|||||||
aria-labelledby="headingFive"
|
aria-labelledby="headingFive"
|
||||||
data-bs-parent="#accordionExample"
|
data-bs-parent="#accordionExample"
|
||||||
>
|
>
|
||||||
<div className="accordion-body">
|
<div className="accordion-body text-start">
|
||||||
Lorem ipsum dolor sit amet consectetur, adipisicing
|
Marco PMS operate under a proprietary license combined
|
||||||
elit. Sequi molestias exercitationem ab cum nemo facere
|
with a subscription model. This means customers don’t
|
||||||
voluptates veritatis quia, eveniet veniam at et
|
own the software but are granted the right to access and
|
||||||
repudiandae mollitia ipsam quasi labore enim architecto
|
use it through the cloud under our Terms of Service.
|
||||||
non!
|
Depending on the plan, licensing may be based on users,
|
||||||
|
features, or usage, and you can upgrade, downgrade, or
|
||||||
|
cancel at any time. non!
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="card accordion-item">
|
||||||
|
<h2 className="accordion-header" id="headingSix">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="accordion-button collapsed"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#accordionSix"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-controls="accordionSix"
|
||||||
|
>
|
||||||
|
Can I customize Marco PMS for my business needs?
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div
|
||||||
|
id="accordionSix"
|
||||||
|
className="accordion-collapse collapse"
|
||||||
|
aria-labelledby="headingSix"
|
||||||
|
data-bs-parent="#accordionExample"
|
||||||
|
>
|
||||||
|
<div className="accordion-body text-start">
|
||||||
|
Yes, Marco PMS is designed to be flexible and adaptable.
|
||||||
|
You can customize workflows, user roles, permissions,
|
||||||
|
and reporting to match your organization’s unique
|
||||||
|
processes. Depending on your plan, we also support
|
||||||
|
advanced customization such as integrating with
|
||||||
|
third-party tools, adding custom fields, and tailoring
|
||||||
|
modules to fit your business requirements.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>{" "}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1116,19 +816,26 @@ const LandingPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="row align-items-center gy-12 mb-12">
|
<div className="row align-items-center gy-12 mb-12">
|
||||||
|
<div className="col-lg-6 pt-lg-12 text-center text-lg-start">
|
||||||
|
<img
|
||||||
|
style={{ width: "80%" }}
|
||||||
|
src="/img/images/contact-customer-service.png"
|
||||||
|
alt="hero elements"
|
||||||
|
></img>
|
||||||
|
</div>
|
||||||
<div className="col-lg-6 text-start text-sm-center text-lg-start">
|
<div className="col-lg-6 text-start text-sm-center text-lg-start">
|
||||||
<div className="mt-5">
|
<div className="mt-5">
|
||||||
{" "}
|
{" "}
|
||||||
<h4 className="text-start mb-1">
|
<h4 className="text-start mb-1">
|
||||||
<span className="position-relative fw-extrabold z-1">
|
<span className="position-relative fw-extrabold z-1">
|
||||||
Let's work
|
Let's Work
|
||||||
<img
|
{/* <img
|
||||||
src="/img/icons/section-title-icon.png"
|
src="/img/icons/section-title-icon.png"
|
||||||
alt="laptop charging"
|
alt="laptop charging"
|
||||||
className="section-title-img position-absolute object-fit-contain bottom-0 z-n1"
|
className="section-title-img position-absolute object-fit-contain bottom-0 z-n1"
|
||||||
/>
|
/> */}
|
||||||
</span>
|
</span>
|
||||||
together
|
Together
|
||||||
</h4>
|
</h4>
|
||||||
<p className="text-start pb-md-4">
|
<p className="text-start pb-md-4">
|
||||||
Any question or remark? just write us a message
|
Any question or remark? just write us a message
|
||||||
@ -1173,15 +880,15 @@ const LandingPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-10">
|
<div className="mt-10">
|
||||||
<h4 className="cta-title text-primary mb-1">
|
<h5 className="cta-title text-primary mb-1">
|
||||||
Ready to Get Started?
|
Ready to Get Started?
|
||||||
</h4>
|
|
||||||
<h5 className="text-body mb-8">
|
|
||||||
Start your project with a 14-day free trial
|
|
||||||
</h5>
|
</h5>
|
||||||
<a href="#landingPricing" className="btn btn-lg btn-primary">
|
<h5 className="text-body mb-8">
|
||||||
|
Start your project with a free trial
|
||||||
|
</h5>
|
||||||
|
{/* <a href="#landingPricing" className="btn btn-lg btn-primary">
|
||||||
Get Started
|
Get Started
|
||||||
</a>{" "}
|
</a>{" "} */}
|
||||||
<a
|
<a
|
||||||
href="/auth/reqest/demo"
|
href="/auth/reqest/demo"
|
||||||
className="btn btn-lg btn-primary"
|
className="btn btn-lg btn-primary"
|
||||||
@ -1190,13 +897,6 @@ const LandingPage = () => {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-lg-6 pt-lg-12 text-center text-lg-end">
|
|
||||||
<img
|
|
||||||
style={{ width: "80%" }}
|
|
||||||
src="/img/images/contact-customer-service.png"
|
|
||||||
alt="hero elements"
|
|
||||||
></img>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@ -1356,7 +1056,10 @@ const LandingPage = () => {
|
|||||||
|
|
||||||
{/* Footer: Start */}
|
{/* Footer: Start */}
|
||||||
<footer className="landing-footer bg-body footer-text">
|
<footer className="landing-footer bg-body footer-text">
|
||||||
<div className="footer-top position-relative overflow-hidden z-1">
|
<div
|
||||||
|
className="footer-top position-relative overflow-hidden z-1"
|
||||||
|
hidden
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
src="/img/backgrounds/footer-bg.png"
|
src="/img/backgrounds/footer-bg.png"
|
||||||
alt="footer bg"
|
alt="footer bg"
|
||||||
@ -1493,7 +1196,7 @@ const LandingPage = () => {
|
|||||||
|
|
||||||
<div className="col-lg-6 col-md-6 d-flex gap-3 align-items-center justify-content-end">
|
<div className="col-lg-6 col-md-6 d-flex gap-3 align-items-center justify-content-end">
|
||||||
<h6 className="footer-title mt-3">Download our app</h6>
|
<h6 className="footer-title mt-3">Download our app</h6>
|
||||||
<a href="javascript:void(0);">
|
<a href="javascript:void(0);" hidden>
|
||||||
<img src="/img/icons/apple-icon.png" alt="apple icon" />
|
<img src="/img/icons/apple-icon.png" alt="apple icon" />
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
@ -1509,21 +1212,9 @@ const LandingPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="footer-bottom py-3 py-md-5">
|
<div className="footer-bottom py-md-4">
|
||||||
<div className="container d-flex flex-wrap justify-content-between flex-md-row flex-column text-center text-md-start">
|
<div className="container d-flex flex-wrap justify-content-between flex-md-row flex-column text-center text-md-start">
|
||||||
<div className="mb-2 mb-md-0">
|
<div className="col-lg-4 col-md-4 d-flex align-items-center justify-content-start">
|
||||||
<span className="footer-bottom-text me-1">
|
|
||||||
©{new Date().getFullYear()}
|
|
||||||
</span>
|
|
||||||
<a
|
|
||||||
href="https://marcoaiot.com"
|
|
||||||
target="_blank"
|
|
||||||
className="text-white"
|
|
||||||
>
|
|
||||||
Marco AIoT Technologies Pvt. Ltd.,
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<a
|
<a
|
||||||
href="https://www.facebook.com/marcoaiot/"
|
href="https://www.facebook.com/marcoaiot/"
|
||||||
className="me-4"
|
className="me-4"
|
||||||
@ -1542,6 +1233,34 @@ const LandingPage = () => {
|
|||||||
<img src="/img/icons/instagram.svg" alt="google icon" />
|
<img src="/img/icons/instagram.svg" alt="google icon" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="col-lg-4 col-md-4 mb-2 mb-md-0 d-flex gap-3 align-items-center justify-content-center">
|
||||||
|
<span className="footer-bottom-text me-1">
|
||||||
|
©{new Date().getFullYear()}
|
||||||
|
</span>
|
||||||
|
<a
|
||||||
|
href="https://marcoaiot.com"
|
||||||
|
target="_blank"
|
||||||
|
className="text-white"
|
||||||
|
>
|
||||||
|
Marco AIoT Technologies Pvt. Ltd.,
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-lg-4 col-md-4 d-flex gap-3 align-items-center justify-content-end">
|
||||||
|
<h6 className="footer-title mt-3">Download our app</h6>
|
||||||
|
<a href="javascript:void(0);" hidden>
|
||||||
|
<img src="/img/icons/apple-icon.png" alt="apple icon" />
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://play.google.com/store/apps/details?id=com.marco.aiotstage&pcampaignid=web_share"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="/img/icons/google-play-icon.png"
|
||||||
|
alt="google play icon"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
44
src/pages/Home/PlanCardSkeleton.jsx
Normal file
44
src/pages/Home/PlanCardSkeleton.jsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const SubscriptionPlanSkeleton = () => {
|
||||||
|
return (
|
||||||
|
<div className="col-xl-4 col-lg-6 col-md-6">
|
||||||
|
<div className="card h-100 shadow-sm border-0 p-3 text-center">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="mb-3">
|
||||||
|
<div className="bg-light rounded-circle mx-auto mb-3" style={{ width: "50px", height: "50px" }}></div>
|
||||||
|
<div className="bg-light rounded w-75 mx-auto mb-2" style={{ height: "20px" }}></div>
|
||||||
|
<div className="bg-light rounded w-50 mx-auto" style={{ height: "16px" }}></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Price */}
|
||||||
|
<div className="mb-3">
|
||||||
|
<div className="bg-light rounded w-50 mx-auto" style={{ height: "24px" }}></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Storage & Trial */}
|
||||||
|
<div className="d-flex justify-content-center gap-4 mb-5">
|
||||||
|
<div className="bg-light rounded" style={{ width: "100px", height: "16px" }}></div>
|
||||||
|
<div className="bg-light rounded" style={{ width: "100px", height: "16px" }}></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Features */}
|
||||||
|
<h6 className="fw-bold text-uppercase border-top pt-3 mb-3 text-center">
|
||||||
|
Features
|
||||||
|
</h6>
|
||||||
|
<ul className="list-unstyled text-start mb-4 ms-7">
|
||||||
|
{[1, 2, 3].map((i) => (
|
||||||
|
<li key={i} className="mb-3">
|
||||||
|
<div className="bg-light rounded" style={{ width: "70%", height: "16px" }}></div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{/* Button */}
|
||||||
|
<div className="bg-light rounded w-100" style={{ height: "40px" }}></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SubscriptionPlanSkeleton;
|
||||||
141
src/pages/Home/SubscriptionPlans.jsx
Normal file
141
src/pages/Home/SubscriptionPlans.jsx
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import axios from "axios";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import PlanCardSkeleton from "./PlanCardSkeleton";
|
||||||
|
|
||||||
|
const SubscriptionPlans = () => {
|
||||||
|
const [plans, setPlans] = useState([]);
|
||||||
|
const [frequency, setFrequency] = useState(1);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchPlans = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const res = await axios.get(
|
||||||
|
`http://localhost:5032/api/market/list/subscription-plan?frequency=${frequency}`,
|
||||||
|
{ headers: { "Content-Type": "application/json" } }
|
||||||
|
);
|
||||||
|
setPlans(res.data?.data || []);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error fetching plans:", err);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchPlans();
|
||||||
|
}, [frequency]);
|
||||||
|
|
||||||
|
const frequencyLabel = (freq) => {
|
||||||
|
switch (freq) {
|
||||||
|
case 0: return "1 mo";
|
||||||
|
case 1: return "3 mo";
|
||||||
|
case 2: return "6 mo";
|
||||||
|
case 3: return "1 yr";
|
||||||
|
default: return "mo";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container py-5">
|
||||||
|
{/* Frequency Switcher */}
|
||||||
|
<div className="text-center mb-4">
|
||||||
|
<div className="btn-group" role="group" aria-label="Plan frequency">
|
||||||
|
{["Monthly", "Quarterly", "Half-Yearly", "Yearly"].map((label, idx) => (
|
||||||
|
<button
|
||||||
|
key={idx}
|
||||||
|
type="button"
|
||||||
|
className={`btn btn-${frequency === idx ? "primary" : "outline-secondary"}`}
|
||||||
|
onClick={() => setFrequency(idx)}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Cards */}
|
||||||
|
<div className="row g-4 mt-10">
|
||||||
|
{loading ? (
|
||||||
|
// Show 3 skeletons
|
||||||
|
<>
|
||||||
|
<PlanCardSkeleton />
|
||||||
|
<PlanCardSkeleton />
|
||||||
|
<PlanCardSkeleton />
|
||||||
|
</>
|
||||||
|
) : plans.length === 0 ? (
|
||||||
|
<div className="text-center">No plans found</div>
|
||||||
|
) : (
|
||||||
|
plans.map((plan) => (
|
||||||
|
<div key={plan.id} className="col-xl-4 col-lg-6 col-md-6">
|
||||||
|
<div className="card h-100 shadow-lg border-0 p-3 text-center p-10">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="mb-3">
|
||||||
|
<i className="bx bxs-package text-primary fs-1 mb-2"></i>
|
||||||
|
<p className="card-title fs-3 fw-bold mb-1">{plan.planName}</p>
|
||||||
|
<p className="text-muted mb-0 fs-5">{plan.description}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Price */}
|
||||||
|
<div className="mb-3">
|
||||||
|
<h4 className="fw-semibold mt-auto mb-0 fs-3">
|
||||||
|
{plan.currency?.symbol} {plan.price}
|
||||||
|
<small className="text-muted ms-1">/ {frequencyLabel(frequency)}</small>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Storage & Trial */}
|
||||||
|
<div className="text-muted mb-5 d-flex justify-content-center gap-4">
|
||||||
|
<div>
|
||||||
|
<i className="fa-solid fa-hdd me-2"></i>
|
||||||
|
Storage {plan.maxStorage} MB
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<i className="fa-regular fa-calendar-check text-success me-2"></i>
|
||||||
|
Trial Days {plan.trialDays}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Features */}
|
||||||
|
<h6 className="fw-bold text-uppercase border-top pt-3 mb-3 text-center">
|
||||||
|
Features
|
||||||
|
</h6>
|
||||||
|
<ul className="list-unstyled text-start mb-4 ms-7 fs-5">
|
||||||
|
{plan.features?.modules &&
|
||||||
|
Object.values(plan.features.modules).map((mod) =>
|
||||||
|
mod && mod.name ? (
|
||||||
|
<li
|
||||||
|
key={mod.id}
|
||||||
|
className="d-flex align-items-center mb-4"
|
||||||
|
>
|
||||||
|
{mod.enabled ? (
|
||||||
|
<i className="fa-regular fa-circle-check text-success me-2"></i>
|
||||||
|
) : (
|
||||||
|
<i className="fa-regular fa-circle-xmark text-danger me-2"></i>
|
||||||
|
)}
|
||||||
|
{mod.name}
|
||||||
|
</li>
|
||||||
|
) : null
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{/* Button */}
|
||||||
|
<div className="mt-auto">
|
||||||
|
<Link
|
||||||
|
to="/auth/reqest/demo"
|
||||||
|
className="btn btn-outline-primary w-100 fw-bold"
|
||||||
|
>
|
||||||
|
Request a Demo
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SubscriptionPlans;
|
||||||
@ -1,9 +1,12 @@
|
|||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||||
import { useOrganizationModal } from "../../hooks/useOrganization";
|
import { useOrganizationModal } from "../../hooks/useOrganization";
|
||||||
|
import OrganizationsList from "../../components/Organization/OrganizationsList";
|
||||||
|
|
||||||
const OrganizationPage = () => {
|
const OrganizationPage = () => {
|
||||||
const orgModal = useOrganizationModal()
|
const orgModal = useOrganizationModal()
|
||||||
|
const [searchText,setSearchText] = useState("")
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container-fluid">
|
<div className="container-fluid">
|
||||||
<Breadcrumb
|
<Breadcrumb
|
||||||
@ -12,10 +15,12 @@ const OrganizationPage = () => {
|
|||||||
<div className="card my-3 px-sm-2 px-0">
|
<div className="card my-3 px-sm-2 px-0">
|
||||||
<div className="card-body py-2 px-3">
|
<div className="card-body py-2 px-3">
|
||||||
<div className="row align-items-center">
|
<div className="row align-items-center">
|
||||||
<div className="col-6 ">
|
<div className="col-6 d-flex ">
|
||||||
<div className="d-flex align-items-center">
|
<div className="d-flex align-items-center">
|
||||||
<input
|
<input
|
||||||
type="search"
|
type="search"
|
||||||
|
value={searchText}
|
||||||
|
onChange={(e)=>setSearchText(e.target.value)}
|
||||||
className="form-control form-control-sm w-auto"
|
className="form-control form-control-sm w-auto"
|
||||||
placeholder="Search Organization"
|
placeholder="Search Organization"
|
||||||
aria-describedby="search-label"
|
aria-describedby="search-label"
|
||||||
@ -36,6 +41,8 @@ const OrganizationPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<OrganizationsList searchText={searchText}/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -136,7 +136,7 @@ const LoginPage = () => {
|
|||||||
)}
|
)}
|
||||||
</div> */}
|
</div> */}
|
||||||
|
|
||||||
<div className="mb-3 form-password-toggle text-start">
|
<div className="mb-3 form-password-toggle text-start">
|
||||||
<label htmlFor="password" className="form-label">
|
<label htmlFor="password" className="form-label">
|
||||||
Password
|
Password
|
||||||
</label>
|
</label>
|
||||||
@ -146,7 +146,8 @@ const LoginPage = () => {
|
|||||||
type={hidepass ? "password" : "text"}
|
type={hidepass ? "password" : "text"}
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
id="password"
|
id="password"
|
||||||
className="form-control form-control-xl shadow-none"
|
className={`form-control form-control-xl shadow-none ${errors.password ? "is-invalid" : ""
|
||||||
|
}`}
|
||||||
name="password"
|
name="password"
|
||||||
{...register("password")}
|
{...register("password")}
|
||||||
placeholder="••••••••••••"
|
placeholder="••••••••••••"
|
||||||
@ -155,7 +156,7 @@ const LoginPage = () => {
|
|||||||
<span className="input-group-text cursor-pointer border-start-0">
|
<span className="input-group-text cursor-pointer border-start-0">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-link p-0"
|
className="btn btn-link-secondary p-0"
|
||||||
onClick={() => setHidepass(!hidepass)}
|
onClick={() => setHidepass(!hidepass)}
|
||||||
>
|
>
|
||||||
{hidepass ? (
|
{hidepass ? (
|
||||||
@ -166,8 +167,16 @@ const LoginPage = () => {
|
|||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* ✅ Error message */}
|
||||||
|
{errors.password && (
|
||||||
|
<div className="invalid-feedback text-start" style={{ fontSize: "12px" }}>
|
||||||
|
{errors.password.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{/* Remember Me + Forgot Password */}
|
{/* Remember Me + Forgot Password */}
|
||||||
<div className="mb-3 d-flex justify-content-between align-items-center">
|
<div className="mb-3 d-flex justify-content-between align-items-center">
|
||||||
<div className="form-check">
|
<div className="form-check">
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { useSearchParams, useParams, useNavigate } from "react-router-dom";
|
import { useSearchParams, useParams, useNavigate } from "react-router-dom";
|
||||||
import { useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useEmployee,
|
useEmployee,
|
||||||
@ -23,22 +23,33 @@ import EmpBanner from "../../components/Employee/EmpBanner";
|
|||||||
import EmpDashboard from "../../components/Employee/EmpDashboard";
|
import EmpDashboard from "../../components/Employee/EmpDashboard";
|
||||||
import EmpDocuments from "../../components/Employee/EmpDocuments";
|
import EmpDocuments from "../../components/Employee/EmpDocuments";
|
||||||
import EmpActivities from "../../components/Employee/EmpActivities";
|
import EmpActivities from "../../components/Employee/EmpActivities";
|
||||||
|
import { setProjectId } from "../../slices/localVariablesSlice";
|
||||||
|
|
||||||
const EmployeeProfile = () => {
|
const EmployeeProfile = () => {
|
||||||
const { profile } = useProfile();
|
const { profile } = useProfile();
|
||||||
|
|
||||||
const projectID = useSelector((store) => store.localVariables.projectId);
|
const projectID = useSelector((store) => store.localVariables.projectId);
|
||||||
|
|
||||||
const { employeeId } = useParams();
|
const { employeeId } = useParams();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const [SearchParams] = useSearchParams();
|
const [SearchParams] = useSearchParams();
|
||||||
const tab = SearchParams.get("for");
|
const tab = SearchParams.get("for");
|
||||||
const [activePill, setActivePill] = useState(tab || "profile");
|
const [activePill, setActivePill] = useState(tab || "profile");
|
||||||
const [showModal, setShowModal] = useState(false);
|
const [showModal, setShowModal] = useState(false);
|
||||||
const {data:currentEmployee,isLoading,isError,error} = useEmployee(employeeId)
|
const {
|
||||||
|
data: currentEmployee,
|
||||||
|
isLoading,
|
||||||
|
isError,
|
||||||
|
error,
|
||||||
|
} = useEmployee(employeeId);
|
||||||
const handlePillClick = (pillKey) => {
|
const handlePillClick = (pillKey) => {
|
||||||
setActivePill(pillKey);
|
setActivePill(pillKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(setProjectId(null));
|
||||||
|
}, [projectID]);
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const renderContent = () => {
|
const renderContent = () => {
|
||||||
@ -87,7 +98,7 @@ const EmployeeProfile = () => {
|
|||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <div>Loading...</div>;
|
return <div>Loading...</div>;
|
||||||
}
|
}
|
||||||
if(isError) return <div >{error.message}</div>
|
if (isError) return <div>{error.message}</div>;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="container-fluid">
|
<div className="container-fluid">
|
||||||
@ -115,7 +126,7 @@ const EmployeeProfile = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className=" ">{renderContent()}</div>
|
<div className=" ">{renderContent()}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { useSelector, useDispatch } from "react-redux"; // Import useSelector
|
|
||||||
import React, { useState, useEffect, useCallback } from "react";
|
import React, { useState, useEffect, useCallback } from "react";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
import ProjectOverview from "../../components/Project/ProjectOverview";
|
import ProjectOverview from "../../components/Project/ProjectOverview";
|
||||||
import AboutProject from "../../components/Project/AboutProject";
|
import AboutProject from "../../components/Project/AboutProject";
|
||||||
@ -9,64 +10,44 @@ import ProjectInfra from "../../components/Project/ProjectInfra";
|
|||||||
import Loader from "../../components/common/Loader";
|
import Loader from "../../components/common/Loader";
|
||||||
import WorkPlan from "../../components/Project/WorkPlan";
|
import WorkPlan from "../../components/Project/WorkPlan";
|
||||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||||
import {
|
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||||
cacheData,
|
import { useProjectDetails, useProjectName } from "../../hooks/useProjects";
|
||||||
clearCacheKey,
|
|
||||||
getCachedData,
|
|
||||||
useSelectedProject,
|
|
||||||
} from "../../slices/apiDataManager";
|
|
||||||
import "./ProjectDetails.css";
|
|
||||||
import { useProjectDetails } from "../../hooks/useProjects";
|
|
||||||
import { ComingSoonPage } from "../Misc/ComingSoonPage";
|
import { ComingSoonPage } from "../Misc/ComingSoonPage";
|
||||||
import eventBus from "../../services/eventBus";
|
import eventBus from "../../services/eventBus";
|
||||||
import ProjectProgressChart from "../../components/Dashboard/ProjectProgressChart";
|
import ProjectProgressChart from "../../components/Dashboard/ProjectProgressChart";
|
||||||
import { useProjectName } from "../../hooks/useProjects";
|
|
||||||
import AttendanceOverview from "../../components/Dashboard/AttendanceChart";
|
import AttendanceOverview from "../../components/Dashboard/AttendanceChart";
|
||||||
import { setProjectId } from "../../slices/localVariablesSlice";
|
import { setProjectId } from "../../slices/localVariablesSlice";
|
||||||
import ProjectDocument from "../../components/Project/ProjectDocuments";
|
|
||||||
import ProjectDocuments from "../../components/Project/ProjectDocuments";
|
import ProjectDocuments from "../../components/Project/ProjectDocuments";
|
||||||
import ProjectSetting from "../../components/Project/ProjectSetting";
|
import ProjectSetting from "../../components/Project/ProjectSetting";
|
||||||
import DirectoryPage from "../Directory/DirectoryPage";
|
import DirectoryPage from "../Directory/DirectoryPage";
|
||||||
import { useHasAnyPermission } from "../../hooks/useExpense";
|
import { useProjectAccess } from "../../hooks/useProjectAccess"; // ✅ new
|
||||||
import { VIEW_PROJECTS } from "../../utils/constants";
|
|
||||||
import { useNavigate, useRoutes } from "react-router-dom";
|
import "./ProjectDetails.css";
|
||||||
|
import ProjectOrganizations from "../../components/Project/ProjectOrganizations";
|
||||||
|
|
||||||
const ProjectDetails = () => {
|
const ProjectDetails = () => {
|
||||||
|
const projectId = useSelectedProject();
|
||||||
const projectId = useSelectedProject()
|
|
||||||
const CanViewProject = useHasAnyPermission(VIEW_PROJECTS);
|
|
||||||
const navigate = useNavigate()
|
|
||||||
|
|
||||||
const { projectNames, fetchData } = useProjectName();
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const { projectNames } = useProjectName();
|
||||||
|
const { projects_Details, loading: projectLoading, refetch } =
|
||||||
|
useProjectDetails(projectId);
|
||||||
|
|
||||||
|
const { canView, loading: permsLoading } = useProjectAccess(projectId);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(!CanViewProject){
|
if (!projectId && projectNames.length > 0) {
|
||||||
navigate("/dashboard")
|
dispatch(setProjectId(projectNames[0].id));
|
||||||
}
|
}
|
||||||
if (projectId == null) {
|
}, [projectId, projectNames, dispatch]);
|
||||||
dispatch(setProjectId(projectNames[0]?.id));
|
|
||||||
}
|
|
||||||
}, [projectNames]);
|
|
||||||
|
|
||||||
const {
|
const [activePill, setActivePill] = useState(
|
||||||
projects_Details,
|
localStorage.getItem("lastActiveProjectTab") || "profile"
|
||||||
loading: projectLoading,
|
);
|
||||||
error: projectError,
|
|
||||||
refetch,
|
|
||||||
} = useProjectDetails(projectId);
|
|
||||||
|
|
||||||
// const [activePill, setActivePill] = useState("profile");
|
|
||||||
const [activePill, setActivePill] = useState(() => {
|
|
||||||
return localStorage.getItem("lastActiveProjectTab") || "profile";
|
|
||||||
});
|
|
||||||
|
|
||||||
const handler = useCallback(
|
const handler = useCallback(
|
||||||
(msg) => {
|
(msg) => {
|
||||||
if (
|
if (msg.keyword === "Update_Project" && projects_Details?.id === msg.response.id) {
|
||||||
msg.keyword === "Update_Project" &&
|
|
||||||
projects_Details?.id === msg.response.id
|
|
||||||
) {
|
|
||||||
refetch();
|
refetch();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -80,69 +61,44 @@ const ProjectDetails = () => {
|
|||||||
|
|
||||||
const handlePillClick = (pillKey) => {
|
const handlePillClick = (pillKey) => {
|
||||||
setActivePill(pillKey);
|
setActivePill(pillKey);
|
||||||
localStorage.setItem("lastActiveProjectTab", pillKey); // ✅ Save to localStorage
|
localStorage.setItem("lastActiveProjectTab", pillKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderContent = () => {
|
if (projectLoading || permsLoading || !projects_Details) {
|
||||||
if (projectLoading || !projects_Details) return <Loader />;
|
return <Loader />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderContent = () => {
|
||||||
switch (activePill) {
|
switch (activePill) {
|
||||||
case "profile":
|
case "profile":
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="row">
|
|
||||||
<div className="col-lg-4 col-md-5 mt-2">
|
|
||||||
<AboutProject></AboutProject>
|
|
||||||
<ProjectOverview project={projectId} />
|
|
||||||
</div>
|
|
||||||
<div className="col-lg-8 col-md-7 mt-5">
|
|
||||||
<ProjectProgressChart
|
|
||||||
ShowAllProject="false"
|
|
||||||
DefaultRange="1M"
|
|
||||||
/>
|
|
||||||
<div className="mt-5">
|
|
||||||
{" "}
|
|
||||||
<AttendanceOverview />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
case "teams":
|
|
||||||
return (
|
return (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-lg-12">
|
<div className="col-lg-4 col-md-5 mt-2">
|
||||||
<Teams />
|
<AboutProject />
|
||||||
|
<ProjectOverview project={projectId} />
|
||||||
|
</div>
|
||||||
|
<div className="col-lg-8 col-md-7 mt-5">
|
||||||
|
<ProjectProgressChart ShowAllProject="false" DefaultRange="1M" />
|
||||||
|
<div className="mt-5">
|
||||||
|
<AttendanceOverview />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
case "teams":
|
||||||
|
return <Teams />;
|
||||||
case "infra":
|
case "infra":
|
||||||
return <ProjectInfra data={projects_Details} onDataChange={refetch} />;
|
return <ProjectInfra data={projects_Details} onDataChange={refetch} />;
|
||||||
|
|
||||||
case "workplan":
|
case "workplan":
|
||||||
return <WorkPlan data={projects_Details} onDataChange={refetch} />;
|
return <WorkPlan data={projects_Details} onDataChange={refetch} />;
|
||||||
|
|
||||||
case "directory":
|
case "directory":
|
||||||
return (
|
return <DirectoryPage IsPage={false} projectId={projects_Details.id} />;
|
||||||
<div className="row mt-2">
|
|
||||||
<DirectoryPage IsPage={false} projectId={projects_Details.id} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
case "documents":
|
case "documents":
|
||||||
return (
|
return <ProjectDocuments />;
|
||||||
<div className="row">
|
case "setting":
|
||||||
<ProjectDocuments />
|
return <ProjectSetting />;
|
||||||
</div>
|
case "organization":
|
||||||
);
|
return <ProjectOrganizations />;
|
||||||
case "setting":
|
|
||||||
return (
|
|
||||||
<div className="row">
|
|
||||||
<ProjectSetting />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return <ComingSoonPage />;
|
return <ComingSoonPage />;
|
||||||
}
|
}
|
||||||
@ -157,7 +113,6 @@ const ProjectDetails = () => {
|
|||||||
{ label: projects_Details?.name || "Project", link: null },
|
{ label: projects_Details?.name || "Project", link: null },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<ProjectNav onPillClick={handlePillClick} activePill={activePill} />
|
<ProjectNav onPillClick={handlePillClick} activePill={activePill} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,9 +1,16 @@
|
|||||||
import { api } from "../utils/axiosClient";
|
import { api } from "../utils/axiosClient";
|
||||||
|
|
||||||
|
const OrganizationRepository = {
|
||||||
|
createOrganization: (data) => api.post("/api/Organization/create", data),
|
||||||
|
getOrganizationList: (pageSize, pageNumber, active, sprid, searchString) => {
|
||||||
|
return api.get(
|
||||||
|
`/api/Organization/list?pageSize=${pageSize}&pageNumber=${pageNumber}&active=${active}&${
|
||||||
|
sprid ? `sprid=${sprid}&` : ""
|
||||||
|
}searchString=${searchString}`
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
const OrganizationRepository = {
|
assignOrganizationToProject:(data)=>api.post(`/api/Organization/assign/project`,data)
|
||||||
createOrganization:(data)=>api.post('/api/Organization/create',data)
|
};
|
||||||
}
|
|
||||||
|
|
||||||
export default OrganizationRepository;
|
export default OrganizationRepository;
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,8 @@ const localVariablesSlice = createSlice({
|
|||||||
reload:false,
|
reload:false,
|
||||||
|
|
||||||
OrganizationModal:{
|
OrganizationModal:{
|
||||||
isOpen:false
|
isOpen:false,
|
||||||
|
orgData:null,
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
@ -37,15 +38,32 @@ const localVariablesSlice = createSlice({
|
|||||||
state.defaultDateRange = action.payload;
|
state.defaultDateRange = action.payload;
|
||||||
},
|
},
|
||||||
|
|
||||||
openOrgModal: (state) => {
|
openOrgModal: (state, action) => {
|
||||||
state.OrganizationModal.isOpen = true;
|
debugger;
|
||||||
},
|
|
||||||
|
if (typeof action.payload === "boolean") {
|
||||||
|
state.OrganizationModal.isOpen = action.payload;
|
||||||
|
if (!action.payload) {
|
||||||
|
state.OrganizationModal.orgData = null;
|
||||||
|
}
|
||||||
|
} else if (typeof action.payload === "object") {
|
||||||
|
const { isOpen, orgData } = action.payload;
|
||||||
|
state.OrganizationModal.isOpen =
|
||||||
|
typeof isOpen === "boolean" ? isOpen : state.OrganizationModal.isOpen;
|
||||||
|
state.OrganizationModal.orgData =
|
||||||
|
orgData !== undefined ? orgData : state.OrganizationModal.orgData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
,
|
||||||
closeOrgModal: (state) => {
|
closeOrgModal: (state) => {
|
||||||
state.OrganizationModal.isOpen = false;
|
state.OrganizationModal.isOpen = false;
|
||||||
},
|
},
|
||||||
toggleOrgModal: (state) => {
|
toggleOrgModal: (state) => {
|
||||||
state.OrganizationModal.isOpen = !state.OrganizationModal.isOpen;
|
state.OrganizationModal.isOpen = !state.OrganizationModal.isOpen;
|
||||||
},
|
},
|
||||||
|
addedOrgModal:(state,action)=>{
|
||||||
|
state.OrganizationModal.orgData = action.payload;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user