Merge branch 'main' of https://git.marcoaiot.com/admin/marco.pms.web into Intefrating_API_Dashboard

This commit is contained in:
Kartik Sharma 2025-09-17 16:51:56 +05:30
commit 6211f52e3a
5 changed files with 179 additions and 100 deletions

View File

@ -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,50 +95,57 @@ 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>
className="form-select form-select-sm" </div>
{...register("employeeId")} <div className="d-block">
disabled={isPending} {" "}
dd <select
> className="form-select form-select-sm"
{loading ? ( {...register("employeeId")}
<option value="">Loading...</option> disabled={isPending}
) : ( >
<> {loading ? (
<option value="">-- Select Employee --</option> <option value="">Loading...</option>
{[...employees] ) : (
?.sort((a, b) => <>
`${a?.firstName} ${a?.firstName}`?.localeCompare( <option value="">-- Select Employee --</option>
`${b?.firstName} ${b?.lastName}` {[...employees]
?.sort((a, b) =>
`${a?.firstName} ${a?.firstName}`?.localeCompare(
`${b?.firstName} ${b?.lastName}`
)
) )
) ?.map((emp) => (
?.map((emp) => ( <option key={emp.id} value={emp.id}>
<option key={emp.id} value={emp.id}> {emp.firstName} {emp.lastName}
{emp.firstName} {emp.lastName} </option>
</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> </div>
{isDirty && (
<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,8 +153,8 @@ const ProjectPermission = () => {
> >
{isPending ? "Please Wait..." : "Save Permission"} {isPending ? "Please Wait..." : "Save Permission"}
</button> </button>
</div> )}
)} </div>
</div> </div>
{ProjectModules.map((feature) => ( {ProjectModules.map((feature) => (
@ -157,22 +165,41 @@ 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}>
<label <Controller
className="form-check-label d-flex align-items-center" name="selectedPermissions"
htmlFor={perm.id} control={control}
> render={({ field }) => {
<input const value = field.value || [];
type="checkbox" const isChecked = value.includes(perm.id);
className="form-check-input me-2"
id={perm.id} return (
value={perm.id} <label
{...register("selectedPermissions")} className="form-check-label d-flex align-items-center"
/> htmlFor={perm.id}
{perm.name} >
</label> <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>
);
}}
/>
</div> </div>
))} ))}
</ul> </ul>

View File

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

View File

@ -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 dont
voluptates veritatis quia, eveniet veniam at et own the software but are granted the right to access and
repudiandae mollitia ipsam quasi labore enim architecto use it through the cloud under our Terms of Service.
non! Depending on the plan, licensing may be based on users,
features, or usage, and you can upgrade, downgrade, or
cancel at any time. non!
</div> </div>
</div> </div>
</div> </div>
<div className="card accordion-item">
<h2 className="accordion-header" id="headingSix">
<button
type="button"
className="accordion-button collapsed"
data-bs-toggle="collapse"
data-bs-target="#accordionSix"
aria-expanded="false"
aria-controls="accordionSix"
>
Can I customize Marco PMS for my business needs?
</button>
</h2>
<div
id="accordionSix"
className="accordion-collapse collapse"
aria-labelledby="headingSix"
data-bs-parent="#accordionExample"
>
<div className="accordion-body text-start">
Yes, Marco PMS is designed to be flexible and adaptable.
You can customize workflows, user roles, permissions,
and reporting to match your organizations unique
processes. Depending on your plan, we also support
advanced customization such as integrating with
third-party tools, adding custom fields, and tailoring
modules to fit your business requirements.
</div>
</div>
</div>{" "}
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,6 +1,6 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { useSearchParams, useParams, useNavigate } from "react-router-dom"; import { useSearchParams, useParams, useNavigate } from "react-router-dom";
import { useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { import {
useEmployee, useEmployee,
@ -23,22 +23,33 @@ import EmpBanner from "../../components/Employee/EmpBanner";
import EmpDashboard from "../../components/Employee/EmpDashboard"; import EmpDashboard from "../../components/Employee/EmpDashboard";
import EmpDocuments from "../../components/Employee/EmpDocuments"; import EmpDocuments from "../../components/Employee/EmpDocuments";
import EmpActivities from "../../components/Employee/EmpActivities"; import EmpActivities from "../../components/Employee/EmpActivities";
import { setProjectId } from "../../slices/localVariablesSlice";
const EmployeeProfile = () => { const EmployeeProfile = () => {
const { profile } = useProfile(); const { profile } = useProfile();
const projectID = useSelector((store) => store.localVariables.projectId); const projectID = useSelector((store) => store.localVariables.projectId);
const { employeeId } = useParams(); const { employeeId } = useParams();
const dispatch = useDispatch();
const [SearchParams] = useSearchParams(); const [SearchParams] = useSearchParams();
const tab = SearchParams.get("for"); const tab = SearchParams.get("for");
const [activePill, setActivePill] = useState(tab || "profile"); const [activePill, setActivePill] = useState(tab || "profile");
const [showModal, setShowModal] = useState(false); const [showModal, setShowModal] = useState(false);
const {data:currentEmployee,isLoading,isError,error} = useEmployee(employeeId) const {
data: currentEmployee,
isLoading,
isError,
error,
} = useEmployee(employeeId);
const handlePillClick = (pillKey) => { const handlePillClick = (pillKey) => {
setActivePill(pillKey); setActivePill(pillKey);
}; };
useEffect(() => {
dispatch(setProjectId(null));
}, [projectID]);
const navigate = useNavigate(); const navigate = useNavigate();
const renderContent = () => { const renderContent = () => {
@ -87,7 +98,7 @@ const EmployeeProfile = () => {
if (isLoading) { if (isLoading) {
return <div>Loading...</div>; return <div>Loading...</div>;
} }
if(isError) return <div >{error.message}</div> if (isError) return <div>{error.message}</div>;
return ( return (
<> <>
<div className="container-fluid"> <div className="container-fluid">
@ -115,11 +126,11 @@ const EmployeeProfile = () => {
</div> </div>
</div> </div>
<div className="row"> <div className="row">
<div className=" ">{renderContent()}</div> <div className=" ">{renderContent()}</div>
</div> </div>
</div> </div>
</> </>
); );
}; };
export default EmployeeProfile; export default EmployeeProfile;

View File

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