Merge branch 'main' of https://git.marcoaiot.com/admin/marco.pms.web into Intefrating_API_Dashboard
This commit is contained in:
commit
6211f52e3a
@ -6,7 +6,7 @@ import {
|
|||||||
} from "../../hooks/useProjects";
|
} from "../../hooks/useProjects";
|
||||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||||
import { useEmployeesByProject } from "../../hooks/useEmployees";
|
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 { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import showToast from "../../services/toastService";
|
import showToast from "../../services/toastService";
|
||||||
@ -27,6 +27,7 @@ const ProjectPermission = () => {
|
|||||||
watch,
|
watch,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
reset,
|
reset,
|
||||||
|
control,
|
||||||
formState: { errors, isDirty },
|
formState: { errors, isDirty },
|
||||||
} = useForm({
|
} = useForm({
|
||||||
resolver: zodResolver(ProjectPermissionSchema),
|
resolver: zodResolver(ProjectPermissionSchema),
|
||||||
@ -73,15 +74,15 @@ const ProjectPermission = () => {
|
|||||||
|
|
||||||
const newSelectedIds = formData.selectedPermissions || [];
|
const newSelectedIds = formData.selectedPermissions || [];
|
||||||
|
|
||||||
const removed = existingEnabledIds
|
|
||||||
.filter((id) => !newSelectedIds.includes(id))
|
|
||||||
.map((id) => ({ id, isEnabled: false }));
|
|
||||||
|
|
||||||
const added = newSelectedIds
|
const added = newSelectedIds
|
||||||
.filter((id) => !existingEnabledIds.includes(id))
|
.filter((id) => !existingEnabledIds.includes(id))
|
||||||
.map((id) => ({ id, isEnabled: true }));
|
.map((id) => ({ id, isEnabled: true }));
|
||||||
|
|
||||||
const payloadPermissions = [...removed, ...added];
|
const removed = existingEnabledIds
|
||||||
|
.filter((id) => !newSelectedIds.includes(id))
|
||||||
|
.map((id) => ({ id, isEnabled: false }));
|
||||||
|
|
||||||
|
const payloadPermissions = [...added, ...removed];
|
||||||
|
|
||||||
if (payloadPermissions.length === 0) {
|
if (payloadPermissions.length === 0) {
|
||||||
showToast("No changes detected", "info");
|
showToast("No changes detected", "info");
|
||||||
@ -94,21 +95,27 @@ const ProjectPermission = () => {
|
|||||||
permission: payloadPermissions,
|
permission: payloadPermissions,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
console.log("Final payload:", payload);
|
||||||
updatePermission(payload);
|
updatePermission(payload);
|
||||||
};
|
};
|
||||||
|
|
||||||
const useOnClick = useCallback((event) => {}, []);
|
|
||||||
return (
|
return (
|
||||||
<div className="w-100 p 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)}>
|
<form className="row" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className="d-flex justify-content-between align-items-end gap-2 mb-3">
|
<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="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">
|
||||||
<div className="d-block"> <select
|
<label className="form-label">Select Employee</label>
|
||||||
|
</div>
|
||||||
|
<div className="d-block">
|
||||||
|
{" "}
|
||||||
|
<select
|
||||||
className="form-select form-select-sm"
|
className="form-select form-select-sm"
|
||||||
{...register("employeeId")}
|
{...register("employeeId")}
|
||||||
disabled={isPending}
|
disabled={isPending}
|
||||||
dd
|
|
||||||
>
|
>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<option value="">Loading...</option>
|
<option value="">Loading...</option>
|
||||||
@ -128,16 +135,17 @@ const ProjectPermission = () => {
|
|||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</select></div>
|
</select>
|
||||||
|
|
||||||
{errors.employeeId && (
|
{errors.employeeId && (
|
||||||
<div className="text-danger small">
|
<div className="d-block text-danger small">
|
||||||
{errors.employeeId.message}
|
{errors.employeeId.message}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{isDirty && (
|
</div>
|
||||||
|
|
||||||
<div className="mt-3 text-end">
|
<div className="mt-3 text-end">
|
||||||
|
{isDirty && (
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="btn btn-sm btn-primary"
|
className="btn btn-sm btn-primary"
|
||||||
@ -145,9 +153,9 @@ const ProjectPermission = () => {
|
|||||||
>
|
>
|
||||||
{isPending ? "Please Wait..." : "Save Permission"}
|
{isPending ? "Please Wait..." : "Save Permission"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{ProjectModules.map((feature) => (
|
{ProjectModules.map((feature) => (
|
||||||
<div
|
<div
|
||||||
@ -157,9 +165,17 @@ const ProjectPermission = () => {
|
|||||||
<div className="card text-start border-1 p-1">
|
<div className="card text-start border-1 p-1">
|
||||||
<p className="card-title fs-6 fw-semibold">{feature.name}</p>
|
<p className="card-title fs-6 fw-semibold">{feature.name}</p>
|
||||||
<div className="px-2">
|
<div className="px-2">
|
||||||
<ul class="list-unstyled">
|
<ul className="list-unstyled">
|
||||||
{feature.featurePermissions?.map((perm) => (
|
{feature.featurePermissions?.map((perm) => (
|
||||||
<div className="d-flex my-2" key={perm.id}>
|
<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
|
<label
|
||||||
className="form-check-label d-flex align-items-center"
|
className="form-check-label d-flex align-items-center"
|
||||||
htmlFor={perm.id}
|
htmlFor={perm.id}
|
||||||
@ -168,11 +184,22 @@ const ProjectPermission = () => {
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
className="form-check-input me-2"
|
className="form-check-input me-2"
|
||||||
id={perm.id}
|
id={perm.id}
|
||||||
value={perm.id}
|
checked={isChecked}
|
||||||
{...register("selectedPermissions")}
|
onChange={(e) => {
|
||||||
|
if (e.target.checked) {
|
||||||
|
field.onChange([...value, perm.id]); // add
|
||||||
|
} else {
|
||||||
|
field.onChange(
|
||||||
|
value.filter((v) => v !== perm.id)
|
||||||
|
); // remove
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{perm.name}
|
{perm.name}
|
||||||
</label>
|
</label>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -3,9 +3,10 @@ import { ComingSoonPage } from "../../pages/Misc/ComingSoonPage";
|
|||||||
import ProjectPermission from "./ProjectPermission";
|
import ProjectPermission from "./ProjectPermission";
|
||||||
|
|
||||||
const ProjectSetting = () => {
|
const ProjectSetting = () => {
|
||||||
const [activePill, setActivePill] = useState(() => {
|
const [activePill, setActivePill] = useState("Permissions")
|
||||||
return localStorage.getItem("lastActiveProjectSettingTab") || "Permissions";
|
// const [activePill, setActivePill] = useState(() => {
|
||||||
});
|
// return localStorage.getItem("lastActiveProjectSettingTab") || "Permissions";
|
||||||
|
// });
|
||||||
const projectSettingTab = [
|
const projectSettingTab = [
|
||||||
{ key: "Permissions", label: "Permissions" },
|
{ key: "Permissions", label: "Permissions" },
|
||||||
{ key: "Notification", label: "Notification" },
|
{ key: "Notification", label: "Notification" },
|
||||||
@ -32,7 +33,7 @@ const ProjectSetting = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="w-100">
|
<div className="w-100">
|
||||||
<div className="card py-2 px-5">
|
<div className="card py-2 px-5">
|
||||||
<div className="col-12">
|
{/* <div className="col-12">
|
||||||
<div className="dropdown text-end">
|
<div className="dropdown text-end">
|
||||||
<button
|
<button
|
||||||
className="btn btn-sm btn-outline-primary dropdown-toggle"
|
className="btn btn-sm btn-outline-primary dropdown-toggle"
|
||||||
@ -63,7 +64,7 @@ const ProjectSetting = () => {
|
|||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> */}
|
||||||
|
|
||||||
<div className="mt-3">{renderContent()}</div>
|
<div className="mt-3">{renderContent()}</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -636,12 +636,12 @@ const LandingPage = () => {
|
|||||||
className="accordion-collapse collapse"
|
className="accordion-collapse collapse"
|
||||||
data-bs-parent="#accordionExample"
|
data-bs-parent="#accordionExample"
|
||||||
>
|
>
|
||||||
<div className="accordion-body">
|
<div className="accordion-body text-start">
|
||||||
Lemon drops chocolate cake gummies carrot cake chupa
|
A smart Project Management System designed to bring
|
||||||
chups muffin topping. Sesame snaps icing marzipan gummi
|
teams, tasks, and timelines together in one place. With
|
||||||
bears macaroon dragée danish caramels powder. Bear claw
|
AI-driven insights, role-based access, and seamless
|
||||||
dragée pastry topping soufflé. Wafer gummi bears
|
reporting, it empowers organizations to deliver projects
|
||||||
marshmallow pastry pie.
|
faster and smarter.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -664,12 +664,13 @@ const LandingPage = () => {
|
|||||||
aria-labelledby="headingTwo"
|
aria-labelledby="headingTwo"
|
||||||
data-bs-parent="#accordionExample"
|
data-bs-parent="#accordionExample"
|
||||||
>
|
>
|
||||||
<div className="accordion-body">
|
<div className="accordion-body text-start">
|
||||||
Dessert ice cream donut oat cake jelly-o pie sugar plum
|
Yes, you have full flexibility to manage your
|
||||||
cheesecake. Bear claw dragée oat cake dragée ice cream
|
subscription. You can upgrade to a higher plan to unlock
|
||||||
halvah tootsie roll. Danish cake oat cake pie macaroon
|
more features, downgrade to a smaller plan if your needs
|
||||||
tart donut gummies. Jelly beans candy canes carrot cake.
|
change, or cancel your subscription anytime. Plan
|
||||||
Fruitcake chocolate chupa chups.
|
changes take effect instantly, and billing adjustments
|
||||||
|
are applied on a pro-rated basis.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -692,17 +693,16 @@ const LandingPage = () => {
|
|||||||
aria-labelledby="headingThree"
|
aria-labelledby="headingThree"
|
||||||
data-bs-parent="#accordionExample"
|
data-bs-parent="#accordionExample"
|
||||||
>
|
>
|
||||||
<div className="accordion-body">
|
<div className="accordion-body text-start">
|
||||||
Regular license can be used for end products that do not
|
Security is at the core of Marco PMS. We use
|
||||||
charge users for access or service(access is free and
|
industry-standard encryption (SSL/TLS) to protect data
|
||||||
there will be no monthly subscription fee). Single
|
in transit and advanced encryption to safeguard data at
|
||||||
regular license can be used for single end product and
|
rest. Role-based access controls ensure that only
|
||||||
end product can be used by you or your client. If you
|
authorized users can access sensitive information. Our
|
||||||
want to sell end product to multiple clients then you
|
system is hosted on secure, cloud-ready infrastructure
|
||||||
will need to purchase separate license for each client.
|
with regular backups, monitoring, and compliance with
|
||||||
The same rule applies if you want to use the same end
|
best practices to keep your data safe and available at
|
||||||
product on multiple domains(unique setup). For more info
|
all times.
|
||||||
on regular license you can check official description.
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -725,12 +725,12 @@ const LandingPage = () => {
|
|||||||
aria-labelledby="headingFour"
|
aria-labelledby="headingFour"
|
||||||
data-bs-parent="#accordionExample"
|
data-bs-parent="#accordionExample"
|
||||||
>
|
>
|
||||||
<div className="accordion-body">
|
<div className="accordion-body text-start">
|
||||||
Lorem ipsum dolor sit amet consectetur adipisicing elit.
|
You can reach our support team anytime through the
|
||||||
Nobis et aliquid quaerat possimus maxime! Mollitia
|
in-app help center, email, or live chat. We also provide
|
||||||
reprehenderit neque repellat deleniti delectus
|
a detailed knowledge base and FAQs to guide you through
|
||||||
architecto dolorum maxime, blanditiis earum ea, incidunt
|
common queries. For personalized assistance, our support
|
||||||
quam possimus cumque.
|
specialists are always ready to help you.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -753,15 +753,47 @@ const LandingPage = () => {
|
|||||||
aria-labelledby="headingFive"
|
aria-labelledby="headingFive"
|
||||||
data-bs-parent="#accordionExample"
|
data-bs-parent="#accordionExample"
|
||||||
>
|
>
|
||||||
<div className="accordion-body">
|
<div className="accordion-body text-start">
|
||||||
Lorem ipsum dolor sit amet consectetur, adipisicing
|
Marco PMS operate under a proprietary license combined
|
||||||
elit. Sequi molestias exercitationem ab cum nemo facere
|
with a subscription model. This means customers don’t
|
||||||
voluptates veritatis quia, eveniet veniam at et
|
own the software but are granted the right to access and
|
||||||
repudiandae mollitia ipsam quasi labore enim architecto
|
use it through the cloud under our Terms of Service.
|
||||||
non!
|
Depending on the plan, licensing may be based on users,
|
||||||
|
features, or usage, and you can upgrade, downgrade, or
|
||||||
|
cancel at any time. non!
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="card accordion-item">
|
||||||
|
<h2 className="accordion-header" id="headingSix">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="accordion-button collapsed"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#accordionSix"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-controls="accordionSix"
|
||||||
|
>
|
||||||
|
Can I customize Marco PMS for my business needs?
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div
|
||||||
|
id="accordionSix"
|
||||||
|
className="accordion-collapse collapse"
|
||||||
|
aria-labelledby="headingSix"
|
||||||
|
data-bs-parent="#accordionExample"
|
||||||
|
>
|
||||||
|
<div className="accordion-body text-start">
|
||||||
|
Yes, Marco PMS is designed to be flexible and adaptable.
|
||||||
|
You can customize workflows, user roles, permissions,
|
||||||
|
and reporting to match your organization’s unique
|
||||||
|
processes. Depending on your plan, we also support
|
||||||
|
advanced customization such as integrating with
|
||||||
|
third-party tools, adding custom fields, and tailoring
|
||||||
|
modules to fit your business requirements.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>{" "}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { useSearchParams, useParams, useNavigate } from "react-router-dom";
|
import { useSearchParams, useParams, useNavigate } from "react-router-dom";
|
||||||
import { useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useEmployee,
|
useEmployee,
|
||||||
@ -23,22 +23,33 @@ import EmpBanner from "../../components/Employee/EmpBanner";
|
|||||||
import EmpDashboard from "../../components/Employee/EmpDashboard";
|
import EmpDashboard from "../../components/Employee/EmpDashboard";
|
||||||
import EmpDocuments from "../../components/Employee/EmpDocuments";
|
import EmpDocuments from "../../components/Employee/EmpDocuments";
|
||||||
import EmpActivities from "../../components/Employee/EmpActivities";
|
import EmpActivities from "../../components/Employee/EmpActivities";
|
||||||
|
import { setProjectId } from "../../slices/localVariablesSlice";
|
||||||
|
|
||||||
const EmployeeProfile = () => {
|
const EmployeeProfile = () => {
|
||||||
const { profile } = useProfile();
|
const { profile } = useProfile();
|
||||||
|
|
||||||
const projectID = useSelector((store) => store.localVariables.projectId);
|
const projectID = useSelector((store) => store.localVariables.projectId);
|
||||||
|
|
||||||
const { employeeId } = useParams();
|
const { employeeId } = useParams();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const [SearchParams] = useSearchParams();
|
const [SearchParams] = useSearchParams();
|
||||||
const tab = SearchParams.get("for");
|
const tab = SearchParams.get("for");
|
||||||
const [activePill, setActivePill] = useState(tab || "profile");
|
const [activePill, setActivePill] = useState(tab || "profile");
|
||||||
const [showModal, setShowModal] = useState(false);
|
const [showModal, setShowModal] = useState(false);
|
||||||
const {data:currentEmployee,isLoading,isError,error} = useEmployee(employeeId)
|
const {
|
||||||
|
data: currentEmployee,
|
||||||
|
isLoading,
|
||||||
|
isError,
|
||||||
|
error,
|
||||||
|
} = useEmployee(employeeId);
|
||||||
const handlePillClick = (pillKey) => {
|
const handlePillClick = (pillKey) => {
|
||||||
setActivePill(pillKey);
|
setActivePill(pillKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(setProjectId(null));
|
||||||
|
}, [projectID]);
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const renderContent = () => {
|
const renderContent = () => {
|
||||||
@ -87,7 +98,7 @@ const EmployeeProfile = () => {
|
|||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <div>Loading...</div>;
|
return <div>Loading...</div>;
|
||||||
}
|
}
|
||||||
if(isError) return <div >{error.message}</div>
|
if (isError) return <div>{error.message}</div>;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="container-fluid">
|
<div className="container-fluid">
|
||||||
|
@ -27,15 +27,23 @@ import ProjectDocument from "../../components/Project/ProjectDocuments";
|
|||||||
import ProjectDocuments from "../../components/Project/ProjectDocuments";
|
import ProjectDocuments from "../../components/Project/ProjectDocuments";
|
||||||
import ProjectSetting from "../../components/Project/ProjectSetting";
|
import ProjectSetting from "../../components/Project/ProjectSetting";
|
||||||
import DirectoryPage from "../Directory/DirectoryPage";
|
import DirectoryPage from "../Directory/DirectoryPage";
|
||||||
|
import { useHasAnyPermission } from "../../hooks/useExpense";
|
||||||
|
import { VIEW_PROJECTS } from "../../utils/constants";
|
||||||
|
import { useNavigate, useRoutes } from "react-router-dom";
|
||||||
|
|
||||||
const ProjectDetails = () => {
|
const ProjectDetails = () => {
|
||||||
|
|
||||||
const projectId = useSelectedProject()
|
const projectId = useSelectedProject()
|
||||||
|
const CanViewProject = useHasAnyPermission(VIEW_PROJECTS);
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const { projectNames, fetchData } = useProjectName();
|
const { projectNames, fetchData } = useProjectName();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if(!CanViewProject){
|
||||||
|
navigate("/dashboard")
|
||||||
|
}
|
||||||
if (projectId == null) {
|
if (projectId == null) {
|
||||||
dispatch(setProjectId(projectNames[0]?.id));
|
dispatch(setProjectId(projectNames[0]?.id));
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user