Compare commits

...

33 Commits

Author SHA1 Message Date
1452e77bc5 initially setup service provider form 2025-09-18 19:23:24 +05:30
7fa2ca9227 Merge branch 'Organization_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Organization_Management 2025-09-18 15:25:02 +05:30
0d9ef7f248 integrated fetch organization list api and display with search filter 2025-09-18 15:23:48 +05:30
9a3488c92b convert inline style css class into class name 2025-09-18 15:23:48 +05:30
1b144aab8a organization created successfully 2025-09-18 15:23:48 +05:30
2e65007f26 fixed Modal provider component and setup organization creation 2025-09-18 15:23:48 +05:30
3c4c25b449 refactore SelectMult Tag for label display is required or optional 2025-09-18 15:23:23 +05:30
979293ad90 setup organization modal, it seprated form another becuase this modal can open anywhere at one hook 2025-09-18 15:21:23 +05:30
99eaf92e3f initially setup 2025-09-18 15:18:51 +05:30
28b0541894 organization created successfully 2025-09-18 15:18:04 +05:30
a48fc1d989 fixed Modal provider component and setup organization creation 2025-09-18 15:18:04 +05:30
78a0ecebf1 resolved conflict during came rebase 2025-09-18 15:18:04 +05:30
8eb8e27f89 refactore SelectMult Tag for label display is required or optional 2025-09-18 15:18:04 +05:30
012a89b3ea setup organization modal, it seprated form another becuase this modal can open anywhere at one hook 2025-09-18 15:18:04 +05:30
18698a67e3 removed unused files 2025-09-18 15:18:04 +05:30
701d1adc0b initially setup 2025-09-18 15:18:03 +05:30
b2c68824dd fixed refresh existen permission 2025-09-18 12:57:37 +05:30
b3b7297bc3 Merge branch 'main' of https://git.marcoaiot.com/admin/marco.pms.web 2025-09-18 12:40:06 +05:30
9311f41f56 fixed high level changes 2025-09-18 12:38:39 +05:30
9ba2ecfb1f Merge pull request 'Intefrating_API_Dashboard Changes in UI of Dashboard and Login form password field.' (#406) from Intefrating_API_Dashboard into main
Reviewed-on: #406
2025-09-18 07:05:38 +00:00
a27b8571b5 cosmatic changes 2025-09-18 12:35:04 +05:30
fd36298543 Changes in UI of Landing Page subscription and add skeleton 2025-09-18 12:07:46 +05:30
09bb58e50e fixed project level permission bug 2025-09-18 11:30:38 +05:30
1c0e8655c4 Changes in Login-form hide/unhide button add secondary. 2025-09-17 17:09:56 +05:30
6211f52e3a Merge branch 'main' of https://git.marcoaiot.com/admin/marco.pms.web into Intefrating_API_Dashboard 2025-09-17 16:51:56 +05:30
df9107f0d8 Changes in UI of Subscription-plan. 2025-09-17 16:49:52 +05:30
eea7252b96 Changes in UI of Dashboard getplans. 2025-09-17 16:10:49 +05:30
442ecff926 Merge pull request 'Refactor_Directory And Project Level Permsssion' (#404) from Refactor_Directory into main
Reviewed-on: #404
Merged
2025-09-17 10:24:45 +00:00
0abd77dab7 Merge pull request 'Calling API for Dashboard paln show.' (#405) from Intefrating_API_Dashboard into Refactor_Directory
Reviewed-on: #405
Merged
2025-09-17 10:23:13 +00:00
4b0ea3a0db Merge branch 'Refactor_Directory' of https://git.marcoaiot.com/admin/marco.pms.web into Refactor_Directory 2025-09-17 15:50:49 +05:30
d3218eb77a handle project set null at employee details page 2025-09-17 15:50:44 +05:30
4ad87af7f4 Add FAQ Answers on landing page 2025-09-17 15:22:25 +05:30
7b15309dbf Calling API for Dashboard paln show. 2025-09-17 14:38:57 +05:30
24 changed files with 1052 additions and 669 deletions

View File

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

View File

@ -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="align-items-center">
<div className="row"> <div className="row">
{isLoading && (<Loader/> )} <InfraTable buildings={projectInfra} projectId={selectedProject} />
{( !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;

View File

@ -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}

View File

@ -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,13 +38,149 @@ 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 = (
<div>
{/* ---------- STEP 1: Service Provider- Form Own Tenant list ---------- */}
{step === 1 && (
<div className="d-block">
<div className="text-start mb-1">
<Label className="text-secondary">Enter Service Provider ID</Label>
<input
type="text"
className="form-control form-control-sm w-auto"
placeholder="SPR - ID"
aria-describedby="search-label"
/>
</div>
<div className="py-2 text-start">
{orgModal.orgData && (<p className="text-secondary">Don't have Service provder id, Select Service Provider <span className="text-primary text-decoration-underline cursor-pointer" onClick={()=>orgModal.orgData ? setStep(2):setStep(3)}>Choose Provider</span></p>
)}
</div>
{/* ======== org list ======*/}
{/* <div className="list-group mt-3">
<div
className="list-group-item list-group-item-action cursor-pointer"
onClick={() => setStep(2)}
>
<i className="bx bx-building-house me-2"></i>
Sample Organization 1
</div>
</div> */}
<div className="d-flex justify-content-center text-secondary mt-3">
<button
type="button"
className="btn btn-xs btn-secondary"
onClick={() => setStep(4)}
>
<i className="bx bx-plus-circle me-2"></i>
Add New Organization
</button>
</div>
</div>
)}
{/* ---------- STEP 1: Service Provider From Own Other Tenant ---------- */}
{step === 2 && (
<div className="d-block">
{/* Optional: dropdown if projectOrganizations exist */}
{/* Optional: dropdown if projectOrganizations exist */}
<p className="text-secondary">Select Tags</p>
{/* ======== org list ======*/}
<div className="list-group mt-3">
<div
className="list-group-item list-group-item-action cursor-pointer"
onClick={() => setStep(3)}
>
<i className="bx bx-building-house me-2"></i>
Sample Organization 1
</div>
</div>
<div className="d-flex justify-content-center text-secondary mt-3">
<button
type="button"
className="btn btn-xs btn-secondary"
onClick={() => setStep(4)}
>
<i className="bx bx-plus-circle me-2"></i>
Add New Organization
</button>
</div>
</div>
)}
{/* ---------- STEP 2: Existing Organization Details ---------- */}
{step === 3 && (
<div>
<p className="text-muted small">
Show organization details here (from SPR list). User selects
services and clicks Add.
</p>
<div className="mb-2">
<Label>Services Offered</Label>
<ul className="list-group">
<li className="list-group-item">
<input type="checkbox" className="form-check-input me-2" />
Service 1
</li>
<li className="list-group-item">
<input type="checkbox" className="form-check-input me-2" />
Service 2
</li>
</ul>
</div>
<div className="d-flex justify-content-between mt-3">
<button
type="button"
className="btn btn-sm btn-outline-secondary"
onClick={() => setStep(1)}
>
Back
</button>
<button type="button" className="btn btn-sm btn-primary">
Add
</button>
</div>
</div>
)}
{/* ---------- STEP 3: Add New Organization ---------- */}
{step === 4 && (
<FormProvider {...method}> <FormProvider {...method}>
<form className="form" onSubmit={handleSubmit(onSubmit)}> <form className="form" onSubmit={handleSubmit(onSubmit)}>
<div className="mb-1 text-start"> <div className="mb-1 text-start">
@ -65,7 +205,9 @@ const ManageOrganization = () => {
{...register("contactPerson")} {...register("contactPerson")}
/> />
{errors.contactPerson && ( {errors.contactPerson && (
<span className="danger-text">{errors.contactPerson.message}</span> <span className="danger-text">
{errors.contactPerson.message}
</span>
)} )}
</div> </div>
@ -78,7 +220,9 @@ const ManageOrganization = () => {
{...register("contactNumber")} {...register("contactNumber")}
/> />
{errors.contactNumber && ( {errors.contactNumber && (
<span className="danger-text">{errors.contactNumber.message}</span> <span className="danger-text">
{errors.contactNumber.message}
</span>
)} )}
</div> </div>
@ -99,7 +243,7 @@ const ManageOrganization = () => {
<SelectMultiple <SelectMultiple
name="serviceIds" name="serviceIds"
label="Services" label="Services"
required={true} required
valueKey="id" valueKey="id"
options={services?.data || []} options={services?.data || []}
/> />
@ -122,10 +266,18 @@ const ManageOrganization = () => {
)} )}
</div> </div>
<div className="d-flex justify-content-end gap-2 my-2"> <div className="d-flex justify-content-between gap-2 my-2">
<button <button
type="button" type="button"
className="btn btn-sm btn-secondary" 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} onClick={orgModal.onClose}
disabled={isPending || isLoading} disabled={isPending || isLoading}
> >
@ -139,18 +291,66 @@ const ManageOrganization = () => {
{isPending ? "Please Wait..." : "Submit"} {isPending ? "Please Wait..." : "Submit"}
</button> </button>
</div> </div>
</div>
</form> </form>
</FormProvider> </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>

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

View File

@ -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">

View File

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

View File

@ -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),
@ -52,11 +53,19 @@ const ProjectPermission = () => {
?.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"

View File

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

View File

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

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

View File

@ -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) => {

View File

@ -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}

View File

@ -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"));

View File

@ -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 {

View File

@ -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, youll get access to powerful No matter which plan you choose, youll 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 dont
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 organizations 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>

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

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

View File

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

View File

@ -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,7 +167,15 @@ 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">

View File

@ -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">

View File

@ -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 ( return (
<>
<div className="row"> <div className="row">
<div className="col-lg-4 col-md-5 mt-2"> <div className="col-lg-4 col-md-5 mt-2">
<AboutProject></AboutProject> <AboutProject />
<ProjectOverview project={projectId} /> <ProjectOverview project={projectId} />
</div> </div>
<div className="col-lg-8 col-md-7 mt-5"> <div className="col-lg-8 col-md-7 mt-5">
<ProjectProgressChart <ProjectProgressChart ShowAllProject="false" DefaultRange="1M" />
ShowAllProject="false"
DefaultRange="1M"
/>
<div className="mt-5"> <div className="mt-5">
{" "}
<AttendanceOverview /> <AttendanceOverview />
</div> </div>
</div> </div>
</div> </div>
</>
); );
case "teams": case "teams":
return ( return <Teams />;
<div className="row">
<div className="col-lg-12">
<Teams />
</div>
</div>
);
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">
<ProjectDocuments />
</div>
);
case "setting": case "setting":
return ( return <ProjectSetting />;
<div className="row"> case "organization":
<ProjectSetting /> return <ProjectOrganizations />;
</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>

View File

@ -1,9 +1,16 @@
import { api } from "../utils/axiosClient"; import { api } from "../utils/axiosClient";
const OrganizationRepository = { const OrganizationRepository = {
createOrganization:(data)=>api.post('/api/Organization/create',data) 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}`
);
},
assignOrganizationToProject:(data)=>api.post(`/api/Organization/assign/project`,data)
};
export default OrganizationRepository; export default OrganizationRepository;

View File

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