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";
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,6 +27,7 @@ const ProjectPermission = () => {
watch,
handleSubmit,
reset,
control,
formState: { errors, isDirty },
} = useForm({
resolver: zodResolver(ProjectPermissionSchema),
@ -73,15 +74,15 @@ const ProjectPermission = () => {
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 payloadPermissions = [...removed, ...added];
const removed = existingEnabledIds
.filter((id) => !newSelectedIds.includes(id))
.map((id) => ({ id, isEnabled: false }));
const payloadPermissions = [...added, ...removed];
if (payloadPermissions.length === 0) {
showToast("No changes detected", "info");
@ -94,50 +95,57 @@ const ProjectPermission = () => {
permission: payloadPermissions,
};
console.log("Final payload:", payload);
updatePermission(payload);
};
const useOnClick = useCallback((event) => {}, []);
return (
<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)}>
<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}
dd
>
{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-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>
{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
type="submit"
className="btn btn-sm btn-primary"
@ -145,8 +153,8 @@ const ProjectPermission = () => {
>
{isPending ? "Please Wait..." : "Save Permission"}
</button>
</div>
)}
)}
</div>
</div>
{ProjectModules.map((feature) => (
@ -157,22 +165,41 @@ const ProjectPermission = () => {
<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 class="list-unstyled">
<ul className="list-unstyled">
{feature.featurePermissions?.map((perm) => (
<div className="d-flex my-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")}
/>
{perm.name}
</label>
<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>
);
}}
/>
</div>
))}
</ul>

View File

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

View File

@ -636,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>
@ -664,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>
@ -692,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>
@ -725,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>
@ -753,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 dont
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 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>

View File

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

View File

@ -27,15 +27,23 @@ import ProjectDocument from "../../components/Project/ProjectDocuments";
import ProjectDocuments from "../../components/Project/ProjectDocuments";
import ProjectSetting from "../../components/Project/ProjectSetting";
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 projectId = useSelectedProject()
const CanViewProject = useHasAnyPermission(VIEW_PROJECTS);
const navigate = useNavigate()
const { projectNames, fetchData } = useProjectName();
const dispatch = useDispatch();
useEffect(() => {
if(!CanViewProject){
navigate("/dashboard")
}
if (projectId == null) {
dispatch(setProjectId(projectNames[0]?.id));
}