Add skeleton in Dashboard Projects, Teams and Tasks. #401
@ -19,54 +19,60 @@ import { useSelectedProject } from "../../slices/apiDataManager";
|
||||
import Loader from "../common/Loader";
|
||||
|
||||
|
||||
const InfraPlanning = () =>
|
||||
{
|
||||
const {profile: LoggedUser, refetch : fetchData} = useProfile()
|
||||
const dispatch = useDispatch()
|
||||
// const selectedProject = useSelector((store)=>store.localVariables.projectId)
|
||||
|
||||
|
||||
const InfraPlanning = () => {
|
||||
const { profile: LoggedUser, refetch: fetchData } = useProfile();
|
||||
const dispatch = useDispatch();
|
||||
const selectedProject = useSelectedProject();
|
||||
const {projectInfra, isLoading, error} = useProjectInfra( selectedProject )
|
||||
|
||||
const { projectInfra, isLoading, isError, error, isFetched } = useProjectInfra(selectedProject);
|
||||
|
||||
|
||||
const ManageInfra = useHasUserPermission( MANAGE_PROJECT_INFRA )
|
||||
const ApprovedTaskRights = useHasUserPermission(APPROVE_TASK)
|
||||
const ReportTaskRights = useHasUserPermission(ASSIGN_REPORT_TASK)
|
||||
const reloadedData = useSelector( ( store ) => store.localVariables.reload )
|
||||
const canManageInfra = useHasUserPermission(MANAGE_PROJECT_INFRA);
|
||||
const canApproveTask = useHasUserPermission(APPROVE_TASK);
|
||||
const canReportTask = useHasUserPermission(ASSIGN_REPORT_TASK);
|
||||
|
||||
|
||||
// useEffect( () =>
|
||||
// {
|
||||
// if (reloadedData)
|
||||
// {
|
||||
// refetch()
|
||||
// dispatch( refreshData( false ) )
|
||||
// }
|
||||
const reloadedData = useSelector((store) => store.localVariables.reload);
|
||||
|
||||
// },[reloadedData])
|
||||
const hasAccess = canManageInfra || canApproveTask || canReportTask;
|
||||
|
||||
if (isError) {
|
||||
return <div>{error?.response?.data?.message || error?.message}</div>;
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="col-md-12 col-lg-12 col-xl-12 order-0 mb-4">
|
||||
<div className="card">
|
||||
<div className="card-body" style={{ padding: "0.5rem" }}>
|
||||
{(ApprovedTaskRights || ReportTaskRights) ? (
|
||||
<div className="align-items-center">
|
||||
<div className="row ">
|
||||
{isLoading && (<Loader/> )}
|
||||
{( !isLoading && projectInfra?.length === 0 ) && ( <p>No Result Found</p> )}
|
||||
{(!isLoading && projectInfra?.length > 0) && (<InfraTable buildings={projectInfra} projectId={selectedProject}/>)}
|
||||
<div className="card">
|
||||
<div className="card-body" style={{ padding: "0.5rem" }}>
|
||||
<div className="row">
|
||||
<InfraTable buildings={projectInfra} projectId={selectedProject} />
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
||||
export default InfraPlanning;
|
||||
|
||||
|
32
src/components/Charts/ProjectCompletionChartSkeleton.jsx
Normal file
32
src/components/Charts/ProjectCompletionChartSkeleton.jsx
Normal file
@ -0,0 +1,32 @@
|
||||
import React from "react";
|
||||
|
||||
const ProjectCompletionChartSkeleton = () => {
|
||||
return (
|
||||
<div className="card h-100">
|
||||
<div className="card-header d-flex align-items-start justify-content-between">
|
||||
<div className="card-title mb-0 text-start">
|
||||
<h5 className="mb-1 fw-bold placeholder-glow">
|
||||
<span className="placeholder col-6 bg-light"></span>
|
||||
</h5>
|
||||
<p className="card-subtitle placeholder-glow">
|
||||
<span className="placeholder col-8 bg-light"></span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/* Keep a fixed height so card doesn't shrink */}
|
||||
<div className="card-body" >
|
||||
<div className="placeholder-glow">
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="placeholder col-12 mb-2 bg-light"
|
||||
style={{ height: "20px", borderRadius: "4px" }}
|
||||
></div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectCompletionChartSkeleton;
|
44
src/components/Charts/ProjectProgressChartSkeleton.jsx
Normal file
44
src/components/Charts/ProjectProgressChartSkeleton.jsx
Normal file
@ -0,0 +1,44 @@
|
||||
// ProjectProgressChartSkeleton.jsx
|
||||
import React from "react";
|
||||
|
||||
const ProjectProgressChartSkeleton = () => {
|
||||
return (
|
||||
<div className="card" style={{ minHeight: "400px" }}>
|
||||
<div className="card-header">
|
||||
<div className="d-flex flex-wrap justify-content-between align-items-start mb-2">
|
||||
{/* Left: Title */}
|
||||
<div className="card-title text-start">
|
||||
<div className="placeholder-glow">
|
||||
<span className="placeholder col-6 mb-2"></span>
|
||||
<span className="placeholder col-4"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Row 2: Time Range Buttons */}
|
||||
<div className="d-flex flex-wrap mt-2">
|
||||
{Array(7)
|
||||
.fill(0)
|
||||
.map((_, idx) => (
|
||||
<span
|
||||
key={idx}
|
||||
className="placeholder bg-light col-1 me-2"
|
||||
style={{ height: "25px", borderRadius: "5px" }}
|
||||
></span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card-body">
|
||||
<div
|
||||
className="placeholder-glow"
|
||||
style={{ height: "250px", width: "100%" }}
|
||||
>
|
||||
<span className="placeholder bg-light col-12 h-100"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectProgressChartSkeleton;
|
18
src/components/Charts/TeamsSkeleton.jsx
Normal file
18
src/components/Charts/TeamsSkeleton.jsx
Normal file
@ -0,0 +1,18 @@
|
||||
import React from "react";
|
||||
|
||||
const TeamsSkeleton = () => {
|
||||
return (
|
||||
<div className="d-flex justify-content-around align-items-start mt-n2 flex-grow-1">
|
||||
<div>
|
||||
<div className="bg-light rounded" style={{ width: "80px", height: "24px", marginBottom: "5px" }}></div>
|
||||
<div className="bg-light rounded" style={{ width: "60px", height: "12px" }}></div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="bg-light rounded" style={{ width: "80px", height: "24px", marginBottom: "5px" }}></div>
|
||||
<div className="bg-light rounded" style={{ width: "60px", height: "12px" }}></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TeamsSkeleton;
|
@ -100,7 +100,7 @@ const AttendanceOverview = () => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="bg-white p-4 rounded shadow d-flex flex-column"
|
||||
className="bg-white p-4 rounded shadow d-flex flex-column"
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="d-flex justify-content-between align-items-center mb-3">
|
||||
@ -119,18 +119,22 @@ const AttendanceOverview = () => {
|
||||
<option value={30}>Last 30 Days</option>
|
||||
</select>
|
||||
<button
|
||||
className={`btn btn-sm ${view === "chart" ? "btn-primary" : "btn-outline-primary"}`}
|
||||
className={`btn btn-sm p-1 ${
|
||||
view === "chart" ? "btn-primary" : "btn-outline-primary"
|
||||
}`}
|
||||
onClick={() => setView("chart")}
|
||||
title="Chart View"
|
||||
>
|
||||
<i className="bx bx-bar-chart-alt-2"></i>
|
||||
</button>
|
||||
<button
|
||||
className={`btn btn-sm ${view === "table" ? "btn-primary" : "btn-outline-primary"}`}
|
||||
className={`btn btn-sm p-1 ${
|
||||
view === "table" ? "btn-primary" : "btn-outline-primary"
|
||||
}`}
|
||||
onClick={() => setView("table")}
|
||||
title="Table View"
|
||||
>
|
||||
<i className="bx bx-task text-success"></i>
|
||||
<i class="bx bx-list-ul fs-5"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,11 +1,13 @@
|
||||
import React from "react";
|
||||
import HorizontalBarChart from "../Charts/HorizontalBarChart";
|
||||
import { useProjects } from "../../hooks/useProjects";
|
||||
import ProjectCompletionChartSkeleton from "../Charts/ProjectCompletionChartSkeleton";
|
||||
|
||||
const ProjectCompletionChart = () => {
|
||||
const { projects, loading } = useProjects();
|
||||
|
||||
// Bar chart logic
|
||||
if (loading) return <ProjectCompletionChartSkeleton />;
|
||||
|
||||
const projectNames = projects?.map((p) => p.name) || [];
|
||||
const projectProgress =
|
||||
projects?.map((p) => {
|
||||
@ -16,14 +18,15 @@ const ProjectCompletionChart = () => {
|
||||
}) || [];
|
||||
|
||||
return (
|
||||
<div className="card h-100">
|
||||
<div className="card" style={{ minHeight: "490px" }}>
|
||||
<div className="card-header d-flex align-items-start justify-content-between">
|
||||
<div className="card-title mb-0 text-start">
|
||||
<h5 className="mb-1 fw-bold ">Projects</h5>
|
||||
<h5 className="mb-1 fw-bold">Projects</h5>
|
||||
<p className="card-subtitle">Projects Completion Status</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
{/* Keep same minHeight as skeleton to avoid shrinking */}
|
||||
<div className="card-body" >
|
||||
<HorizontalBarChart
|
||||
categories={projectNames}
|
||||
seriesData={projectProgress}
|
||||
|
@ -3,6 +3,7 @@ import LineChart from "../Charts/LineChart";
|
||||
import { useProjects } from "../../hooks/useProjects";
|
||||
import { useDashboard_Data } from "../../hooks/useDashboard_Data";
|
||||
import { useSelector } from "react-redux";
|
||||
import ProjectProgressChartSkeleton from "../Charts/ProjectProgressChartSkeleton";
|
||||
|
||||
const ProjectProgressChart = ({
|
||||
ShowAllProject = true,
|
||||
@ -85,7 +86,7 @@ const ProjectProgressChart = ({
|
||||
: selectedProjectData?.name;
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card" style={{ minHeight: "490px" }}>
|
||||
<div className="card-header">
|
||||
<div className="d-flex flex-wrap justify-content-between align-items-start mb-2">
|
||||
{/* Left: Title */}
|
||||
@ -100,11 +101,10 @@ const ProjectProgressChart = ({
|
||||
{["1D", "1W", "15D", "1M", "3M", "1Y", "5Y"].map((key) => (
|
||||
<button
|
||||
key={key}
|
||||
className={`border-0 bg-transparent px-2 py-1 text-sm rounded ${
|
||||
range === key
|
||||
className={`border-0 bg-transparent px-2 py-1 text-sm rounded ${range === key
|
||||
? "border-bottom border-primary text-primary"
|
||||
: "text-muted"
|
||||
}`}
|
||||
}`}
|
||||
style={{ cursor: "pointer", transition: "all 0.2s ease" }}
|
||||
onClick={() => setRange(key)}
|
||||
>
|
||||
@ -114,14 +114,17 @@ const ProjectProgressChart = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card-body">
|
||||
{isLineChartLoading ? (
|
||||
<ProjectProgressChartSkeleton />
|
||||
) : (
|
||||
<LineChart
|
||||
seriesData={lineChartSeries}
|
||||
categories={lineChartCategories}
|
||||
loading={isLineChartLoading}
|
||||
lineChartCategoriesDates={lineChartCategoriesDates}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -2,9 +2,10 @@ import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useDashboardProjectsCardData } from "../../hooks/useDashboard_Data";
|
||||
import eventBus from "../../services/eventBus";
|
||||
import GlobalRepository from "../../repositories/GlobalRepository";
|
||||
import TeamsSkeleton from "../Charts/TeamsSkeleton";
|
||||
|
||||
const Projects = () => {
|
||||
const { projectsCardData } = useDashboardProjectsCardData();
|
||||
const { projectsCardData,loading } = useDashboardProjectsCardData();
|
||||
const [projectData, setProjectsData] = useState(projectsCardData);
|
||||
|
||||
useEffect(() => {
|
||||
@ -13,13 +14,13 @@ const Projects = () => {
|
||||
|
||||
const handler = useCallback(
|
||||
async (msg) => {
|
||||
try {
|
||||
const response =
|
||||
await GlobalRepository.getDashboardProjectsCardData();
|
||||
setProjectsData(response.data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
try {
|
||||
const response =
|
||||
await GlobalRepository.getDashboardProjectsCardData();
|
||||
setProjectsData(response.data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
},
|
||||
[GlobalRepository]
|
||||
);
|
||||
@ -37,20 +38,24 @@ const Projects = () => {
|
||||
Projects
|
||||
</h5>
|
||||
</div>
|
||||
<div className="d-flex justify-content-around align-items-start mt-n2">
|
||||
<div>
|
||||
<h4 className="mb-0 fw-bold">
|
||||
{projectData.totalProjects?.toLocaleString()}
|
||||
</h4>
|
||||
<small className="text-muted">Total</small>
|
||||
{loading ? (
|
||||
<TeamsSkeleton />
|
||||
) : (
|
||||
<div className="d-flex justify-content-around align-items-start mt-n2">
|
||||
<div>
|
||||
<h4 className="mb-0 fw-bold">
|
||||
{projectData.totalProjects?.toLocaleString()}
|
||||
</h4>
|
||||
<small className="text-muted">Total</small>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="mb-0 fw-bold">
|
||||
{projectData.ongoingProjects?.toLocaleString()}
|
||||
</h4>
|
||||
<small className="text-muted">Ongoing</small>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="mb-0 fw-bold">
|
||||
{projectData.ongoingProjects?.toLocaleString()}
|
||||
</h4>
|
||||
<small className="text-muted">Ongoing</small>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useDashboardTasksCardData } from "../../hooks/useDashboard_Data";
|
||||
|
||||
import TeamsSkeleton from "../Charts/TeamsSkeleton";
|
||||
const TasksCard = () => {
|
||||
const projectId = useSelector((store) => store.localVariables?.projectId);
|
||||
const { tasksCardData, loading, error } = useDashboardTasksCardData(projectId);
|
||||
@ -16,11 +16,7 @@ const TasksCard = () => {
|
||||
|
||||
{loading ? (
|
||||
// Loader will be displayed when loading is true
|
||||
<div className="d-flex justify-content-center align-items-center flex-grow-1">
|
||||
<div className="spinner-border text-primary" role="status">
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
<TeamsSkeleton/>
|
||||
) : error ? (
|
||||
// Error message if there's an error
|
||||
<div className="text-danger flex-grow-1 d-flex justify-content-center align-items-center">{error}</div>
|
||||
|
@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useDashboardTeamsCardData } from "../../hooks/useDashboard_Data";
|
||||
import eventBus from "../../services/eventBus";
|
||||
import TeamsSkeleton from "../Charts/TeamsSkeleton";
|
||||
|
||||
const Teams = () => {
|
||||
const projectId = useSelector((store) => store.localVariables?.projectId);
|
||||
@ -38,11 +39,7 @@ const Teams = () => {
|
||||
|
||||
{loading ? (
|
||||
// Blue spinner loader
|
||||
<div className="d-flex justify-content-center align-items-center flex-grow-1">
|
||||
<div className="spinner-border text-primary" role="status">
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
<TeamsSkeleton/>
|
||||
) : error ? (
|
||||
// Error message if data fetching fails
|
||||
<div className="text-danger flex-grow-1 d-flex justify-content-center align-items-center">{error}</div>
|
||||
|
@ -102,10 +102,10 @@ const ListViewContact = ({ data, Pagination }) => {
|
||||
className="card-datatable table-responsive"
|
||||
id="horizontal-example"
|
||||
>
|
||||
<div className="dataTables_wrapper no-footer ">
|
||||
<div className="dataTables_wrapper no-footer mx-5 pb-2">
|
||||
<table className="table dataTable text-nowrap">
|
||||
<thead>
|
||||
<tr className="shadow-sm ">
|
||||
<tr style={{ borderBottom: "2px solid var(--bs-table-border-color)"}}>
|
||||
{contactList?.map((col) => (
|
||||
<th key={col.key} className={col.align}>
|
||||
{col.label}
|
||||
@ -116,7 +116,7 @@ const ListViewContact = ({ data, Pagination }) => {
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody >
|
||||
{Array.isArray(data) && data.length > 0 ? (
|
||||
data.map((row, i) => (
|
||||
<tr
|
||||
|
@ -10,6 +10,7 @@ import BucketList from "./BucketList";
|
||||
import BucketForm from "./BucketForm";
|
||||
import AssignEmployees from "./AssignedBucket";
|
||||
import AssignedBucket from "./AssignedBucket";
|
||||
import { useDirectoryContext } from "../../pages/Directory/DirectoryPage";
|
||||
|
||||
const ManageBucket1 = () => {
|
||||
const { data, isError, isLoading, error } = useBucketList();
|
||||
@ -17,18 +18,21 @@ const ManageBucket1 = () => {
|
||||
const [action, setAction] = useState(null); // "create" | "edit" | null
|
||||
const [selectedBucket, setSelectedBucket] = useState(null);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const { setContactOpen, setDeleteBucket } = useDirectoryContext();
|
||||
|
||||
const handleClose = ()=>{
|
||||
setAction(null);
|
||||
const handleClose = () => {
|
||||
setAction(null);
|
||||
setSelectedBucket(null);
|
||||
}
|
||||
setDeleteId(null);
|
||||
};
|
||||
const { mutate: createBucket, isPending: creating } = useCreateBucket(() => {
|
||||
handleClose()
|
||||
handleClose();
|
||||
});
|
||||
const { mutate: updateBucket, isPending: updating } = useUpdateBucket(() => {
|
||||
handleClose()
|
||||
handleClose();
|
||||
});
|
||||
|
||||
|
||||
const handleSubmit = (BucketPayload) => {
|
||||
if (selectedBucket) {
|
||||
updateBucket({
|
||||
@ -39,13 +43,13 @@ const ManageBucket1 = () => {
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className="container m-0 p-0" style={{ minHeight: "00px" }}>
|
||||
<div className="d-flex justify-content-center">
|
||||
<p className="fs-5 fw-semibold m-0">Manage Buckets</p>
|
||||
</div>
|
||||
{action ? (
|
||||
|
||||
{action == "create" ? (
|
||||
<>
|
||||
<BucketForm
|
||||
selectedBucket={selectedBucket}
|
||||
@ -58,7 +62,10 @@ const ManageBucket1 = () => {
|
||||
isPending={creating || updating}
|
||||
/>
|
||||
{action === "edit" && selectedBucket && (
|
||||
<AssignedBucket selectedBucket={selectedBucket} handleClose={handleClose} />
|
||||
<AssignedBucket
|
||||
selectedBucket={selectedBucket}
|
||||
handleClose={handleClose}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
@ -84,11 +91,7 @@ const ManageBucket1 = () => {
|
||||
buckets={data}
|
||||
loading={isLoading}
|
||||
searchTerm={searchTerm}
|
||||
onEdit={(bucket) => {
|
||||
setSelectedBucket(bucket);
|
||||
setAction("edit");
|
||||
}}
|
||||
onDelete={(id) => console.log("delete", id)}
|
||||
onDelete={(id) => setDeleteBucket({isOpen:true,bucketId:id})}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
@ -189,7 +189,7 @@ const DocumentFilterPanel = ({ entityTypeId, onApply }) => {
|
||||
<div className="d-flex justify-content-end py-3 gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary btn-xs"
|
||||
className="btn btn-label-secondary btn-xs"
|
||||
onClick={onClear}
|
||||
>
|
||||
Clear
|
||||
|
@ -235,24 +235,9 @@ const ManageDocument = ({ closeModal, Document_Entity, Entity }) => {
|
||||
<p className="fw-bold fs-6">Upload New Document</p>
|
||||
<FormProvider key={documentTypeId} {...methods}>
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="text-start">
|
||||
{/* Document Name */}
|
||||
<div className="mb-2">
|
||||
<Label htmlFor="name" required>
|
||||
Document Name
|
||||
</Label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control form-control-sm"
|
||||
{...register("name")}
|
||||
/>
|
||||
{errors.name && (
|
||||
<div className="danger-text">{errors.name.message}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Category */}
|
||||
<div className="mb-2">
|
||||
<Label htmlFor="documentCategoryId">Document Category</Label>
|
||||
<Label htmlFor="documentCategoryId" required>Document Category</Label>
|
||||
<select
|
||||
{...register("documentCategoryId")}
|
||||
className="form-select form-select-sm"
|
||||
@ -279,7 +264,7 @@ const ManageDocument = ({ closeModal, Document_Entity, Entity }) => {
|
||||
{/* Type */}
|
||||
{categoryId && (
|
||||
<div className="mb-2">
|
||||
<Label htmlFor="documentTypeId">Document Type</Label>
|
||||
<Label htmlFor="documentTypeId" required>Document Type</Label>
|
||||
<select
|
||||
{...register("documentTypeId")}
|
||||
className="form-select form-select-sm"
|
||||
@ -303,14 +288,15 @@ const ManageDocument = ({ closeModal, Document_Entity, Entity }) => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{/* Document ID */}
|
||||
<div className="mb-2">
|
||||
<Label
|
||||
<label
|
||||
htmlFor="documentId"
|
||||
required={selectedType?.isMandatory ?? false}
|
||||
>
|
||||
Document ID
|
||||
</Label>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control form-control-sm"
|
||||
@ -321,6 +307,23 @@ const ManageDocument = ({ closeModal, Document_Entity, Entity }) => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Document Name */}
|
||||
<div className="mb-2">
|
||||
<Label htmlFor="name" required>
|
||||
Document Name
|
||||
</Label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control form-control-sm"
|
||||
{...register("name")}
|
||||
/>
|
||||
{errors.name && (
|
||||
<div className="danger-text">{errors.name.message}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{/* Upload */}
|
||||
<div className="row my-2">
|
||||
<div className="col-md-12">
|
||||
|
@ -16,29 +16,27 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
import { localToUtc } from "../../utils/appUtils";
|
||||
|
||||
|
||||
|
||||
const EmpAttendance = ({ employee }) => {
|
||||
const [attendances, setAttendnaces] = useState([]);
|
||||
const [selectedDate, setSelectedDate] = useState("");
|
||||
const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" });
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [attendanceId, setAttendanecId] = useState();
|
||||
|
||||
|
||||
const methods = useForm({
|
||||
resolver: zodResolver(z.object({
|
||||
startDate: z.string(),
|
||||
endDate: z.string()
|
||||
})),
|
||||
resolver: zodResolver(
|
||||
z.object({
|
||||
startDate: z.string(),
|
||||
endDate: z.string(),
|
||||
})
|
||||
),
|
||||
defaultValues: {
|
||||
startDate: "",
|
||||
endDate: ""
|
||||
endDate: "",
|
||||
},
|
||||
});
|
||||
const { control, register, handleSubmit, reset, watch } = methods;
|
||||
const startDate = watch('startDate')
|
||||
const endDate = watch('endDate')
|
||||
const startDate = watch("startDate");
|
||||
const endDate = watch("endDate");
|
||||
const {
|
||||
data = [],
|
||||
isLoading: loading,
|
||||
@ -46,15 +44,13 @@ const EmpAttendance = ({ employee }) => {
|
||||
isError,
|
||||
error,
|
||||
refetch,
|
||||
} = useAttendanceByEmployee(employee, localToUtc(startDate), localToUtc(endDate));
|
||||
} = useAttendanceByEmployee(
|
||||
employee,
|
||||
localToUtc(startDate),
|
||||
localToUtc(endDate)
|
||||
);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// const { data, loading, error } = useSelector(
|
||||
// (store) => store.employeeAttendance
|
||||
// );
|
||||
|
||||
const [isRefreshing, setIsRefreshing] = useState(true);
|
||||
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
|
||||
@ -92,13 +88,6 @@ const EmpAttendance = ({ employee }) => {
|
||||
.sort(sortByName);
|
||||
const group5 = data.filter((d) => d.activity === 5).sort(sortByName);
|
||||
|
||||
// const sortedFinalList = [
|
||||
// ...group1,
|
||||
// ...group2,
|
||||
// ...group3,
|
||||
// ...group4,
|
||||
// ...group5,
|
||||
// ];
|
||||
|
||||
const uniqueMap = new Map();
|
||||
|
||||
@ -111,7 +100,7 @@ const EmpAttendance = ({ employee }) => {
|
||||
if (
|
||||
!existing ||
|
||||
new Date(rec.checkInTime || rec.checkOutTime) >
|
||||
new Date(existing.checkInTime || existing.checkOutTime)
|
||||
new Date(existing.checkInTime || existing.checkOutTime)
|
||||
) {
|
||||
uniqueMap.set(key, rec);
|
||||
}
|
||||
@ -135,11 +124,7 @@ const EmpAttendance = ({ employee }) => {
|
||||
};
|
||||
const closeModal = () => setIsModalOpen(false);
|
||||
|
||||
|
||||
|
||||
const onSubmit = (formData) => {
|
||||
|
||||
}
|
||||
const onSubmit = (formData) => {};
|
||||
return (
|
||||
<>
|
||||
{isModalOpen && (
|
||||
@ -152,27 +137,28 @@ const EmpAttendance = ({ employee }) => {
|
||||
className="dataTables_length text-start py-2 d-flex justify-content-between "
|
||||
id="DataTables_Table_0_length"
|
||||
>
|
||||
<div className="col-md-4 my-0 ">
|
||||
<div className="col-3 my-0 ">
|
||||
<>
|
||||
<FormProvider {...methods}>
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="p-2 text-start">
|
||||
<form
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
className="p-2 text-start"
|
||||
>
|
||||
<DateRangePicker1
|
||||
|
||||
placeholder="DD-MM-YYYY To DD-MM-YYYY"
|
||||
startField="startDate"
|
||||
endField="endDate"
|
||||
|
||||
defaultRange={true}
|
||||
/>
|
||||
</form>
|
||||
</FormProvider>
|
||||
</>
|
||||
|
||||
</div>
|
||||
<div className="col-md-2 m-0 text-end">
|
||||
<i
|
||||
className={`bx bx-refresh cursor-pointer fs-4 ${isFetching ? "spin" : ""
|
||||
}`}
|
||||
className={`bx bx-refresh cursor-pointer fs-4 ${
|
||||
isFetching ? "spin" : ""
|
||||
}`}
|
||||
data-toggle="tooltip"
|
||||
title="Refresh"
|
||||
onClick={() => refetch()}
|
||||
@ -265,8 +251,9 @@ const EmpAttendance = ({ employee }) => {
|
||||
{[...Array(totalPages)].map((_, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className={`page-item ${currentPage === index + 1 ? "active" : ""
|
||||
}`}
|
||||
className={`page-item ${
|
||||
currentPage === index + 1 ? "active" : ""
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
className="page-link "
|
||||
@ -277,8 +264,9 @@ const EmpAttendance = ({ employee }) => {
|
||||
</li>
|
||||
))}
|
||||
<li
|
||||
className={`page-item ${currentPage === totalPages ? "disabled" : ""
|
||||
}`}
|
||||
className={`page-item ${
|
||||
currentPage === totalPages ? "disabled" : ""
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
className="page-link "
|
||||
|
@ -7,6 +7,10 @@ import {
|
||||
DIRECTORY_USER,
|
||||
MANAGE_PROJECT_INFRA,
|
||||
MANAGE_TASK,
|
||||
MANAGE_TEAM,
|
||||
MODIFY_DOCUMENT,
|
||||
UPLOAD_DOCUMENT,
|
||||
VIEW_DOCUMENT,
|
||||
VIEW_PROJECT_INFRA,
|
||||
} from "../../utils/constants";
|
||||
|
||||
@ -17,6 +21,10 @@ const ProjectNav = ({ onPillClick, activePill }) => {
|
||||
const DirAdmin = useHasUserPermission(DIRECTORY_ADMIN);
|
||||
const DireManager = useHasUserPermission(DIRECTORY_MANAGER);
|
||||
const DirUser = useHasUserPermission(DIRECTORY_USER);
|
||||
const isManageTeam = useHasUserPermission(MANAGE_TEAM)
|
||||
const isViewDocuments = hasUserPermission(VIEW_DOCUMENT);
|
||||
const isUploadDocument = useHasUserPermission(UPLOAD_DOCUMENT)
|
||||
const isModifyDocument = useHasUserPermission(MODIFY_DOCUMENT)
|
||||
|
||||
const ProjectTab = [
|
||||
{ key: "profile", icon: "bx bx-user", label: "Profile" },
|
||||
@ -33,8 +41,8 @@ const ProjectNav = ({ onPillClick, activePill }) => {
|
||||
label: "Directory",
|
||||
hidden: !(DirAdmin || DireManager || DirUser),
|
||||
},
|
||||
{ key: "documents", icon: "bx bx-folder-open", label: "Documents" },
|
||||
{ key: "setting", icon: "bx bxs-cog", label: "Setting" },
|
||||
{ key: "documents", icon: "bx bx-folder-open", label: "Documents",hidden:!(isViewDocuments || isModifyDocument || isUploadDocument) },
|
||||
{ key: "setting", icon: "bx bxs-cog", label: "Setting",hidden:!isManageTeam },
|
||||
];
|
||||
return (
|
||||
<div className="nav-align-top">
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useEffect } from "react";
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
import {
|
||||
useProjectLevelEmployeePermission,
|
||||
useProjectLevelModules,
|
||||
@ -6,7 +6,7 @@ import {
|
||||
} from "../../hooks/useProjects";
|
||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||
import { useEmployeesByProject } from "../../hooks/useEmployees";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useForm, Controller } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
import showToast from "../../services/toastService";
|
||||
@ -27,7 +27,9 @@ const ProjectPermission = () => {
|
||||
watch,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { errors },
|
||||
control,
|
||||
setValue,
|
||||
formState: { errors, isDirty },
|
||||
} = useForm({
|
||||
resolver: zodResolver(ProjectPermissionSchema),
|
||||
defaultValues: {
|
||||
@ -43,7 +45,7 @@ const ProjectPermission = () => {
|
||||
selectedProject
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (!selectedEmployee) return;
|
||||
|
||||
const enabledPerms =
|
||||
@ -51,127 +53,167 @@ useEffect(() => {
|
||||
?.filter((perm) => perm.isEnabled)
|
||||
?.map((perm) => perm.id) || [];
|
||||
|
||||
reset((prev) => ({
|
||||
...prev,
|
||||
selectedPermissions: enabledPerms,
|
||||
}));
|
||||
}, [selectedEmpPermissions, reset, selectedEmployee]);
|
||||
setValue("selectedPermissions", enabledPerms, { shouldValidate: true });
|
||||
}, [selectedEmpPermissions, setValue, selectedEmployee]);
|
||||
|
||||
const selectedPermissions = watch("selectedPermissions") || [];
|
||||
|
||||
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 } =
|
||||
useUpdateProjectLevelEmployeePermission();
|
||||
|
||||
const onSubmit = (formData) => {
|
||||
if (!formData.employeeId) {
|
||||
showToast("Please select an employee", "warn");
|
||||
return;
|
||||
}
|
||||
const onSubmit = (formData) => {
|
||||
if (!formData.employeeId) {
|
||||
showToast("Please select an employee", "warn");
|
||||
return;
|
||||
}
|
||||
|
||||
const existingPermissions = selectedEmpPermissions?.permissions || [];
|
||||
const existingEnabledIds = existingPermissions
|
||||
.filter((p) => p.isEnabled)
|
||||
.map((p) => p.id);
|
||||
const existingPermissions = selectedEmpPermissions?.permissions || [];
|
||||
const existingEnabledIds = existingPermissions
|
||||
.filter((p) => p.isEnabled)
|
||||
.map((p) => p.id);
|
||||
|
||||
const newSelectedIds = formData.selectedPermissions || [];
|
||||
const newSelectedIds = formData.selectedPermissions || [];
|
||||
|
||||
const removed = existingEnabledIds
|
||||
.filter((id) => !newSelectedIds.includes(id))
|
||||
.map((id) => ({ id, isEnabled: false }));
|
||||
const added = newSelectedIds
|
||||
.filter((id) => !existingEnabledIds.includes(id))
|
||||
.map((id) => ({ id, isEnabled: true }));
|
||||
|
||||
const added = newSelectedIds
|
||||
.filter((id) => !existingEnabledIds.includes(id))
|
||||
.map((id) => ({ id, isEnabled: true }));
|
||||
const removed = existingEnabledIds
|
||||
.filter((id) => !newSelectedIds.includes(id))
|
||||
.map((id) => ({ id, isEnabled: false }));
|
||||
|
||||
const payloadPermissions = [...removed, ...added];
|
||||
const payloadPermissions = [...added, ...removed];
|
||||
|
||||
if (payloadPermissions.length === 0) {
|
||||
showToast("No changes detected", "info");
|
||||
return;
|
||||
}
|
||||
if (payloadPermissions.length === 0) {
|
||||
showToast("No changes detected", "info");
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
employeeId: formData.employeeId,
|
||||
projectId: selectedProject,
|
||||
permission: payloadPermissions,
|
||||
const payload = {
|
||||
employeeId: formData.employeeId,
|
||||
projectId: selectedProject,
|
||||
permission: payloadPermissions,
|
||||
};
|
||||
|
||||
console.log("Final payload:", payload);
|
||||
updatePermission(payload);
|
||||
};
|
||||
|
||||
updatePermission(payload);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className="row px-2 py-1">
|
||||
<div className="w-100 p py-1 ">
|
||||
<div className="text-start m-0">
|
||||
<p className="fw-semibold fs-6">Project Permission</p>
|
||||
</div>
|
||||
<form className="row" onSubmit={handleSubmit(onSubmit)}>
|
||||
{/* Employee Dropdown */}
|
||||
<div className="d-flex align-items-end gap-2">
|
||||
<div className="text-start">
|
||||
<label className="form-label">Select Employee</label>
|
||||
<select
|
||||
className="form-select form-select-sm"
|
||||
{...register("employeeId")}
|
||||
disabled={isPending}
|
||||
>
|
||||
{loading ? (
|
||||
<option value="">Loading...</option>
|
||||
) : (
|
||||
<>
|
||||
<option value="">-- Select Employee --</option>
|
||||
{[...employees]?.sort((a, b) =>
|
||||
`${a.firstName} ${a.firstName}`?.localeCompare(
|
||||
`${b.firstName} ${b.lastName}`
|
||||
<div className="d-flex justify-content-between align-items-end gap-2 mb-3">
|
||||
<div className="text-start d-flex align-items-center gap-2">
|
||||
<div className="d-block">
|
||||
<label className="form-label">Select Employee</label>
|
||||
</div>
|
||||
<div className="d-block">
|
||||
{" "}
|
||||
<select
|
||||
className="form-select form-select-sm"
|
||||
{...register("employeeId")}
|
||||
disabled={isPending}
|
||||
>
|
||||
{loading ? (
|
||||
<option value="">Loading...</option>
|
||||
) : (
|
||||
<>
|
||||
<option value="">-- Select Employee --</option>
|
||||
{[...employees]
|
||||
?.sort((a, b) =>
|
||||
`${a?.firstName} ${a?.firstName}`?.localeCompare(
|
||||
`${b?.firstName} ${b?.lastName}`
|
||||
)
|
||||
)
|
||||
)
|
||||
?.map((emp) => (
|
||||
<option key={emp.id} value={emp.id}>
|
||||
{emp.firstName} {emp.lastName}
|
||||
</option>
|
||||
))}
|
||||
</>
|
||||
?.map((emp) => (
|
||||
<option key={emp.id} value={emp.id}>
|
||||
{emp.firstName} {emp.lastName}
|
||||
</option>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</select>
|
||||
{errors.employeeId && (
|
||||
<div className="d-block text-danger small">
|
||||
{errors.employeeId.message}
|
||||
</div>
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{errors.employeeId && (
|
||||
<div className="text-danger small">
|
||||
{errors.employeeId.message}
|
||||
</div>
|
||||
<div className="mt-3 text-end">
|
||||
{hasChanges && (
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-sm btn-primary"
|
||||
disabled={isPending || loading}
|
||||
>
|
||||
{isPending ? "Please Wait..." : "Save Permission"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
className="btn btn-sm btn-primary"
|
||||
disabled={isPending || loading}
|
||||
>
|
||||
{isPending ? "Please Wait..." : "Update Permission"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Permissions */}
|
||||
{ProjectModules.map((feature) => (
|
||||
<div key={feature.id} className="row my-2 px-3 ">
|
||||
<div className="col-12 text-start fw-semibold mb-2">
|
||||
{feature.name}
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<div className="row">
|
||||
{feature.featurePermissions?.map((perm) => (
|
||||
<div className="col-12 col-sm-6 col-md-4 mb-2" key={perm.id}>
|
||||
<label
|
||||
className="form-check-label d-flex align-items-center"
|
||||
htmlFor={perm.id}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input me-2"
|
||||
id={perm.id}
|
||||
value={perm.id}
|
||||
{...register("selectedPermissions")}
|
||||
<div
|
||||
key={feature.id}
|
||||
className="col-12 col-sm-6 col-md-4 col-lg-4 mb-4"
|
||||
>
|
||||
<div className="card text-start border-1 p-1">
|
||||
<p className="card-title fs-6 fw-semibold">{feature.name}</p>
|
||||
<div className="px-2">
|
||||
<ul className="list-unstyled">
|
||||
{feature.featurePermissions?.map((perm) => (
|
||||
<div className="d-flex my-2" key={perm.id}>
|
||||
<Controller
|
||||
name="selectedPermissions"
|
||||
control={control}
|
||||
render={({ field }) => {
|
||||
const value = field.value || [];
|
||||
const isChecked = value.includes(perm.id);
|
||||
|
||||
return (
|
||||
<label
|
||||
className="form-check-label d-flex align-items-center"
|
||||
htmlFor={perm.id}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input me-2"
|
||||
id={perm.id}
|
||||
checked={isChecked}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
field.onChange([...value, perm.id]); // add
|
||||
} else {
|
||||
field.onChange(
|
||||
value.filter((v) => v !== perm.id)
|
||||
); // remove
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{perm.name}
|
||||
</label>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{perm.name}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<hr className="my-2" />
|
||||
</div>
|
||||
))}
|
||||
</form>
|
||||
|
@ -3,9 +3,10 @@ import { ComingSoonPage } from "../../pages/Misc/ComingSoonPage";
|
||||
import ProjectPermission from "./ProjectPermission";
|
||||
|
||||
const ProjectSetting = () => {
|
||||
const [activePill, setActivePill] = useState(() => {
|
||||
return localStorage.getItem("lastActiveProjectSettingTab") || "Permissions";
|
||||
});
|
||||
const [activePill, setActivePill] = useState("Permissions")
|
||||
// const [activePill, setActivePill] = useState(() => {
|
||||
// return localStorage.getItem("lastActiveProjectSettingTab") || "Permissions";
|
||||
// });
|
||||
const projectSettingTab = [
|
||||
{ key: "Permissions", label: "Permissions" },
|
||||
{ key: "Notification", label: "Notification" },
|
||||
@ -32,7 +33,7 @@ const ProjectSetting = () => {
|
||||
return (
|
||||
<div className="w-100">
|
||||
<div className="card py-2 px-5">
|
||||
<div className="col-12">
|
||||
{/* <div className="col-12">
|
||||
<div className="dropdown text-end">
|
||||
<button
|
||||
className="btn btn-sm btn-outline-primary dropdown-toggle"
|
||||
@ -63,7 +64,7 @@ const ProjectSetting = () => {
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div className="mt-3">{renderContent()}</div>
|
||||
</div>
|
||||
|
@ -125,18 +125,18 @@ const TenantsList = ({
|
||||
];
|
||||
if (isInitialLoading)
|
||||
return <TenantTableSkeleton columns={TenantColumns} rows={13} />;
|
||||
if (isError)
|
||||
if (isError)
|
||||
return (
|
||||
<div className="">
|
||||
<div className="card text-center my-4 p-2">
|
||||
<i className="fa-solid fa-triangle-exclamation fs-5"></i>
|
||||
<p>{error.message}</p>
|
||||
</div>
|
||||
<div className="text-center my-4 p-2">
|
||||
<i className="fa-solid fa-triangle-exclamation fs-5"></i>
|
||||
<p>{error.message}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<div className="card p-2 mt-3">
|
||||
<div className="p-2 mt-3">
|
||||
<div className="card-datatable text-nowrap table-responsive">
|
||||
<table className="table border-top dataTable text-nowrap">
|
||||
<thead>
|
||||
|
@ -1,11 +1,26 @@
|
||||
import { useSelectedProject } from "../slices/apiDataManager";
|
||||
import { useAllProjectLevelPermissions, useProfile } from "./useProfile";
|
||||
|
||||
import { useProfile } from "./useProfile"
|
||||
export const useHasUserPermission = (permission) => {
|
||||
const selectedProject = useSelectedProject();
|
||||
const { profile } = useProfile();
|
||||
const {
|
||||
data: projectPermissions = [],
|
||||
isLoading,
|
||||
isError,
|
||||
} = useAllProjectLevelPermissions(selectedProject);
|
||||
|
||||
if (profile && permission && typeof permission === "string") {
|
||||
return profile?.featurePermissions.includes(permission);
|
||||
if (isLoading || !permission) return false;
|
||||
|
||||
const globalPerms = profile?.featurePermissions ?? [];
|
||||
const projectPerms = projectPermissions ?? [];
|
||||
if (selectedProject) {
|
||||
if (projectPerms.length === 0) {
|
||||
return projectPerms.includes(permission);
|
||||
} else {
|
||||
return projectPerms.includes(permission);
|
||||
}
|
||||
} else {
|
||||
return globalPerms.includes(permission);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
@ -1,67 +1,20 @@
|
||||
import {useState,useEffect, useCallback} from "react";
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import AuthRepository from "../repositories/AuthRepository";
|
||||
import {cacheData, cacheProfileData, getCachedData, getCachedProfileData} from "../slices/apiDataManager";
|
||||
import {useSelector} from "react-redux";
|
||||
import {
|
||||
cacheData,
|
||||
cacheProfileData,
|
||||
getCachedData,
|
||||
getCachedProfileData,
|
||||
useSelectedProject,
|
||||
} from "../slices/apiDataManager";
|
||||
import { useSelector } from "react-redux";
|
||||
import eventBus from "../services/eventBus";
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import ProjectRepository from "../repositories/ProjectRepository";
|
||||
|
||||
let hasFetched = false;
|
||||
let hasReceived = false;
|
||||
|
||||
// export const useProfile = () => {
|
||||
// const loggedUser = useSelector( ( store ) => store.globalVariables.loginUser );
|
||||
// const [profile, setProfile] = useState(null);
|
||||
// const [loading, setLoading] = useState(false);
|
||||
// const [error, setError] = useState("");
|
||||
|
||||
// const fetchData = async () => {
|
||||
// try {
|
||||
// setLoading(true);
|
||||
// let response = await AuthRepository.profile();
|
||||
// setProfile(response.data);
|
||||
// cacheProfileData(response.data);
|
||||
// } catch (error) {
|
||||
// setError("Failed to fetch data.");
|
||||
// } finally {
|
||||
// setLoading(false);
|
||||
// }
|
||||
// };
|
||||
|
||||
// const validation = () => {
|
||||
// if (!hasFetched) {
|
||||
// hasFetched = true;
|
||||
// if (!loggedUser) {
|
||||
// fetchData();
|
||||
// } else {
|
||||
// setProfile(loggedUser);
|
||||
// }
|
||||
// }
|
||||
|
||||
// setProfile(loggedUser);
|
||||
// }
|
||||
|
||||
// useEffect(() => {
|
||||
// validation();
|
||||
// }, [loggedUser]);
|
||||
|
||||
// const handler = useCallback(
|
||||
// (data) => {
|
||||
// if(!getCachedData("hasReceived")){
|
||||
// cacheData("hasReceived", true);
|
||||
// hasFetched = false;
|
||||
// validation();
|
||||
// }
|
||||
// },[]
|
||||
// );
|
||||
|
||||
// useEffect(() => {
|
||||
// eventBus.on("assign_project_one", handler);
|
||||
// return () => eventBus.off("assign_project_one", handler);
|
||||
// }, [handler]);
|
||||
|
||||
// return { profile, loading, error };
|
||||
// };
|
||||
|
||||
export const useProfile = () => {
|
||||
const loggedUser = useSelector((store) => store.globalVariables.loginUser);
|
||||
const queryClient = useQueryClient();
|
||||
@ -100,12 +53,26 @@ export const useProfile = () => {
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
export const useSidBarMenu = ()=>{
|
||||
const userLogged = useSelector((store)=>store.globalVariables.loginUser);
|
||||
export const useSidBarMenu = () => {
|
||||
const userLogged = useSelector((store) => store.globalVariables.loginUser);
|
||||
return useQuery({
|
||||
queryKey:["AppMenu"],
|
||||
queryFn:async()=> await AuthRepository.appmenu(),
|
||||
enabled: !!userLogged
|
||||
})
|
||||
}
|
||||
queryKey: ["AppMenu"],
|
||||
queryFn: async () => await AuthRepository.appmenu(),
|
||||
enabled: !!userLogged,
|
||||
});
|
||||
};
|
||||
|
||||
export const useAllProjectLevelPermissions = (projectId) => {
|
||||
|
||||
|
||||
return useQuery({
|
||||
queryKey: ["AllProjectLevelPermission", projectId],
|
||||
queryFn: async () => {
|
||||
const resp = await ProjectRepository.getAllProjectLevelPermission(
|
||||
projectId
|
||||
);
|
||||
return resp.data;
|
||||
},
|
||||
enabled: !!projectId,
|
||||
});
|
||||
};
|
||||
|
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,
|
||||
isLoading,
|
||||
error,
|
||||
isFetched
|
||||
} = useQuery({
|
||||
queryKey: ["ProjectInfra", projectId],
|
||||
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) => {
|
||||
|
@ -95,11 +95,11 @@ const AttendancePage = () => {
|
||||
{(modelConfig?.action === 0 ||
|
||||
modelConfig?.action === 1 ||
|
||||
modelConfig?.action === 2) && (
|
||||
<CheckCheckOutmodel
|
||||
modeldata={modelConfig}
|
||||
closeModal={closeModal}
|
||||
/>
|
||||
)}
|
||||
<CheckCheckOutmodel
|
||||
modeldata={modelConfig}
|
||||
closeModal={closeModal}
|
||||
/>
|
||||
)}
|
||||
{/* For view logs */}
|
||||
{modelConfig?.action === 6 && (
|
||||
<AttendLogs Id={modelConfig?.id} closeModal={closeModal} />
|
||||
@ -118,19 +118,19 @@ const AttendancePage = () => {
|
||||
]}
|
||||
></Breadcrumb>
|
||||
|
||||
<div className="nav-align-top nav-tabs-shadow">
|
||||
<div className="nav-align-top nav-tabs-shadow ">
|
||||
{/* Tabs */}
|
||||
<div className="nav-align-top nav-tabs-shadow bg-white border-bottom">
|
||||
<div className="row align-items-center g-0 mb-3 mb-md-0">
|
||||
<div className="nav-align-top nav-tabs-shadow bg-white border-bottom pt-5">
|
||||
<div className="row align-items-center g-0 mb-3 mb-md-0 mx-5">
|
||||
{/* Tabs */}
|
||||
<div className="col-12 col-md">
|
||||
<ul className="nav nav-tabs" role="tablist">
|
||||
<li className="nav-item">
|
||||
<button
|
||||
type="button"
|
||||
className={`nav-link ${
|
||||
activeTab === "all" ? "active" : ""
|
||||
} fs-6`}
|
||||
className={`nav-link ${
|
||||
activeTab === "all" ? "active" : ""
|
||||
} fs-6`}
|
||||
onClick={() => handleTabChange("all")}
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#navs-top-home"
|
||||
@ -141,9 +141,9 @@ const AttendancePage = () => {
|
||||
<li className="nav-item">
|
||||
<button
|
||||
type="button"
|
||||
className={`nav-link ${
|
||||
activeTab === "logs" ? "active" : ""
|
||||
} fs-6`}
|
||||
className={`nav-link ${
|
||||
activeTab === "logs" ? "active" : ""
|
||||
} fs-6`}
|
||||
onClick={() => handleTabChange("logs")}
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#navs-top-profile"
|
||||
@ -155,9 +155,9 @@ const AttendancePage = () => {
|
||||
<li className={`nav-item ${!DoRegularized ? "d-none" : ""}`}>
|
||||
<button
|
||||
type="button"
|
||||
className={`nav-link ${
|
||||
activeTab === "regularization" ? "active" : ""
|
||||
} fs-6`}
|
||||
className={`nav-link ${
|
||||
activeTab === "regularization" ? "active" : ""
|
||||
} fs-6`}
|
||||
onClick={() => handleTabChange("regularization")}
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#navs-top-messages"
|
||||
@ -182,11 +182,11 @@ const AttendancePage = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="tab-content attedanceTabs py-0 px-1 px-sm-3">
|
||||
<div className="tab-content attedanceTabs py-0 px-1 px-sm-3 pb-10">
|
||||
{selectedProject ? (
|
||||
<>
|
||||
{activeTab === "all" && (
|
||||
<div className="tab-pane fade show active py-0">
|
||||
<div className="tab-pane fade show active py-0 mx-5">
|
||||
<Attendance
|
||||
handleModalData={handleModalData}
|
||||
getRole={getRole}
|
||||
@ -214,7 +214,6 @@ const AttendancePage = () => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
@ -13,7 +13,7 @@ const { projectNames = [], loading: projectLoading } = useProjectName();
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedProject) {
|
||||
dispatch(setProjectId(projectNames[0].id));
|
||||
dispatch(setProjectId(projectNames[0]?.id));
|
||||
}
|
||||
}, [projectNames, selectedProject?.id, dispatch]);
|
||||
|
||||
|
@ -8,7 +8,11 @@ import {
|
||||
} from "react";
|
||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||
import { useFab } from "../../Context/FabContext";
|
||||
import { useBucketList, useBuckets } from "../../hooks/useDirectory";
|
||||
import {
|
||||
useBucketList,
|
||||
useBuckets,
|
||||
useDeleteBucket,
|
||||
} from "../../hooks/useDirectory";
|
||||
import ManageBucket1 from "../../components/Directory/ManageBucket1";
|
||||
import ManageContact from "../../components/Directory/ManageContact";
|
||||
import BucketList from "../../components/Directory/BucketList";
|
||||
@ -16,6 +20,8 @@ import { MainDirectoryPageSkeleton } from "../../components/Directory/DirectoryP
|
||||
import ContactProfile from "../../components/Directory/ContactProfile";
|
||||
import GlobalModel from "../../components/common/GlobalModel";
|
||||
import { exportToCSV } from "../../utils/exportUtils";
|
||||
import ConfirmModal from "../../components/common/ConfirmModal";
|
||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||
|
||||
const NotesPage = lazy(() => import("./NotesPage"));
|
||||
const ContactsPage = lazy(() => import("./ContactsPage"));
|
||||
@ -44,6 +50,10 @@ export default function DirectoryPage({ IsPage = true, projectId = null }) {
|
||||
isOpen: false,
|
||||
contactId: null,
|
||||
});
|
||||
const [deleteBucket, setDeleteBucket] = useState({
|
||||
isOpen: false,
|
||||
bucketId: null,
|
||||
});
|
||||
const [showActive, setShowActive] = useState(true);
|
||||
const [contactOpen, setContactOpen] = useState({
|
||||
contact: null,
|
||||
@ -100,129 +110,140 @@ export default function DirectoryPage({ IsPage = true, projectId = null }) {
|
||||
data,
|
||||
setManageContact,
|
||||
setContactOpen,
|
||||
setDeleteBucket,
|
||||
};
|
||||
|
||||
const { mutate: DeleteBucket, isPending: Deleting } = useDeleteBucket(() => {
|
||||
setDeleteBucket({ isOpen: false, bucketId: null });
|
||||
});
|
||||
const handleDelete = (bucketId) => {
|
||||
DeleteBucket(bucketId);
|
||||
};
|
||||
if (isLoading) return <MainDirectoryPageSkeleton />;
|
||||
if (isError) return <div>{error.message}</div>;
|
||||
return (
|
||||
<>
|
||||
<DirectoryContext.Provider value={contextValues}>
|
||||
<div className={`${IsPage ? "container-fluid" : ""}`}>
|
||||
{IsPage && (<Breadcrumb
|
||||
data={[
|
||||
{ label: "Home", link: "/dashboard" },
|
||||
{ label: "Directory", link: null },
|
||||
]}
|
||||
></Breadcrumb>)}
|
||||
{IsPage && (
|
||||
<Breadcrumb
|
||||
data={[
|
||||
{ label: "Home", link: "/dashboard" },
|
||||
{ label: "Directory", link: null },
|
||||
]}
|
||||
></Breadcrumb>
|
||||
)}
|
||||
<div className="card">
|
||||
<div className="d-flex justify-content-between align-items-center mb-1 px-2">
|
||||
<ul className="nav nav-tabs">
|
||||
<li className="nav-item cursor-pointer">
|
||||
<a
|
||||
className={`nav-link ${
|
||||
activeTab === "notes" ? "active" : ""
|
||||
} fs-6`}
|
||||
onClick={(e) => handleTabClick("notes", e)}
|
||||
>
|
||||
<i className="bx bx-note bx-sm me-1_5"></i>
|
||||
<span className="d-none d-md-inline">Notes</span>
|
||||
</a>
|
||||
</li>
|
||||
<li className="nav-item cursor-pointer">
|
||||
<a
|
||||
className={`nav-link ${
|
||||
activeTab === "contacts" ? "active" : ""
|
||||
} fs-6`}
|
||||
onClick={(e) => handleTabClick("contacts", e)}
|
||||
>
|
||||
<i className="bx bxs-contact bx-sm me-1_5"></i>
|
||||
<span className="d-none d-md-inline">Contacts</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div className="btn-group">
|
||||
<button
|
||||
className="btn btn-sm btn-label-secondary dropdown-toggle"
|
||||
type="button"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<i className="bx bx-export me-2 bx-sm"></i>Export
|
||||
</button>
|
||||
<ul className="dropdown-menu">
|
||||
<li>
|
||||
{/* <a className="dropdown-item" href="#"> */}
|
||||
<div className="d-flex-row px-2">
|
||||
<div className="d-flex justify-content-between align-items-center mb-1">
|
||||
<ul className="nav nav-tabs">
|
||||
<li className="nav-item cursor-pointer">
|
||||
<a
|
||||
className="dropdown-item cursor-pointer"
|
||||
onClick={() => handleExport("csv")}
|
||||
className={`nav-link ${
|
||||
activeTab === "notes" ? "active" : ""
|
||||
} fs-6`}
|
||||
onClick={(e) => handleTabClick("notes", e)}
|
||||
>
|
||||
<i className="bx bx-file me-1"></i> CSV
|
||||
<i className="bx bx-notepad bx-sm me-1_5"></i>
|
||||
<span className="d-none d-md-inline">Notes</span>
|
||||
</a>
|
||||
</li>
|
||||
<li className="nav-item cursor-pointer">
|
||||
<a
|
||||
className={`nav-link ${
|
||||
activeTab === "contacts" ? "active" : ""
|
||||
} fs-6`}
|
||||
onClick={(e) => handleTabClick("contacts", e)}
|
||||
>
|
||||
<i className="bx bxs-contact bx-sm me-1_5"></i>
|
||||
<span className="d-none d-md-inline">Contacts</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-1 px-2">
|
||||
<div className="d-flex align-items-center justify-content-between">
|
||||
<div>
|
||||
{activeTab === "notes" && (
|
||||
<input
|
||||
type="search"
|
||||
className="form-control form-control-sm"
|
||||
placeholder="Search notes..."
|
||||
value={searchNote}
|
||||
onChange={(e) => setSearchNote(e.target.value)}
|
||||
/>
|
||||
)}
|
||||
<div className="mb-1 px-2 py-3">
|
||||
<div className="d-flex align-items-center justify-content-between">
|
||||
<div className="d-flex align-items-center gap-3">
|
||||
{activeTab === "notes" && (
|
||||
<input
|
||||
type="search"
|
||||
className="form-control form-control-sm"
|
||||
placeholder="Search notes..."
|
||||
value={searchNote}
|
||||
onChange={(e) => setSearchNote(e.target.value)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{activeTab === "contacts" && (
|
||||
<div className="d-flex align-items-center">
|
||||
<div className="d-flex gap-2 align-items-center">
|
||||
<input
|
||||
type="search"
|
||||
className="form-control form-control-sm"
|
||||
placeholder="Search contacts..."
|
||||
value={searchContact}
|
||||
onChange={(e) => setsearchContact(e.target.value)}
|
||||
/>
|
||||
<button
|
||||
className={`btn btn-xs ${
|
||||
!gridView ? "btn-primary" : "btn-outline-secondary"
|
||||
{activeTab === "contacts" && (
|
||||
<div className="d-flex align-items-center gap-3">
|
||||
<div className="d-flex gap-2 align-items-center">
|
||||
<input
|
||||
type="search"
|
||||
className="form-control form-control-sm"
|
||||
placeholder="Search contacts..."
|
||||
value={searchContact}
|
||||
onChange={(e) => setsearchContact(e.target.value)}
|
||||
/>
|
||||
<button
|
||||
className={`btn btn-sm p-1 ${
|
||||
!gridView ? "btn-primary" : "btn-outline-primary"
|
||||
}`}
|
||||
onClick={() => setGridView(false)}
|
||||
>
|
||||
<i className="bx bx-list-ul"></i>
|
||||
</button>
|
||||
onClick={() => setGridView(false)}
|
||||
>
|
||||
<i className="bx bx-list-ul"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
className={`btn btn-xs ${
|
||||
gridView ? "btn-primary" : "btn-outline-secondary"
|
||||
<button
|
||||
className={`btn btn-sm p-1 ${
|
||||
gridView ? " btn-primary" : " btn-outline-primary"
|
||||
}`}
|
||||
onClick={() => setGridView(true)}
|
||||
>
|
||||
<i className="bx bx-grid-alt"></i>
|
||||
</button>
|
||||
onClick={() => setGridView(true)}
|
||||
>
|
||||
<i className="bx bx-grid-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="form-check form-switch d-flex align-items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
role="switch"
|
||||
id="inactiveEmployeesCheckbox"
|
||||
checked={showActive}
|
||||
onChange={(e) => setShowActive(e.target.checked)}
|
||||
/>
|
||||
<label
|
||||
className="form-check-label ms-2"
|
||||
htmlFor="inactiveEmployeesCheckbox"
|
||||
>
|
||||
{showActive ? "Active" : "Inactive"} Contacts
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-check form-switch d-flex align-items-center ms-4">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
role="switch"
|
||||
id="inactiveEmployeesCheckbox"
|
||||
checked={showActive}
|
||||
onChange={(e) => setShowActive(e.target.checked)}
|
||||
/>
|
||||
<label
|
||||
className="form-check-label ms-2"
|
||||
htmlFor="inactiveEmployeesCheckbox"
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="btn-group">
|
||||
<button
|
||||
className="btn btn-sm btn-label-secondary dropdown-toggle"
|
||||
type="button"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<i className="bx bx-export me-2 bx-sm"></i>Export
|
||||
</button>
|
||||
<ul className="dropdown-menu">
|
||||
<li>
|
||||
<a
|
||||
className="dropdown-item cursor-pointer"
|
||||
onClick={() => handleExport("csv")}
|
||||
>
|
||||
{showActive ? "Active" : "Inactive" }
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<i className="bx bx-file me-1"></i> CSV
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -231,10 +252,18 @@ export default function DirectoryPage({ IsPage = true, projectId = null }) {
|
||||
<div>
|
||||
<Suspense fallback={<MainDirectoryPageSkeleton />}>
|
||||
{activeTab === "notes" && (
|
||||
<NotesPage projectId={projectId} searchText={searchNote} onExport={setNotesData} />
|
||||
<NotesPage
|
||||
projectId={projectId}
|
||||
searchText={searchNote}
|
||||
onExport={setNotesData}
|
||||
/>
|
||||
)}
|
||||
{activeTab === "contacts" && (
|
||||
<ContactsPage projectId={projectId} searchText={searchContact} onExport={setContactData} />
|
||||
<ContactsPage
|
||||
projectId={projectId}
|
||||
searchText={searchContact}
|
||||
onExport={setContactData}
|
||||
/>
|
||||
)}
|
||||
</Suspense>
|
||||
</div>
|
||||
@ -274,6 +303,19 @@ export default function DirectoryPage({ IsPage = true, projectId = null }) {
|
||||
/>
|
||||
</GlobalModel>
|
||||
)}
|
||||
|
||||
{deleteBucket.isOpen && (
|
||||
<ConfirmModal
|
||||
isOpen={deleteBucket.isOpen}
|
||||
type="delete"
|
||||
header="Delete Bucket"
|
||||
message="Are you sure you want delete?"
|
||||
onSubmit={handleDelete}
|
||||
onClose={() => setDeleteBucket({ isOpen: false, bucketId: null })}
|
||||
loading={Deleting}
|
||||
paramData={deleteBucket.bucketId}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</DirectoryContext.Provider>
|
||||
</>
|
||||
|
@ -299,7 +299,7 @@ nav.layout-navbar.navbar-active::after {
|
||||
color: #d3d4dc;
|
||||
}
|
||||
.landing-footer .footer-bottom {
|
||||
background-color: #282c3e;
|
||||
background-color: #f44336;
|
||||
}
|
||||
.landing-footer .footer-link {
|
||||
transition: all 0.2s ease-in-out;
|
||||
@ -312,6 +312,7 @@ nav.layout-navbar.navbar-active::after {
|
||||
padding-bottom: 1.3rem;
|
||||
border-top-left-radius: 1.75rem;
|
||||
border-top-right-radius: 1.75rem;
|
||||
background-color: #f44336;
|
||||
}
|
||||
@media (max-width: 767.98px) {
|
||||
.landing-footer .footer-top {
|
||||
|
@ -12,8 +12,7 @@ import "swiper/css";
|
||||
import "swiper/css/navigation";
|
||||
import SwaperSlideContent from "./SwaperSlideContent";
|
||||
import SwaperBlogContent from "./SwaperBlogContent";
|
||||
|
||||
|
||||
import SubscriptionPlans from "./SubscriptionPlans";
|
||||
|
||||
const swiperConfig = {
|
||||
spaceBetween: 30,
|
||||
@ -110,7 +109,7 @@ const LandingPage = () => {
|
||||
</a>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<a className="nav-link fw-medium" href="#landingReviews">
|
||||
<a className="nav-link fw-medium" href="#sectionBlog">
|
||||
Blogs
|
||||
</a>
|
||||
</li>
|
||||
@ -182,7 +181,7 @@ const LandingPage = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
<div
|
||||
id="heroDashboardAnimation"
|
||||
className="hero-animation-img mt-10"
|
||||
>
|
||||
@ -296,10 +295,7 @@ const LandingPage = () => {
|
||||
<div className="features-icon-wrapper row gx-0 gy-6 g-sm-12">
|
||||
<div className="col-lg-3 col-sm-4 text-center features-icon-box">
|
||||
<div className="text-center mb-4">
|
||||
<img
|
||||
src="/img/icons/laptop.svg"
|
||||
alt="laptop charging"
|
||||
/>
|
||||
<img src="/img/icons/laptop.svg" alt="laptop charging" />
|
||||
</div>
|
||||
<h5 className="mb-2">Project & Task Management</h5>
|
||||
<p className="features-icon-description">
|
||||
@ -309,10 +305,7 @@ const LandingPage = () => {
|
||||
</div>
|
||||
<div className="col-lg-3 col-sm-4 text-center features-icon-box">
|
||||
<div className="text-center mb-4">
|
||||
<img
|
||||
src="/img/icons/rocket.svg"
|
||||
alt="transition up"
|
||||
/>
|
||||
<img src="/img/icons/rocket.svg" alt="transition up" />
|
||||
</div>
|
||||
<h5 className="mb-2">Attendance & Leave Tracking</h5>
|
||||
<p className="features-icon-description">
|
||||
@ -332,10 +325,7 @@ const LandingPage = () => {
|
||||
</div>
|
||||
<div className="col-lg-3 col-sm-4 text-center features-icon-box">
|
||||
<div className="text-center mb-4">
|
||||
<img
|
||||
src="/img/icons/check.svg"
|
||||
alt="3d select solid"
|
||||
/>
|
||||
<img src="/img/icons/check.svg" alt="3d select solid" />
|
||||
</div>
|
||||
<h5 className="mb-2">Expense & Budget Tracking</h5>
|
||||
<p className="features-icon-description">
|
||||
@ -355,10 +345,7 @@ const LandingPage = () => {
|
||||
</div>
|
||||
<div className="col-lg-3 col-sm-4 text-center features-icon-box">
|
||||
<div className="text-center mb-4">
|
||||
<img
|
||||
src="/img/icons/keyboard.svg"
|
||||
alt="keyboard"
|
||||
/>
|
||||
<img src="/img/icons/keyboard.svg" alt="keyboard" />
|
||||
</div>
|
||||
<h5 className="mb-2">Document Management</h5>
|
||||
<p className="features-icon-description">
|
||||
@ -368,10 +355,7 @@ const LandingPage = () => {
|
||||
</div>
|
||||
<div className="col-lg-3 col-sm-4 text-center features-icon-box">
|
||||
<div className="text-center mb-4">
|
||||
<img
|
||||
src="/img/icons/keyboard.svg"
|
||||
alt="keyboard"
|
||||
/>
|
||||
<img src="/img/icons/keyboard.svg" alt="keyboard" />
|
||||
</div>
|
||||
<h5 className="mb-2">
|
||||
Service Provider & Subcontractor Tracking
|
||||
@ -383,10 +367,7 @@ const LandingPage = () => {
|
||||
</div>{" "}
|
||||
<div className="col-lg-3 col-sm-4 text-center features-icon-box">
|
||||
<div className="text-center mb-4">
|
||||
<img
|
||||
src="/img/icons/inventory.svg"
|
||||
alt="keyboard"
|
||||
/>
|
||||
<img src="/img/icons/inventory.svg" alt="keyboard" />
|
||||
</div>
|
||||
<h5 className="mb-2">Inventory Management</h5>
|
||||
<p className="features-icon-description">
|
||||
@ -396,10 +377,7 @@ const LandingPage = () => {
|
||||
</div>
|
||||
<div className="col-lg-3 col-sm-4 text-center features-icon-box">
|
||||
<div className="text-center mb-4">
|
||||
<img
|
||||
src="/img/icons/keyboard.svg"
|
||||
alt="keyboard"
|
||||
/>
|
||||
<img src="/img/icons/keyboard.svg" alt="keyboard" />
|
||||
</div>
|
||||
<h5 className="mb-2">Directory</h5>
|
||||
<p className="features-icon-description">
|
||||
@ -411,10 +389,11 @@ const LandingPage = () => {
|
||||
</section>
|
||||
{/* Useful features: End */}
|
||||
|
||||
{/* <!-- Real customers reviews: Start --> */}
|
||||
{/* <!-- Real blog/ case studies: Start --> */}
|
||||
<section
|
||||
id="landingReviews"
|
||||
id="sectionBlog"
|
||||
class="section-py bg-body landing-reviews pb-0"
|
||||
hidden
|
||||
>
|
||||
{/* <!-- What people say slider: Start --> */}
|
||||
<div class="container">
|
||||
@ -518,341 +497,8 @@ const LandingPage = () => {
|
||||
No matter which plan you choose, you’ll get access to powerful
|
||||
features. <strong>Choose the best plan to fit your needs.</strong>
|
||||
</p>
|
||||
<div className="text-center mb-8">
|
||||
<div className="position-relative d-inline-block pt-3 pt-md-0">
|
||||
<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>
|
||||
{/* <SubscriptionPlans/> */}
|
||||
<SubscriptionPlans />
|
||||
</div>
|
||||
</section>
|
||||
{/* Pricing plans: End */}
|
||||
@ -945,14 +591,14 @@ const LandingPage = () => {
|
||||
<span className="badge bg-label-primary heading">FAQ</span>
|
||||
</div>
|
||||
<h4 className="text-center mb-1">
|
||||
Frequently asked
|
||||
<span className="position-relative fw-extrabold z-1">
|
||||
questions
|
||||
<img
|
||||
Frequently Asked
|
||||
<span className="position-relative fw-extrabold z-1 ms-2">
|
||||
Questions
|
||||
{/* <img
|
||||
src="/img/icons/section-title-icon.png"
|
||||
alt="laptop charging"
|
||||
className="section-title-img position-absolute object-fit-contain bottom-0 z-n1"
|
||||
/>
|
||||
/> */}
|
||||
</span>
|
||||
</h4>
|
||||
<p className="text-center mb-12 pb-md-4">
|
||||
@ -990,12 +636,12 @@ const LandingPage = () => {
|
||||
className="accordion-collapse collapse"
|
||||
data-bs-parent="#accordionExample"
|
||||
>
|
||||
<div className="accordion-body">
|
||||
Lemon drops chocolate cake gummies carrot cake chupa
|
||||
chups muffin topping. Sesame snaps icing marzipan gummi
|
||||
bears macaroon dragée danish caramels powder. Bear claw
|
||||
dragée pastry topping soufflé. Wafer gummi bears
|
||||
marshmallow pastry pie.
|
||||
<div className="accordion-body text-start">
|
||||
A smart Project Management System designed to bring
|
||||
teams, tasks, and timelines together in one place. With
|
||||
AI-driven insights, role-based access, and seamless
|
||||
reporting, it empowers organizations to deliver projects
|
||||
faster and smarter.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1018,12 +664,13 @@ const LandingPage = () => {
|
||||
aria-labelledby="headingTwo"
|
||||
data-bs-parent="#accordionExample"
|
||||
>
|
||||
<div className="accordion-body">
|
||||
Dessert ice cream donut oat cake jelly-o pie sugar plum
|
||||
cheesecake. Bear claw dragée oat cake dragée ice cream
|
||||
halvah tootsie roll. Danish cake oat cake pie macaroon
|
||||
tart donut gummies. Jelly beans candy canes carrot cake.
|
||||
Fruitcake chocolate chupa chups.
|
||||
<div className="accordion-body text-start">
|
||||
Yes, you have full flexibility to manage your
|
||||
subscription. You can upgrade to a higher plan to unlock
|
||||
more features, downgrade to a smaller plan if your needs
|
||||
change, or cancel your subscription anytime. Plan
|
||||
changes take effect instantly, and billing adjustments
|
||||
are applied on a pro-rated basis.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1046,17 +693,16 @@ const LandingPage = () => {
|
||||
aria-labelledby="headingThree"
|
||||
data-bs-parent="#accordionExample"
|
||||
>
|
||||
<div className="accordion-body">
|
||||
Regular license can be used for end products that do not
|
||||
charge users for access or service(access is free and
|
||||
there will be no monthly subscription fee). Single
|
||||
regular license can be used for single end product and
|
||||
end product can be used by you or your client. If you
|
||||
want to sell end product to multiple clients then you
|
||||
will need to purchase separate license for each client.
|
||||
The same rule applies if you want to use the same end
|
||||
product on multiple domains(unique setup). For more info
|
||||
on regular license you can check official description.
|
||||
<div className="accordion-body text-start">
|
||||
Security is at the core of Marco PMS. We use
|
||||
industry-standard encryption (SSL/TLS) to protect data
|
||||
in transit and advanced encryption to safeguard data at
|
||||
rest. Role-based access controls ensure that only
|
||||
authorized users can access sensitive information. Our
|
||||
system is hosted on secure, cloud-ready infrastructure
|
||||
with regular backups, monitoring, and compliance with
|
||||
best practices to keep your data safe and available at
|
||||
all times.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1079,12 +725,12 @@ const LandingPage = () => {
|
||||
aria-labelledby="headingFour"
|
||||
data-bs-parent="#accordionExample"
|
||||
>
|
||||
<div className="accordion-body">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit.
|
||||
Nobis et aliquid quaerat possimus maxime! Mollitia
|
||||
reprehenderit neque repellat deleniti delectus
|
||||
architecto dolorum maxime, blanditiis earum ea, incidunt
|
||||
quam possimus cumque.
|
||||
<div className="accordion-body text-start">
|
||||
You can reach our support team anytime through the
|
||||
in-app help center, email, or live chat. We also provide
|
||||
a detailed knowledge base and FAQs to guide you through
|
||||
common queries. For personalized assistance, our support
|
||||
specialists are always ready to help you.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1107,15 +753,47 @@ const LandingPage = () => {
|
||||
aria-labelledby="headingFive"
|
||||
data-bs-parent="#accordionExample"
|
||||
>
|
||||
<div className="accordion-body">
|
||||
Lorem ipsum dolor sit amet consectetur, adipisicing
|
||||
elit. Sequi molestias exercitationem ab cum nemo facere
|
||||
voluptates veritatis quia, eveniet veniam at et
|
||||
repudiandae mollitia ipsam quasi labore enim architecto
|
||||
non!
|
||||
<div className="accordion-body text-start">
|
||||
Marco PMS operate under a proprietary license combined
|
||||
with a subscription model. This means customers don’t
|
||||
own the software but are granted the right to access and
|
||||
use it through the cloud under our Terms of Service.
|
||||
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 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>
|
||||
@ -1138,19 +816,26 @@ const LandingPage = () => {
|
||||
</div>
|
||||
|
||||
<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="mt-5">
|
||||
{" "}
|
||||
<h4 className="text-start mb-1">
|
||||
<span className="position-relative fw-extrabold z-1">
|
||||
Let's work
|
||||
<img
|
||||
Let's Work
|
||||
{/* <img
|
||||
src="/img/icons/section-title-icon.png"
|
||||
alt="laptop charging"
|
||||
className="section-title-img position-absolute object-fit-contain bottom-0 z-n1"
|
||||
/>
|
||||
/> */}
|
||||
</span>
|
||||
together
|
||||
Together
|
||||
</h4>
|
||||
<p className="text-start pb-md-4">
|
||||
Any question or remark? just write us a message
|
||||
@ -1186,7 +871,7 @@ const LandingPage = () => {
|
||||
href="tel:+1234-568-963"
|
||||
className="text-heading"
|
||||
>
|
||||
+1234 568 963
|
||||
+91 70288 83755
|
||||
</a>
|
||||
</h6>
|
||||
</div>
|
||||
@ -1195,15 +880,15 @@ const LandingPage = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-10">
|
||||
<h4 className="cta-title text-primary mb-1">
|
||||
<h5 className="cta-title text-primary mb-1">
|
||||
Ready to Get Started?
|
||||
</h4>
|
||||
<h5 className="text-body mb-8">
|
||||
Start your project with a 14-day free trial
|
||||
</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
|
||||
</a>{" "}
|
||||
</a>{" "} */}
|
||||
<a
|
||||
href="/auth/reqest/demo"
|
||||
className="btn btn-lg btn-primary"
|
||||
@ -1212,13 +897,6 @@ const LandingPage = () => {
|
||||
</a>
|
||||
</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>
|
||||
</section>
|
||||
@ -1378,7 +1056,10 @@ const LandingPage = () => {
|
||||
|
||||
{/* Footer: Start */}
|
||||
<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
|
||||
src="/img/backgrounds/footer-bg.png"
|
||||
alt="footer bg"
|
||||
@ -1515,11 +1196,8 @@ const LandingPage = () => {
|
||||
|
||||
<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>
|
||||
<a href="javascript:void(0);">
|
||||
<img
|
||||
src="/img/icons/apple-icon.png"
|
||||
alt="apple icon"
|
||||
/>
|
||||
<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"
|
||||
@ -1534,9 +1212,28 @@ const LandingPage = () => {
|
||||
</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="mb-2 mb-md-0">
|
||||
<div className="col-lg-4 col-md-4 d-flex align-items-center justify-content-start">
|
||||
<a
|
||||
href="https://www.facebook.com/marcoaiot/"
|
||||
className="me-4"
|
||||
target="_blank"
|
||||
>
|
||||
<img src="/img/icons/facebook.svg" alt="facebook icon" />
|
||||
</a>
|
||||
<a
|
||||
href="https://twitter.com/marcoaiot"
|
||||
className="me-4"
|
||||
target="_blank"
|
||||
>
|
||||
<img src="/img/icons/twitter.svg" alt="twitter icon" />
|
||||
</a>
|
||||
<a href="https://www.instagram.com/marcoaiot/" target="_blank">
|
||||
<img src="/img/icons/instagram.svg" alt="google icon" />
|
||||
</a>
|
||||
</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>
|
||||
@ -1548,31 +1245,19 @@ const LandingPage = () => {
|
||||
Marco AIoT Technologies Pvt. Ltd.,
|
||||
</a>
|
||||
</div>
|
||||
<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://www.facebook.com/marcoaiot/"
|
||||
className="me-4"
|
||||
href="https://play.google.com/store/apps/details?id=com.marco.aiotstage&pcampaignid=web_share"
|
||||
target="_blank"
|
||||
>
|
||||
<img
|
||||
src="/img/icons/facebook.svg"
|
||||
alt="facebook icon"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://twitter.com/marcoaiot"
|
||||
className="me-4"
|
||||
target="_blank"
|
||||
>
|
||||
<img
|
||||
src="/img/icons/twitter.svg"
|
||||
alt="twitter icon"
|
||||
/>
|
||||
</a>
|
||||
<a href="https://www.instagram.com/marcoaiot/" target="_blank">
|
||||
<img
|
||||
src="/img/icons/instagram.svg"
|
||||
alt="google icon"
|
||||
src="/img/icons/google-play-icon.png"
|
||||
alt="google play icon"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
|
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;
|
@ -121,66 +121,67 @@ const TenantPage = () => {
|
||||
{ label: "Tenant", link: null },
|
||||
]}
|
||||
/>
|
||||
<div className="card text-center my-4 p-5 pb-10">
|
||||
{/* Super Tenant Actions */}
|
||||
{isSuperTenant && (
|
||||
<div className="p-0">
|
||||
<div className="row align-items-center">
|
||||
{/* Search */}
|
||||
<div className="col-6 col-md-6 col-lg-3 mb-md-0">
|
||||
<input
|
||||
type="search"
|
||||
value={searchText}
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
className="form-control form-control"
|
||||
placeholder="Search Tenant"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Super Tenant Actions */}
|
||||
{isSuperTenant && (
|
||||
<div className="card d-flex p-2">
|
||||
<div className="row align-items-center">
|
||||
{/* Search */}
|
||||
<div className="col-6 col-md-6 col-lg-3 mb-md-0">
|
||||
<input
|
||||
type="search"
|
||||
value={searchText}
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
className="form-control form-control-sm"
|
||||
placeholder="Search Tenant"
|
||||
/>
|
||||
</div>
|
||||
{/* Actions */}
|
||||
<div className="col-6 col-md-6 col-lg-9 text-end">
|
||||
<span
|
||||
className="text-tiny text-muted p-1 border-0 bg-none lead mx-3 cursor-pointer"
|
||||
disabled={isRefetching}
|
||||
onClick={() => refetchFn && refetchFn()}
|
||||
>
|
||||
Refresh{" "}
|
||||
<i
|
||||
className={`bx bx-refresh ms-1 ${
|
||||
isRefetching ? "bx-spin" : ""
|
||||
}`}
|
||||
></i>
|
||||
</span>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="col-6 col-md-6 col-lg-9 text-end">
|
||||
<span
|
||||
className="text-tiny text-muted p-1 border-0 bg-none lead mx-3 cursor-pointer"
|
||||
disabled={isRefetching}
|
||||
onClick={() => refetchFn && refetchFn()}
|
||||
>
|
||||
Refresh{" "}
|
||||
<i
|
||||
className={`bx bx-refresh ms-1 ${
|
||||
isRefetching ? "bx-spin" : ""
|
||||
}`}
|
||||
></i>
|
||||
</span>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
title="Add New Tenant"
|
||||
className="p-1 bg-primary rounded-circle cursor-pointer"
|
||||
onClick={handleNewTenant}
|
||||
>
|
||||
<i className="bx bx-plus fs-4 text-white"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
title="Add New Tenant"
|
||||
className="p-1 bg-primary rounded-circle cursor-pointer"
|
||||
onClick={handleNewTenant}
|
||||
>
|
||||
<i className="bx bx-plus fs-4 text-white"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
|
||||
{/* Tenant List or Access Denied */}
|
||||
{isSuperTenant ? (
|
||||
<TenantsList
|
||||
filters={filters}
|
||||
searchText={debouncedSearch}
|
||||
setIsRefetching={setIsRefetching}
|
||||
setRefetchFn={setRefetchFn}
|
||||
/>
|
||||
) : !isSelfTenant ? (
|
||||
<div className="card text-center my-4 p-2">
|
||||
<i className="fa-solid fa-triangle-exclamation fs-5"></i>
|
||||
<p>
|
||||
Access Denied: You don't have permission to perform this action!
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
{/* Tenant List or Access Denied */}
|
||||
{isSuperTenant ? (
|
||||
<TenantsList
|
||||
filters={filters}
|
||||
searchText={debouncedSearch}
|
||||
setIsRefetching={setIsRefetching}
|
||||
setRefetchFn={setRefetchFn}
|
||||
/>
|
||||
) : !isSelfTenant ? (
|
||||
<div className="text-center my-4 p-2">
|
||||
<i className="fa-solid fa-triangle-exclamation fs-5"></i>
|
||||
<p>
|
||||
Access Denied: You don't have permission to perform this action!
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</TenantContext.Provider>
|
||||
);
|
||||
|
@ -136,7 +136,7 @@ const LoginPage = () => {
|
||||
)}
|
||||
</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">
|
||||
Password
|
||||
</label>
|
||||
@ -146,7 +146,8 @@ const LoginPage = () => {
|
||||
type={hidepass ? "password" : "text"}
|
||||
autoComplete="new-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"
|
||||
{...register("password")}
|
||||
placeholder="••••••••••••"
|
||||
@ -155,7 +156,7 @@ const LoginPage = () => {
|
||||
<span className="input-group-text cursor-pointer border-start-0">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-link p-0"
|
||||
className="btn btn-link-secondary p-0"
|
||||
onClick={() => setHidepass(!hidepass)}
|
||||
>
|
||||
{hidepass ? (
|
||||
@ -166,8 +167,16 @@ const LoginPage = () => {
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* ✅ Error message */}
|
||||
{errors.password && (
|
||||
<div className="invalid-feedback text-start" style={{ fontSize: "12px" }}>
|
||||
{errors.password.message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
{/* Remember Me + Forgot Password */}
|
||||
<div className="mb-3 d-flex justify-content-between align-items-center">
|
||||
<div className="form-check">
|
||||
|
@ -176,10 +176,12 @@ const EmployeeList = () => {
|
||||
useEffect(() => {
|
||||
if (!loading && Array.isArray(employees)) {
|
||||
const sorted = [...employees].sort((a, b) => {
|
||||
const nameA = `${a.firstName || ""}${a.middleName || ""}${a.lastName || ""
|
||||
}`.toLowerCase();
|
||||
const nameB = `${b.firstName || ""}${b.middleName || ""}${b.lastName || ""
|
||||
}`.toLowerCase();
|
||||
const nameA = `${a.firstName || ""}${a.middleName || ""}${
|
||||
a.lastName || ""
|
||||
}`.toLowerCase();
|
||||
const nameB = `${b.firstName || ""}${b.middleName || ""}${
|
||||
b.lastName || ""
|
||||
}`.toLowerCase();
|
||||
return nameA?.localeCompare(nameB);
|
||||
});
|
||||
|
||||
@ -266,8 +268,9 @@ const EmployeeList = () => {
|
||||
? "Suspend Employee"
|
||||
: "Reactivate Employee"
|
||||
}
|
||||
message={`Are you sure you want to ${selectedEmpFordelete?.isActive ? "suspend" : "reactivate"
|
||||
} this employee?`}
|
||||
message={`Are you sure you want to ${
|
||||
selectedEmpFordelete?.isActive ? "suspend" : "reactivate"
|
||||
} this employee?`}
|
||||
onSubmit={(id) =>
|
||||
suspendEmployee({
|
||||
employeeId: id,
|
||||
@ -291,11 +294,11 @@ const EmployeeList = () => {
|
||||
{ViewTeamMember ? (
|
||||
// <div className="row">
|
||||
<div className="card p-1">
|
||||
<div className="card-datatable table-responsive pt-2">
|
||||
<div className="card-datatable table-responsive pt-5 mx-5 py-10">
|
||||
<div
|
||||
id="DataTables_Table_0_wrapper"
|
||||
className="dataTables_wrapper dt-bootstrap5 no-footer"
|
||||
style={{ width: "98%" }}
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
<div className="d-flex flex-wrap align-items-center justify-content-between gap-3 mb-3">
|
||||
{/* Switches: All Employees + Inactive */}
|
||||
@ -315,7 +318,7 @@ const EmployeeList = () => {
|
||||
className="form-check-label ms-0"
|
||||
htmlFor="allEmployeesCheckbox"
|
||||
>
|
||||
All Employees
|
||||
Show All Employees
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
@ -351,7 +354,7 @@ const EmployeeList = () => {
|
||||
value={searchText}
|
||||
onChange={handleSearch}
|
||||
className="form-control form-control-sm"
|
||||
placeholder="Search User"
|
||||
placeholder="Search Employee"
|
||||
aria-controls="DataTables_Table_0"
|
||||
/>
|
||||
</label>
|
||||
@ -499,8 +502,9 @@ const EmployeeList = () => {
|
||||
Status
|
||||
</th>
|
||||
<th
|
||||
className={`sorting_disabled ${!Manage_Employee && "d-none"
|
||||
}`}
|
||||
className={`sorting_disabled ${
|
||||
!Manage_Employee && "d-none"
|
||||
}`}
|
||||
rowSpan="1"
|
||||
colSpan="1"
|
||||
style={{ width: "50px" }}
|
||||
@ -520,9 +524,9 @@ const EmployeeList = () => {
|
||||
)}
|
||||
{/* Conditional messages for no data or no search results */}
|
||||
{!loading &&
|
||||
displayData?.length === 0 &&
|
||||
searchText &&
|
||||
!showAllEmployees ? (
|
||||
displayData?.length === 0 &&
|
||||
searchText &&
|
||||
!showAllEmployees ? (
|
||||
<tr>
|
||||
<td colSpan={8}>
|
||||
<small className="muted">
|
||||
@ -532,8 +536,8 @@ const EmployeeList = () => {
|
||||
</tr>
|
||||
) : null}
|
||||
{!loading &&
|
||||
displayData?.length === 0 &&
|
||||
(!searchText || showAllEmployees) ? (
|
||||
displayData?.length === 0 &&
|
||||
(!searchText || showAllEmployees) ? (
|
||||
<tr>
|
||||
<td
|
||||
colSpan={8}
|
||||
@ -578,7 +582,7 @@ const EmployeeList = () => {
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-truncate text-italic">
|
||||
NA
|
||||
-
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
@ -627,7 +631,9 @@ const EmployeeList = () => {
|
||||
<div className="dropdown-menu dropdown-menu-end">
|
||||
{/* View always visible */}
|
||||
<button
|
||||
onClick={() => navigate(`/employee/${item.id}`)}
|
||||
onClick={() =>
|
||||
navigate(`/employee/${item.id}`)
|
||||
}
|
||||
className="dropdown-item py-1"
|
||||
>
|
||||
<i className="bx bx-detail bx-sm"></i> View
|
||||
@ -638,9 +644,12 @@ const EmployeeList = () => {
|
||||
<>
|
||||
<button
|
||||
className="dropdown-item py-1"
|
||||
onClick={() => handleEmployeeModel(item.id)}
|
||||
onClick={() =>
|
||||
handleEmployeeModel(item.id)
|
||||
}
|
||||
>
|
||||
<i className="bx bx-edit bx-sm"></i> Edit
|
||||
<i className="bx bx-edit bx-sm"></i>{" "}
|
||||
Edit
|
||||
</button>
|
||||
|
||||
{/* Suspend only when active */}
|
||||
@ -649,7 +658,8 @@ const EmployeeList = () => {
|
||||
className="dropdown-item py-1"
|
||||
onClick={() => handleOpenDelete(item)}
|
||||
>
|
||||
<i className="bx bx-task-x bx-sm"></i> Suspend
|
||||
<i className="bx bx-task-x bx-sm"></i>{" "}
|
||||
Suspend
|
||||
</button>
|
||||
)}
|
||||
|
||||
@ -658,11 +668,13 @@ const EmployeeList = () => {
|
||||
type="button"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#managerole-modal"
|
||||
onClick={() => setEmpForManageRole(item.id)}
|
||||
onClick={() =>
|
||||
setEmpForManageRole(item.id)
|
||||
}
|
||||
>
|
||||
<i className="bx bx-cog bx-sm"></i> Manage Role
|
||||
<i className="bx bx-cog bx-sm"></i>{" "}
|
||||
Manage Role
|
||||
</button>
|
||||
|
||||
</>
|
||||
)}
|
||||
|
||||
@ -672,7 +684,8 @@ const EmployeeList = () => {
|
||||
className="dropdown-item py-1"
|
||||
onClick={() => handleOpenDelete(item)}
|
||||
>
|
||||
<i className="bx bx-refresh bx-sm me-1"></i> Re-activate
|
||||
<i className="bx bx-refresh bx-sm me-1"></i>{" "}
|
||||
Re-activate
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@ -691,8 +704,9 @@ const EmployeeList = () => {
|
||||
<nav aria-label="Page">
|
||||
<ul className="pagination pagination-sm justify-content-end py-1">
|
||||
<li
|
||||
className={`page-item ${currentPage === 1 ? "disabled" : ""
|
||||
}`}
|
||||
className={`page-item ${
|
||||
currentPage === 1 ? "disabled" : ""
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
className="page-link btn-xs"
|
||||
@ -705,8 +719,9 @@ const EmployeeList = () => {
|
||||
{[...Array(totalPages)]?.map((_, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className={`page-item ${currentPage === index + 1 ? "active" : ""
|
||||
}`}
|
||||
className={`page-item ${
|
||||
currentPage === index + 1 ? "active" : ""
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
className="page-link"
|
||||
@ -718,8 +733,9 @@ const EmployeeList = () => {
|
||||
))}
|
||||
|
||||
<li
|
||||
className={`page-item ${currentPage === totalPages ? "disabled" : ""
|
||||
}`}
|
||||
className={`page-item ${
|
||||
currentPage === totalPages ? "disabled" : ""
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
className="page-link"
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useSearchParams, useParams, useNavigate } from "react-router-dom";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
import {
|
||||
useEmployee,
|
||||
@ -23,22 +23,33 @@ import EmpBanner from "../../components/Employee/EmpBanner";
|
||||
import EmpDashboard from "../../components/Employee/EmpDashboard";
|
||||
import EmpDocuments from "../../components/Employee/EmpDocuments";
|
||||
import EmpActivities from "../../components/Employee/EmpActivities";
|
||||
import { setProjectId } from "../../slices/localVariablesSlice";
|
||||
|
||||
const EmployeeProfile = () => {
|
||||
const { profile } = useProfile();
|
||||
|
||||
const projectID = useSelector((store) => store.localVariables.projectId);
|
||||
|
||||
const { employeeId } = useParams();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [SearchParams] = useSearchParams();
|
||||
const tab = SearchParams.get("for");
|
||||
const [activePill, setActivePill] = useState(tab || "profile");
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const {data:currentEmployee,isLoading,isError,error} = useEmployee(employeeId)
|
||||
const {
|
||||
data: currentEmployee,
|
||||
isLoading,
|
||||
isError,
|
||||
error,
|
||||
} = useEmployee(employeeId);
|
||||
const handlePillClick = (pillKey) => {
|
||||
setActivePill(pillKey);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setProjectId(null));
|
||||
}, [projectID]);
|
||||
|
||||
const navigate = useNavigate();
|
||||
const renderContent = () => {
|
||||
@ -87,7 +98,7 @@ const EmployeeProfile = () => {
|
||||
if (isLoading) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
if(isError) return <div >{error.message}</div>
|
||||
if (isError) return <div>{error.message}</div>;
|
||||
return (
|
||||
<>
|
||||
<div className="container-fluid">
|
||||
@ -115,11 +126,11 @@ const EmployeeProfile = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className=" ">{renderContent()}</div>
|
||||
<div className=" ">{renderContent()}</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmployeeProfile;
|
||||
export default EmployeeProfile;
|
||||
|
@ -5,7 +5,10 @@ import Breadcrumb from "../../components/common/Breadcrumb";
|
||||
import MasterModal from "../../components/master/MasterModal";
|
||||
import ConfirmModal from "../../components/common/ConfirmModal";
|
||||
import MasterTable from "./MasterTable";
|
||||
import useMaster, { useDeleteMasterItem, useMasterMenu } from "../../hooks/masterHook/useMaster";
|
||||
import useMaster, {
|
||||
useDeleteMasterItem,
|
||||
useMasterMenu,
|
||||
} from "../../hooks/masterHook/useMaster";
|
||||
import { changeMaster } from "../../slices/localVariablesSlice";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
import { MANAGE_MASTER } from "../../utils/constants";
|
||||
@ -14,11 +17,22 @@ import GlobalModel from "../../components/common/GlobalModel";
|
||||
const MasterPage = () => {
|
||||
const dispatch = useDispatch();
|
||||
const queryClient = useQueryClient();
|
||||
const selectedMaster = useSelector((store) => store.localVariables.selectedMaster);
|
||||
const selectedMaster = useSelector(
|
||||
(store) => store.localVariables.selectedMaster
|
||||
);
|
||||
const hasMasterPermission = useHasUserPermission(MANAGE_MASTER);
|
||||
|
||||
const { data: menuData, isLoading: menuLoading, isError: menuErrorFlag, error: menuError } = useMasterMenu();
|
||||
const { data: masterData = [], loading, isError: isMasterError } = useMaster();
|
||||
const {
|
||||
data: menuData,
|
||||
isLoading: menuLoading,
|
||||
isError: menuErrorFlag,
|
||||
error: menuError,
|
||||
} = useMasterMenu();
|
||||
const {
|
||||
data: masterData = [],
|
||||
loading,
|
||||
isError: isMasterError,
|
||||
} = useMaster();
|
||||
const { mutate: DeleteMaster, isPending: isDeleting } = useDeleteMasterItem();
|
||||
|
||||
const [modalConfig, setModalConfig] = useState(null);
|
||||
@ -26,7 +40,8 @@ const MasterPage = () => {
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
|
||||
const displayData = useMemo(() => {
|
||||
const dataSource = queryClient.getQueryData(["masterData", selectedMaster]) || masterData;
|
||||
const dataSource =
|
||||
queryClient.getQueryData(["masterData", selectedMaster]) || masterData;
|
||||
if (!searchTerm) return dataSource;
|
||||
return dataSource.filter((item) =>
|
||||
Object.values(item).some((val) =>
|
||||
@ -37,7 +52,10 @@ const MasterPage = () => {
|
||||
|
||||
const columns = useMemo(() => {
|
||||
if (!displayData.length) return [];
|
||||
return Object.keys(displayData[0]).map((key) => ({ key, label: key.toUpperCase() }));
|
||||
return Object.keys(displayData[0]).map((key) => ({
|
||||
key,
|
||||
label: key.toUpperCase(),
|
||||
}));
|
||||
}, [displayData]);
|
||||
|
||||
const handleModalData = (type, item = null, masterType = selectedMaster) => {
|
||||
@ -47,18 +65,24 @@ const MasterPage = () => {
|
||||
|
||||
const handleDeleteSubmit = () => {
|
||||
if (!deleteData) return;
|
||||
DeleteMaster({ masterType: deleteData.masterType, item: deleteData.item }, {
|
||||
onSuccess: () => setDeleteData(null),
|
||||
});
|
||||
DeleteMaster(
|
||||
{ masterType: deleteData.masterType, item: deleteData.item },
|
||||
{
|
||||
onSuccess: () => setDeleteData(null),
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
if (menuErrorFlag || isMasterError)
|
||||
return (
|
||||
<div className="d-flex flex-column align-items-center justify-content-center py-5">
|
||||
<h4 className="mb-3">
|
||||
<i className="fa-solid fa-triangle-exclamation fs-5" /> Oops, an error occurred
|
||||
<i className="fa-solid fa-triangle-exclamation fs-5" /> Oops, an error
|
||||
occurred
|
||||
</h4>
|
||||
<p className="text-muted">{menuError?.message || "Error fetching master data"}</p>
|
||||
<p className="text-muted">
|
||||
{menuError?.message || "Error fetching master data"}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -66,11 +90,20 @@ const MasterPage = () => {
|
||||
<>
|
||||
{modalConfig && (
|
||||
<GlobalModel
|
||||
size={["Application Role", "Edit-Application Role"].includes(modalConfig.masterType) ? "lg" : "md"}
|
||||
size={
|
||||
["Application Role", "Edit-Application Role"].includes(
|
||||
modalConfig.masterType
|
||||
)
|
||||
? "lg"
|
||||
: "md"
|
||||
}
|
||||
isOpen={!!modalConfig}
|
||||
closeModal={() => setModalConfig(null)}
|
||||
>
|
||||
<MasterModal modaldata={modalConfig} closeModal={() => setModalConfig(null)} />
|
||||
<MasterModal
|
||||
modaldata={modalConfig}
|
||||
closeModal={() => setModalConfig(null)}
|
||||
/>
|
||||
</GlobalModel>
|
||||
)}
|
||||
|
||||
@ -85,40 +118,58 @@ const MasterPage = () => {
|
||||
/>
|
||||
|
||||
<div className="container-fluid">
|
||||
<Breadcrumb data={[{ label: "Home", link: "/dashboard" }, { label: "Masters" }]} />
|
||||
<Breadcrumb
|
||||
data={[{ label: "Home", link: "/dashboard" }, { label: "Masters" }]}
|
||||
/>
|
||||
|
||||
<div className="row">
|
||||
<div className="card">
|
||||
<div className="card-datatable table-responsive py-4">
|
||||
<div
|
||||
className="card-datatable table-responsive py-10 mx-5 "
|
||||
style={{ overflow: "hidden" }}
|
||||
>
|
||||
<div className="row mb-2">
|
||||
<div className="col-md-3 col-sm-6">
|
||||
<select
|
||||
className="form-select py-1 px-2"
|
||||
style={{ fontSize: "0.875rem", height: "32px", width: "190px" }}
|
||||
style={{
|
||||
fontSize: "0.875rem",
|
||||
height: "32px",
|
||||
width: "190px",
|
||||
}}
|
||||
value={selectedMaster}
|
||||
onChange={(e) => dispatch(changeMaster(e.target.value))}
|
||||
>
|
||||
{menuLoading ? (
|
||||
<option value="">Loading...</option>
|
||||
) : (
|
||||
menuData?.map((item) => <option key={item.id} value={item.name}>{item.name}</option>)
|
||||
menuData?.map((item) => (
|
||||
<option key={item.id} value={item.name}>
|
||||
{item.name}
|
||||
</option>
|
||||
))
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
<div className="col-md-9 col-sm-6 d-flex justify-content-end align-items-center gap-2">
|
||||
<div className="w-25"><input
|
||||
type="search"
|
||||
className="form-control form-control-sm"
|
||||
placeholder="Search"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/></div>
|
||||
<div className="w-25">
|
||||
<input
|
||||
type="search"
|
||||
className="form-control form-control-sm"
|
||||
placeholder="Search"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{hasMasterPermission && (
|
||||
<button
|
||||
className="btn btn-sm btn-primary"
|
||||
onClick={() => handleModalData(selectedMaster, null, selectedMaster)}
|
||||
onClick={() =>
|
||||
handleModalData(selectedMaster, null, selectedMaster)
|
||||
}
|
||||
>
|
||||
<i className="bx bx-plus-circle me-2"></i>Add {selectedMaster}
|
||||
<i className="bx bx-plus-circle me-2"></i>Add{" "}
|
||||
{selectedMaster}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
@ -179,8 +179,9 @@ const MasterTable = ({ data, columns, loading, handleModalData }) => {
|
||||
aria-label="Delete"
|
||||
type="button"
|
||||
className="btn p-0 dropdown-toggle hide-arrow"
|
||||
onClick={() => handleModalData("delete", item, selectedMaster)}
|
||||
|
||||
onClick={() =>
|
||||
handleModalData("delete", item, selectedMaster)
|
||||
}
|
||||
>
|
||||
<i className="bx bx-trash me-1 text-danger"></i>
|
||||
</button>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { useSelector, useDispatch } from "react-redux"; // Import useSelector
|
||||
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 AboutProject from "../../components/Project/AboutProject";
|
||||
@ -9,56 +10,43 @@ import ProjectInfra from "../../components/Project/ProjectInfra";
|
||||
import Loader from "../../components/common/Loader";
|
||||
import WorkPlan from "../../components/Project/WorkPlan";
|
||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||
import {
|
||||
cacheData,
|
||||
clearCacheKey,
|
||||
getCachedData,
|
||||
useSelectedProject,
|
||||
} from "../../slices/apiDataManager";
|
||||
import "./ProjectDetails.css";
|
||||
import { useProjectDetails } from "../../hooks/useProjects";
|
||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||
import { useProjectDetails, useProjectName } from "../../hooks/useProjects";
|
||||
import { ComingSoonPage } from "../Misc/ComingSoonPage";
|
||||
import eventBus from "../../services/eventBus";
|
||||
import ProjectProgressChart from "../../components/Dashboard/ProjectProgressChart";
|
||||
import { useProjectName } from "../../hooks/useProjects";
|
||||
import AttendanceOverview from "../../components/Dashboard/AttendanceChart";
|
||||
import { setProjectId } from "../../slices/localVariablesSlice";
|
||||
import ProjectDocument from "../../components/Project/ProjectDocuments";
|
||||
import ProjectDocuments from "../../components/Project/ProjectDocuments";
|
||||
import ProjectSetting from "../../components/Project/ProjectSetting";
|
||||
import DirectoryPage from "../Directory/DirectoryPage";
|
||||
import { useProjectAccess } from "../../hooks/useProjectAccess"; // ✅ new
|
||||
|
||||
import "./ProjectDetails.css";
|
||||
|
||||
const ProjectDetails = () => {
|
||||
|
||||
const projectId = useSelectedProject()
|
||||
|
||||
const { projectNames, fetchData } = useProjectName();
|
||||
const projectId = useSelectedProject();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { projectNames } = useProjectName();
|
||||
const { projects_Details, loading: projectLoading, refetch } =
|
||||
useProjectDetails(projectId);
|
||||
|
||||
const { canView, loading: permsLoading } = useProjectAccess(projectId);
|
||||
|
||||
useEffect(() => {
|
||||
if (projectId == null) {
|
||||
dispatch(setProjectId(projectNames[0]?.id));
|
||||
if (!projectId && projectNames.length > 0) {
|
||||
dispatch(setProjectId(projectNames[0].id));
|
||||
}
|
||||
}, [projectNames]);
|
||||
}, [projectId, projectNames, dispatch]);
|
||||
|
||||
const {
|
||||
projects_Details,
|
||||
loading: projectLoading,
|
||||
error: projectError,
|
||||
refetch,
|
||||
} = useProjectDetails(projectId);
|
||||
|
||||
// const [activePill, setActivePill] = useState("profile");
|
||||
const [activePill, setActivePill] = useState(() => {
|
||||
return localStorage.getItem("lastActiveProjectTab") || "profile";
|
||||
});
|
||||
const [activePill, setActivePill] = useState(
|
||||
localStorage.getItem("lastActiveProjectTab") || "profile"
|
||||
);
|
||||
|
||||
const handler = useCallback(
|
||||
(msg) => {
|
||||
if (
|
||||
msg.keyword === "Update_Project" &&
|
||||
projects_Details?.id === msg.response.id
|
||||
) {
|
||||
if (msg.keyword === "Update_Project" && projects_Details?.id === msg.response.id) {
|
||||
refetch();
|
||||
}
|
||||
},
|
||||
@ -72,69 +60,42 @@ const ProjectDetails = () => {
|
||||
|
||||
const handlePillClick = (pillKey) => {
|
||||
setActivePill(pillKey);
|
||||
localStorage.setItem("lastActiveProjectTab", pillKey); // ✅ Save to localStorage
|
||||
localStorage.setItem("lastActiveProjectTab", pillKey);
|
||||
};
|
||||
|
||||
const renderContent = () => {
|
||||
if (projectLoading || !projects_Details) return <Loader />;
|
||||
if (projectLoading || permsLoading || !projects_Details) {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
const renderContent = () => {
|
||||
switch (activePill) {
|
||||
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 (
|
||||
<div className="row">
|
||||
<div className="col-lg-12">
|
||||
<Teams />
|
||||
<div className="col-lg-4 col-md-5 mt-2">
|
||||
<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 <Teams />;
|
||||
case "infra":
|
||||
return <ProjectInfra data={projects_Details} onDataChange={refetch} />;
|
||||
|
||||
case "workplan":
|
||||
return <WorkPlan data={projects_Details} onDataChange={refetch} />;
|
||||
|
||||
case "directory":
|
||||
return (
|
||||
<div className="row mt-2">
|
||||
<DirectoryPage IsPage={false} projectId={projects_Details.id} />
|
||||
</div>
|
||||
);
|
||||
return <DirectoryPage IsPage={false} projectId={projects_Details.id} />;
|
||||
case "documents":
|
||||
return (
|
||||
<div className="row">
|
||||
<ProjectDocuments />
|
||||
</div>
|
||||
);
|
||||
case "setting":
|
||||
return (
|
||||
<div className="row">
|
||||
<ProjectSetting />
|
||||
</div>
|
||||
);
|
||||
|
||||
return <ProjectDocuments />;
|
||||
case "setting":
|
||||
return <ProjectSetting />;
|
||||
default:
|
||||
return <ComingSoonPage />;
|
||||
}
|
||||
@ -149,7 +110,6 @@ const ProjectDetails = () => {
|
||||
{ label: projects_Details?.name || "Project", link: null },
|
||||
]}
|
||||
/>
|
||||
|
||||
<div className="row">
|
||||
<ProjectNav onPillClick={handlePillClick} activePill={activePill} />
|
||||
</div>
|
||||
|
@ -44,7 +44,8 @@ const ProjectRepository = {
|
||||
getProjectLevelEmployeeList:(projectId)=>api.get(`/api/Project/get/proejct-level/employees/${projectId}`),
|
||||
getProjectLevelModules:()=>api.get(`/api/Project/get/proejct-level/modules`),
|
||||
getProjectLevelEmployeePermissions:(employeeId,projectId)=>api.get(`/api/Project/get/project-level-permission/employee/${employeeId}/project/${projectId}`),
|
||||
updateProjectLevelEmployeePermission:(data)=>api.post(`/api/Project/assign/project-level-permission`,data)
|
||||
updateProjectLevelEmployeePermission:(data)=>api.post(`/api/Project/assign/project-level-permission`,data),
|
||||
getAllProjectLevelPermission:(projectId)=>api.get(`/api/Project/get/all/project-level-permission/${projectId}`)
|
||||
};
|
||||
|
||||
export const TasksRepository = {
|
||||
|
@ -17,6 +17,7 @@ export const VIEW_ALL_EMPLOYEES = "60611762-7f8a-4fb5-b53f-b1139918796b"
|
||||
|
||||
export const VIEW_TEAM_MEMBERS = "b82d2b7e-0d52-45f3-997b-c008ea460e7f"
|
||||
|
||||
export const MANAGE_TEAM = "b94802ce-0689-4643-9e1d-11c86950c35b"
|
||||
|
||||
export const MANAGE_PROJECT_INFRA = "cf2825ad-453b-46aa-91d9-27c124d63373"
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user