Merge branch 'Issues_Oct_4W_V1' of https://git.marcoaiot.com/admin/marco.pms.web into Kartik_Bug_V1#1539

This commit is contained in:
Kartik Sharma 2025-11-07 14:52:16 +05:30
commit 480daadaf0
15 changed files with 508 additions and 414 deletions

View File

@ -5,7 +5,7 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Marco PMS</title> <title>OnFieldWork.com</title>
<meta name="description" content="" /> <meta name="description" content="" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

View File

@ -0,0 +1,5 @@
<svg width="65" height="65" viewBox="0 0 65 65" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.2" d="M46.5001 10.5288H32.5001L20.2251 26.5288L32.5001 56.5288L60.5001 26.5288L46.5001 10.5288Z" fill="#03C3EC"/>
<path d="M18.5 10.5288H46.5L60.5 26.5288L32.5 56.5288L4.5 26.5288L18.5 10.5288Z" stroke="#03C3EC" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M33.2934 9.92012C33.1042 9.67343 32.8109 9.52881 32.5 9.52881C32.1891 9.52881 31.8958 9.67343 31.7066 9.92012L19.7318 25.5288H4.5C3.94772 25.5288 3.5 25.9765 3.5 26.5288C3.5 27.0811 3.94772 27.5288 4.5 27.5288H19.5537L31.5745 56.9075C31.7282 57.2833 32.094 57.5288 32.5 57.5288C32.906 57.5288 33.2718 57.2833 33.4255 56.9075L45.4463 27.5288H60.5C61.0523 27.5288 61.5 27.0811 61.5 26.5288C61.5 25.9765 61.0523 25.5288 60.5 25.5288H45.2682L33.2934 9.92012ZM42.7474 25.5288L32.5 12.1717L22.2526 25.5288H42.7474ZM21.7146 27.5288L32.5 53.8881L43.2854 27.5288H21.7146Z" fill="#03C3EC"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -61,7 +61,7 @@ const CardViewContact = ({
(contact?.name || "").trim().split(" ")[1]?.charAt(0) || "" (contact?.name || "").trim().split(" ")[1]?.charAt(0) || ""
} }
/>{" "} />{" "}
<span className="text-heading fs-6"> {contact?.name}</span> <span className="text-heading fs-6 ms-2"> {contact?.name}</span>
</div> </div>
<div> <div>
{IsActive && ( {IsActive && (

View File

@ -87,7 +87,7 @@ const NoteCardDirectoryEditable = ({
/> />
<div> <div>
<div <div
className="d-flex ms-0 align-middle cursor-pointer" className="d-flex ms-3 align-middle cursor-pointer"
onClick={() => contactProfile(noteItem.contactId)} onClick={() => contactProfile(noteItem.contactId)}
> >
<span> <span>
@ -98,7 +98,7 @@ const NoteCardDirectoryEditable = ({
</span> </span>
</div> </div>
<div className="d-flex ms-0 align-middle"></div> <div className="d-flex ms-0 align-middle"></div>
<div className="d-flex ms-0 mt-2"> <div className="d-flex ms-3 mt-2">
<span className="text-muted"> <span className="text-muted">
by{" "} by{" "}
<span className="fw-bold "> <span className="fw-bold ">
@ -184,7 +184,7 @@ const NoteCardDirectoryEditable = ({
</> </>
) : ( ) : (
<div <div
className="mx-4 px-10 text-start" className="mx-4 px-11 text-start"
dangerouslySetInnerHTML={{ __html: noteItem.note }} dangerouslySetInnerHTML={{ __html: noteItem.note }}
/> />
)} )}

View File

@ -13,230 +13,236 @@ import { useParams } from "react-router-dom";
const DocumentFilterPanel = forwardRef( const DocumentFilterPanel = forwardRef(
({ entityTypeId, onApply, setFilterdata }, ref) => { ({ entityTypeId, onApply, setFilterdata }, ref) => {
const [resetKey, setResetKey] = useState(0); const [resetKey, setResetKey] = useState(0);
const { status } = useParams(); const { status } = useParams();
const { data, isError, isLoading, error } = const { data, isError, isLoading, error } =
useDocumentFilterEntities(entityTypeId); useDocumentFilterEntities(entityTypeId);
//changes useEffect(() => {
return () => {
closePanel();
};
}, []);
const dynamicDocumentFilterDefaultValues = useMemo(() => { //changes
return {
...DocumentFilterDefaultValues, const dynamicDocumentFilterDefaultValues = useMemo(() => {
uploadedByIds: DocumentFilterDefaultValues.uploadedByIds || [], return {
documentCategoryIds: DocumentFilterDefaultValues.documentCategoryIds || [], ...DocumentFilterDefaultValues,
documentTypeIds: DocumentFilterDefaultValues.documentTypeIds || [], uploadedByIds: DocumentFilterDefaultValues.uploadedByIds || [],
documentTagIds: DocumentFilterDefaultValues.documentTagIds || [], documentCategoryIds: DocumentFilterDefaultValues.documentCategoryIds || [],
startDate: DocumentFilterDefaultValues.startDate, documentTypeIds: DocumentFilterDefaultValues.documentTypeIds || [],
endDate: DocumentFilterDefaultValues.endDate, documentTagIds: DocumentFilterDefaultValues.documentTagIds || [],
startDate: DocumentFilterDefaultValues.startDate,
endDate: DocumentFilterDefaultValues.endDate,
};
}, [status]);
const methods = useForm({
resolver: zodResolver(DocumentFilterSchema),
defaultValues: dynamicDocumentFilterDefaultValues,
});
const { handleSubmit, reset, setValue, watch } = methods;
// Watch values from form
const isUploadedAt = watch("isUploadedAt");
const isVerified = watch("isVerified");
// Close the offcanvas (bootstrap specific)
const closePanel = () => {
document.querySelector(".offcanvas.show .btn-close")?.click();
}; };
}, [status]); useImperativeHandle(ref, () => ({
resetFieldValue: (name, value) => {
if (value !== undefined) {
setValue(name, value);
} else {
reset({ ...methods.getValues(), [name]: DocumentFilterDefaultValues[name] });
}
},
getValues: methods.getValues, // optional, to read current filter state
}));
const methods = useForm({ //changes
resolver: zodResolver(DocumentFilterSchema), useEffect(() => {
defaultValues: dynamicDocumentFilterDefaultValues, if (data && setFilterdata) {
}); setFilterdata(data);
const { handleSubmit, reset, setValue, watch } = methods;
// Watch values from form
const isUploadedAt = watch("isUploadedAt");
const isVerified = watch("isVerified");
// Close the offcanvas (bootstrap specific)
const closePanel = () => {
document.querySelector(".offcanvas.show .btn-close")?.click();
};
useImperativeHandle(ref, () => ({
resetFieldValue: (name, value) => {
if (value !== undefined) {
setValue(name, value);
} else {
reset({ ...methods.getValues(), [name]: DocumentFilterDefaultValues[name] });
} }
}, }, [data, setFilterdata]);
getValues: methods.getValues, // optional, to read current filter state
}));
//changes const onSubmit = (values) => {
useEffect(() => { onApply({
if (data && setFilterdata) { ...values,
setFilterdata(data); startDate: values.startDate
} ? moment.utc(values.startDate, "DD-MM-YYYY").toISOString()
}, [data, setFilterdata]); : null,
endDate: values.endDate
? moment.utc(values.endDate, "DD-MM-YYYY").toISOString()
: null,
});
// closePanel();
};
const onSubmit = (values) => { const onClear = () => {
onApply({ reset(DocumentFilterDefaultValues);
...values, setResetKey((prev) => prev + 1);
startDate: values.startDate onApply(DocumentFilterDefaultValues);
? moment.utc(values.startDate, "DD-MM-YYYY").toISOString() // closePanel();
: null, };
endDate: values.endDate
? moment.utc(values.endDate, "DD-MM-YYYY").toISOString()
: null,
});
// closePanel();
};
const onClear = () => { if (isLoading) return <div>Loading...</div>;
reset(DocumentFilterDefaultValues); if (isError)
setResetKey((prev) => prev + 1); return <div>Error: {error?.message || "Something went wrong!"}</div>;
onApply(DocumentFilterDefaultValues);
// closePanel();
};
if (isLoading) return <div>Loading...</div>; const {
if (isError) uploadedBy = [],
return <div>Error: {error?.message || "Something went wrong!"}</div>; documentCategory = [],
documentType = [],
const { documentTag = [],
uploadedBy = [], } = data?.data || {};
documentCategory = [],
documentType = [],
documentTag = [],
} = data?.data || {};
return ( return (
<FormProvider {...methods}> <FormProvider {...methods}>
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
{/* Date Range Section */} {/* Date Range Section */}
<div className="mb-2"> <div className="mb-2">
<div className="text-start d-flex align-items-center my-1"> <div className="text-start d-flex align-items-center my-1">
<label className="form-label me-2 my-0">Choose Date:</label> <label className="form-label me-2 my-0">Choose Date:</label>
<div className="d-inline-flex border rounded-pill overflow-hidden shadow-none"> <div className="d-inline-flex border rounded-pill overflow-hidden shadow-none">
<button <button
type="button" type="button"
className={`btn px-2 py-1 rounded-0 text-tiny ${isUploadedAt ? "active btn-secondary text-white" : "" className={`btn px-2 py-1 rounded-0 text-tiny ${isUploadedAt ? "active btn-secondary text-white" : ""
}`} }`}
onClick={() => setValue("isUploadedAt", true)} onClick={() => setValue("isUploadedAt", true)}
> >
Uploaded On Uploaded On
</button> </button>
<button <button
type="button" type="button"
className={`btn px-2 py-1 rounded-0 text-tiny ${!isUploadedAt ? "active btn-secondary text-white" : "" className={`btn px-2 py-1 rounded-0 text-tiny ${!isUploadedAt ? "active btn-secondary text-white" : ""
}`} }`}
onClick={() => setValue("isUploadedAt", false)} onClick={() => setValue("isUploadedAt", false)}
> >
Updated On Updated On
</button> </button>
</div>
</div>
<DateRangePicker1
placeholder="DD-MM-YYYY To DD-MM-YYYY"
startField="startDate"
endField="endDate"
defaultRange={false}
resetSignal={resetKey}
maxDate={new Date()}
/>
</div>
{/* Dropdown Filters */}
<div className="row g-2 text-start">
<SelectMultiple
name="uploadedByIds"
label="Uploaded By:"
options={uploadedBy}
labelKey="name"
valueKey="id"
/>
<SelectMultiple
name="documentCategoryIds"
label="Document Category:"
options={documentCategory}
labelKey="name"
valueKey="id"
/>
<SelectMultiple
name="documentTypeIds"
label="Document Type:"
options={documentType}
labelKey="name"
valueKey="id"
/>
<SelectMultiple
name="documentTagIds"
label="Tags:"
options={documentTag}
labelKey="name"
valueKey="id"
/>
</div>
{/* Status Filter */}
<div className="text-start my-2">
<label className="form-label d-block mb-2">Choose Status:</label>
<div className="d-flex gap-4">
<label className="switch switch-sm">
<input
type="radio"
className="switch-input"
name="isVerified"
checked={isVerified === null}
onChange={() => setValue("isVerified", null)}
/>
<span className="switch-toggle-slider">
<span className="switch-on"></span>
<span className="switch-off"></span>
</span>
<span className="switch-label">All</span>
</label>
<label className="switch switch-sm">
<input
type="radio"
className="switch-input"
name="isVerified"
checked={isVerified === true}
onChange={() => setValue("isVerified", true)}
/>
<span className="switch-toggle-slider">
<span className="switch-on"></span>
<span className="switch-off"></span>
</span>
<span className="switch-label">Verified</span>
</label>
<label className="switch switch-sm">
<input
type="radio"
className="switch-input"
name="isVerified"
checked={isVerified === false}
onChange={() => setValue("isVerified", false)}
/>
<span className="switch-toggle-slider">
<span className="switch-on"></span>
<span className="switch-off"></span>
</span>
<span className="switch-label">Rejected</span>
</label>
</div> </div>
</div> </div>
<DateRangePicker1 {/* Footer Buttons */}
placeholder="DD-MM-YYYY To DD-MM-YYYY" <div className="d-flex justify-content-end py-3 gap-2">
startField="startDate" <button
endField="endDate" type="button"
defaultRange={false} className="btn btn-label-secondary btn-sm"
resetSignal={resetKey} onClick={onClear}
maxDate={new Date()} >
/> Clear
</div> </button>
<button type="submit" className="btn btn-primary btn-sm">
{/* Dropdown Filters */} Apply
<div className="row g-2 text-start"> </button>
<SelectMultiple
name="uploadedByIds"
label="Uploaded By:"
options={uploadedBy}
labelKey="name"
valueKey="id"
/>
<SelectMultiple
name="documentCategoryIds"
label="Document Category:"
options={documentCategory}
labelKey="name"
valueKey="id"
/>
<SelectMultiple
name="documentTypeIds"
label="Document Type:"
options={documentType}
labelKey="name"
valueKey="id"
/>
<SelectMultiple
name="documentTagIds"
label="Tags:"
options={documentTag}
labelKey="name"
valueKey="id"
/>
</div>
{/* Status Filter */}
<div className="text-start my-2">
<label className="form-label d-block mb-2">Choose Status:</label>
<div className="d-flex gap-4">
<label className="switch switch-sm">
<input
type="radio"
className="switch-input"
name="isVerified"
checked={isVerified === null}
onChange={() => setValue("isVerified", null)}
/>
<span className="switch-toggle-slider">
<span className="switch-on"></span>
<span className="switch-off"></span>
</span>
<span className="switch-label">All</span>
</label>
<label className="switch switch-sm">
<input
type="radio"
className="switch-input"
name="isVerified"
checked={isVerified === true}
onChange={() => setValue("isVerified", true)}
/>
<span className="switch-toggle-slider">
<span className="switch-on"></span>
<span className="switch-off"></span>
</span>
<span className="switch-label">Verified</span>
</label>
<label className="switch switch-sm">
<input
type="radio"
className="switch-input"
name="isVerified"
checked={isVerified === false}
onChange={() => setValue("isVerified", false)}
/>
<span className="switch-toggle-slider">
<span className="switch-on"></span>
<span className="switch-off"></span>
</span>
<span className="switch-label">Rejected</span>
</label>
</div> </div>
</div> </form>
</FormProvider>
{/* Footer Buttons */} );
<div className="d-flex justify-content-end py-3 gap-2"> });
<button
type="button"
className="btn btn-label-secondary btn-sm"
onClick={onClear}
>
Clear
</button>
<button type="submit" className="btn btn-primary btn-sm">
Apply
</button>
</div>
</form>
</FormProvider>
);
});
export default DocumentFilterPanel; export default DocumentFilterPanel;

View File

@ -74,7 +74,7 @@ const DocumentVersionList = ({
firstName={currentDoc.uploadedBy?.firstName} firstName={currentDoc.uploadedBy?.firstName}
lastName={currentDoc.uploadedBy?.lastName} lastName={currentDoc.uploadedBy?.lastName}
/> />
<span className="ms-1"> <span className="ms-3">
<small className="fw-normal" style={{ marginLeft: "-10px" }}> <small className="fw-normal" style={{ marginLeft: "-10px" }}>
{`${currentDoc.uploadedBy?.firstName ?? ""} ${currentDoc.uploadedBy?.lastName ?? ""}`.trim() || "N/A"} {`${currentDoc.uploadedBy?.firstName ?? ""} ${currentDoc.uploadedBy?.lastName ?? ""}`.trim() || "N/A"}
</small> </small>
@ -196,7 +196,7 @@ const DocumentVersionList = ({
firstName={document.uploadedBy?.firstName} firstName={document.uploadedBy?.firstName}
lastName={document.uploadedBy?.lastName} lastName={document.uploadedBy?.lastName}
/> />
<span className="ms-1"> <span className="ms-3">
<small className="fw-normal" style={{ marginLeft: "-10px" }}> <small className="fw-normal" style={{ marginLeft: "-10px" }}>
{`${document.uploadedBy?.firstName ?? ""} ${document.uploadedBy?.lastName ?? ""}`.trim() || "N/A"} {`${document.uploadedBy?.firstName ?? ""} ${document.uploadedBy?.lastName ?? ""}`.trim() || "N/A"}
</small> </small>
@ -216,7 +216,7 @@ const DocumentVersionList = ({
firstName={document.verifiedBy?.firstName} firstName={document.verifiedBy?.firstName}
lastName={document.verifiedBy?.lastName} lastName={document.verifiedBy?.lastName}
/> />
<span className="ms-1"> <span className="ms-3">
<small className="fw-normal" style={{ marginLeft: "-10px" }}> <small className="fw-normal" style={{ marginLeft: "-10px" }}>
{`${document.verifiedBy?.firstName ?? ""} ${document.verifiedBy?.lastName ?? ""}`.trim() || "N/A"} {`${document.verifiedBy?.firstName ?? ""} ${document.verifiedBy?.lastName ?? ""}`.trim() || "N/A"}
</small> </small>

View File

@ -15,7 +15,7 @@ const Sidebar = () => {
> >
<div className="app-brand" style={{ paddingLeft: "30px" }}> <div className="app-brand" style={{ paddingLeft: "30px" }}>
<Link to="/dashboard" className="app-brand-link"> <Link to="/dashboard" className="app-brand-link">
<span className="app-brand-logo rounded-circle app-brand-logo-border"> {/* <span className="app-brand-logo rounded-circle app-brand-logo-border">
<img <img
className="app-brand-logo-sidebar" className="app-brand-logo-sidebar"
src="/img/brand/marco.png" src="/img/brand/marco.png"
@ -23,8 +23,19 @@ const Sidebar = () => {
aria-label="logo image" aria-label="logo image"
style={{ margin: "5px", paddingRight: "5px" }} style={{ margin: "5px", paddingRight: "5px" }}
/> />
</span> </span> */}
<span className="app-brand-text menu-text fw-bold ms-2">PMS</span>
<a
href="/"
class="app-brand-link fw-bold navbar-brand text-green fs-6"
>
<span class="app-brand-logo demo">
<img src="/img/brand/marco.png" width="50" />
</span>
<span class="text-blue">OnField</span>
<span>Work</span>
<span class="text-dark">.com</span>
</a>
</Link> </Link>
<a className="layout-menu-toggle menu-link text-large ms-auto"> <a className="layout-menu-toggle menu-link text-large ms-auto">

View File

@ -23,13 +23,13 @@ const AuthLayout = () => {
to="/" to="/"
className="app-brand-link gap-2 position-fixed top-2 start-0 mx-2 mx-sm-4" className="app-brand-link gap-2 position-fixed top-2 start-0 mx-2 mx-sm-4"
> >
<span className="app-brand-logo rounded-circle "> {/* <span className="app-brand-logo rounded-circle ">
<img <img
src="/img/brand/marco.png" src="/img/brand/marco.png"
alt="marco-logo" alt="marco-logo"
className="app-brand-logo-login" className="app-brand-logo-login"
/> />
</span> </span> */}
</Link> </Link>
<Outlet /> <Outlet />
</div> </div>

View File

@ -654,3 +654,15 @@ nav.layout-navbar.navbar-active::after {
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
} }
.light-style .landing-hero {
background: linear-gradient(138.18deg, #eae8fd, #ede7e7 94.44%);
}
.text-green {
color: #49bf3c !important;
}
.text-blue {
color: var(--bs-blue);
}

View File

@ -54,15 +54,27 @@ const LandingPage = () => {
> >
<i className="tf-icons bx bx-menu bx-lg align-middle text-heading fw-medium"></i> <i className="tf-icons bx bx-menu bx-lg align-middle text-heading fw-medium"></i>
</button> </button>
{/* Mobile menu toggle: End*/}
<a href="/" className="app-brand-link"> {/* <a href="/" className="app-brand-link">
<span className="app-brand-logo demo"> <span className="app-brand-logo demo">
<img src="/img/brand/marco.png" width={50}></img> <img src="/img/brand/marco.png" width={50}></img>
</span> </span>
<span className="app-brand-text demo menu-text fw-bold ms-2 ps-1 "> <span className="app-brand-text demo menu-text fw-bold ms-2 ps-1 ">
{/* <Link> */} PMS PMS
{/* </Link> */}
</span> </span>
</a> */}
<a
href="/"
class="app-brand-link fw-bold navbar-brand text-green fs-5"
>
<span class="app-brand-logo demo">
<img src="/img/brand/marco.png" width="50" />
</span>
<span class="text-blue">OnField</span>
<span>Work</span>
<span class="text-dark">.com</span>
</a> </a>
</div> </div>
{/* Menu logo wrapper: End */} {/* Menu logo wrapper: End */}
@ -226,7 +238,7 @@ const LandingPage = () => {
</SwiperSlide> </SwiperSlide>
<SwiperSlide> <SwiperSlide>
<SwaperSlideContent <SwaperSlideContent
ImageUrl="/app/dashboard-light-04.png" ImageUrl="/img/app/dashboard-light-09.png"
Title="Role-based Permissions" Title="Role-based Permissions"
Body="Securely control access with customizable roles and permissions." Body="Securely control access with customizable roles and permissions."
></SwaperSlideContent> ></SwaperSlideContent>
@ -367,7 +379,7 @@ const LandingPage = () => {
</div>{" "} </div>{" "}
<div className="col-lg-3 col-sm-4 text-center features-icon-box"> <div className="col-lg-3 col-sm-4 text-center features-icon-box">
<div className="text-center mb-4"> <div className="text-center mb-4">
<img src="/img/icons/inventory.svg" alt="keyboard" /> <img src="/img/icons/user.svg" alt="keyboard" />
</div> </div>
<h5 className="mb-2">Inventory Management</h5> <h5 className="mb-2">Inventory Management</h5>
<p className="features-icon-description"> <p className="features-icon-description">
@ -436,7 +448,7 @@ const LandingPage = () => {
{" "} {" "}
<SwaperBlogContent <SwaperBlogContent
ImageUrl="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQs28_JnxJUKdAgaZsiWW4NyekVmfmLtpUtaA&s" ImageUrl="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQs28_JnxJUKdAgaZsiWW4NyekVmfmLtpUtaA&s"
Title="Transforming Residential Project Delivery with Marco PMS" Title="Transforming Residential Project Delivery with OnFieldWork.com"
Body="Sunrise Builders struggled with delays, cost overruns, and lack of transparency in their multi-phase township project. Different contractors for civil, electrical, and finishing works operated in silos, and communication gaps with the Project Management Consultant (PMC) often led to rework and disputes." Body="Sunrise Builders struggled with delays, cost overruns, and lack of transparency in their multi-phase township project. Different contractors for civil, electrical, and finishing works operated in silos, and communication gaps with the Project Management Consultant (PMC) often led to rework and disputes."
></SwaperBlogContent> ></SwaperBlogContent>
</SwiperSlide> </SwiperSlide>
@ -627,7 +639,7 @@ const LandingPage = () => {
aria-expanded="true" aria-expanded="true"
aria-controls="accordionOne" aria-controls="accordionOne"
> >
What is MarcoPMS? What is OnFieldWork.com?
</button> </button>
</h2> </h2>
@ -684,7 +696,7 @@ const LandingPage = () => {
aria-expanded="false" aria-expanded="false"
aria-controls="accordionThree" aria-controls="accordionThree"
> >
How secure is Marco PMS? How secure is OnFieldWork.com?
</button> </button>
</h2> </h2>
<div <div
@ -694,7 +706,7 @@ const LandingPage = () => {
data-bs-parent="#accordionExample" data-bs-parent="#accordionExample"
> >
<div className="accordion-body text-start"> <div className="accordion-body text-start">
Security is at the core of Marco PMS. We use Security is at the core of OnFieldWork.com. We use
industry-standard encryption (SSL/TLS) to protect data industry-standard encryption (SSL/TLS) to protect data
in transit and advanced encryption to safeguard data at in transit and advanced encryption to safeguard data at
rest. Role-based access controls ensure that only rest. Role-based access controls ensure that only
@ -754,13 +766,13 @@ const LandingPage = () => {
data-bs-parent="#accordionExample" data-bs-parent="#accordionExample"
> >
<div className="accordion-body text-start"> <div className="accordion-body text-start">
Marco PMS operate under a proprietary license combined OnFieldWork.com operate under a proprietary license
with a subscription model. This means customers dont combined with a subscription model. This means customers
own the software but are granted the right to access and dont own the software but are granted the right to
use it through the cloud under our Terms of Service. access and use it through the cloud under our Terms of
Depending on the plan, licensing may be based on users, Service. Depending on the plan, licensing may be based
features, or usage, and you can upgrade, downgrade, or on users, features, or usage, and you can upgrade,
cancel at any time. non! downgrade, or cancel at any time. non!
</div> </div>
</div> </div>
</div> </div>
@ -774,7 +786,7 @@ const LandingPage = () => {
aria-expanded="false" aria-expanded="false"
aria-controls="accordionSix" aria-controls="accordionSix"
> >
Can I customize Marco PMS for my business needs? Can I customize OnFieldWork.com for my business needs?
</button> </button>
</h2> </h2>
<div <div
@ -784,11 +796,11 @@ const LandingPage = () => {
data-bs-parent="#accordionExample" data-bs-parent="#accordionExample"
> >
<div className="accordion-body text-start"> <div className="accordion-body text-start">
Yes, Marco PMS is designed to be flexible and adaptable. Yes, OnFieldWork.com is designed to be flexible and
You can customize workflows, user roles, permissions, adaptable. You can customize workflows, user roles,
and reporting to match your organizations unique permissions, and reporting to match your organizations
processes. Depending on your plan, we also support unique processes. Depending on your plan, we also
advanced customization such as integrating with support advanced customization such as integrating with
third-party tools, adding custom fields, and tailoring third-party tools, adding custom fields, and tailoring
modules to fit your business requirements. modules to fit your business requirements.
</div> </div>
@ -823,7 +835,12 @@ const LandingPage = () => {
alt="hero elements" alt="hero elements"
></img> ></img>
</div> </div>
<div className="col-lg-6 text-start text-sm-center text-lg-start"> <div
className="col-lg-6 text-start text-sm-center text-lg-start p-5 rounded"
style={{
border: "1px solid #d5d5d5",
}}
>
<div className="mt-5"> <div className="mt-5">
{" "} {" "}
<h4 className="text-start mb-1"> <h4 className="text-start mb-1">
@ -1164,7 +1181,7 @@ const LandingPage = () => {
src="/img/brand/marco.png" src="/img/brand/marco.png"
width="50" width="50"
/> />
<span> Marco PMS</span> <span> OnFieldWork.com</span>
</div> </div>
</Link> </Link>
</span> </span>
@ -1252,7 +1269,7 @@ const LandingPage = () => {
<img src="/img/icons/apple-icon.png" alt="apple icon" /> <img src="/img/icons/apple-icon.png" alt="apple icon" />
</a> </a>
<a <a
href="https://play.google.com/store/apps/details?id=com.marco.aiotstage&pcampaignid=web_share" href="https://play.google.com/store/apps/details?id=com.marcoonfieldwork.aiot&pcampaignid=web_share "
target="_blank" target="_blank"
> >
<img <img

View File

@ -1,6 +1,6 @@
import { useState } from "react"; import { useState } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { AuthWrapper } from "./AuthWrapper" import { AuthWrapper } from "./AuthWrapper";
import "./page-auth.css"; import "./page-auth.css";
import AuthRepository from "../../repositories/AuthRepository"; import AuthRepository from "../../repositories/AuthRepository";
import showToast from "../../services/toastService"; import showToast from "../../services/toastService";
@ -8,54 +8,76 @@ import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod"; import { z } from "zod";
const forgotPassSceham = z.object({ const forgotPassSceham = z.object({
email: z.string().trim().email(), email: z.string().trim().email(),
}) });
const ForgotPasswordPage = () => { const ForgotPasswordPage = () => {
const [loding, setLoading] = useState(false);
const [loding, setLoading] = useState(false) const {
register,
const { register,
handleSubmit, handleSubmit,
formState: { errors }, formState: { errors },
reset, reset,
getValues } = useForm({ getValues,
resolver: zodResolver(forgotPassSceham), } = useForm({
defaultValues: { resolver: zodResolver(forgotPassSceham),
email: "" defaultValues: {
} email: "",
}) },
});
const onSubmit = async (data) => { const onSubmit = async (data) => {
try { try {
setLoading(true) setLoading(true);
const response = await AuthRepository.forgotPassword(data) const response = await AuthRepository.forgotPassword(data);
if (response.data && response.success) if (response.data && response.success)
showToast("verification email has been sent to your registered email address", "success") showToast(
reset() "verification email has been sent to your registered email address",
setLoading(false) "success"
);
reset();
setLoading(false);
} catch (err) { } catch (err) {
reset() reset();
if (err.response.status === 404) { if (err.response.status === 404) {
showToast("verification email has been sent to your registered email address", "success") showToast(
"verification email has been sent to your registered email address",
"success"
);
} else { } else {
showToast("Something wrong", "error") showToast("Something wrong", "error");
} }
setLoading(false) setLoading(false);
} }
} };
return ( return (
<div className="col-12 col-lg-5 col-xl-4 d-flex align-items-center p-4 p-sm-5 bg-gray-60"> <div className="col-12 col-lg-5 col-xl-4 d-flex align-items-center p-4 p-sm-5 bg-gray-60">
<div className="w-100" style={{ maxWidth: 420, margin: "0 auto" }}> <div className="w-100 m-auto" style={{ maxWidth: 420 }}>
<h4 className="mb-2">Forgot Password? 🔒</h4> <div className="d-flex align-items-center justify-content-center ">
<img src="/img/brand/marco.png" width="70" />
<Link aria-label="Go to Home Page" to="/">
<span class="app-brand-logo ">
<span class="text-blue fs-4">OnField</span>
<span className="text-green fs-4">Work</span>
<span class="text-dark fs-4">.com</span>
</span>
<br />
</Link>
</div>
<h5 className="mb-2 mt-5 ">Forgot Password? </h5>
<p className="mb-4"> <p className="mb-4">
Enter your email and we'll send you instructions to reset your password Enter your email and we'll send you instructions to reset your
password
</p> </p>
<form id="formAuthentication" className="mb-3" onSubmit={handleSubmit(onSubmit)}> <form
id="formAuthentication"
className="mb-3"
onSubmit={handleSubmit(onSubmit)}
>
<div className="mb-3 text-start"> <div className="mb-3 text-start">
<label htmlFor="email" className="form-label"> <label htmlFor="email" className="form-label">
Email Email
@ -78,26 +100,28 @@ const ForgotPasswordPage = () => {
</div> </div>
)} )}
</div> </div>
<button aria-label="Click me" className="btn btn-primary d-grid w-100"> <button
aria-label="Click me"
className="btn btn-primary d-grid w-100"
>
{loding ? "Please Wait..." : "Send Reset Link"} {loding ? "Please Wait..." : "Send Reset Link"}
</button> </button>
</form> </form>
<div className="text-center"> <div className="text-center">
<Link <Link
aria-label="Go to Login Page" aria-label="Go to Login Page"
to="/auth/login" to="/auth/login"
className="d-flex align-items-center justify-content-center" className="d-flex align-items-center justify-content-center"
> >
<i className="bx bx-chevron-left scaleX-n1-rtl bx-sm"></i> <i className="bx bx-chevron-left scaleX-n1-rtl bx-sm"></i>
Back to login Back to login
</Link> </Link>
</div> </div>
{/* Footer Text */} {/* Footer Text */}
</div> </div>
</div> </div>
); );
}; };
export default ForgotPasswordPage; export default ForgotPasswordPage;

View File

@ -45,11 +45,11 @@ const LoginPage = () => {
if (data.rememberMe) { if (data.rememberMe) {
localStorage.setItem("jwtToken", response.data.token); localStorage.setItem("jwtToken", response.data.token);
localStorage.setItem("refreshToken", response.data.refreshToken); localStorage.setItem("refreshToken", response.data.refreshToken);
removeSession("session") removeSession("session");
} else { } else {
sessionStorage.setItem("jwtToken", response.data.token); sessionStorage.setItem("jwtToken", response.data.token);
sessionStorage.setItem("refreshToken", response.data.refreshToken); sessionStorage.setItem("refreshToken", response.data.refreshToken);
removeSession("local") removeSession("local");
} }
setLoading(false); setLoading(false);
navigate("/auth/switch/org"); navigate("/auth/switch/org");
@ -78,25 +78,35 @@ const LoginPage = () => {
}, [IsLoginWithOTP]); }, [IsLoginWithOTP]);
useEffect(() => { useEffect(() => {
const token = const token =
localStorage.getItem("jwtToken") || localStorage.getItem("jwtToken") || sessionStorage.getItem("jwtToken");
sessionStorage.getItem("jwtToken");
if (token) { if (token) {
navigate("/dashboard", { replace: true }); navigate("/dashboard", { replace: true });
} }
}, []); }, []);
return ( return (
<div className="col-12 col-lg-5 col-xl-4 d-flex align-items-center p-4 p-sm-5 bg-gray-60"> <div className="col-12 col-lg-5 col-xl-4 d-flex align-items-center p-4 p-sm-5 bg-gray-60">
<div className="w-100" style={{ maxWidth: 420, margin: "0 auto" }}> <div className="w-100" style={{ maxWidth: 420, margin: "0 auto" }}>
<h4 className="mb-2">Welcome to PMS!</h4> <Link aria-label="Go to Home Page" to="/">
<span class="app-brand-logo rounded-circle app-brand-logo-border">
<img src="/img/brand/marco.png" width="70" />
</span>
<br />
<span class="text-dark fs-5">Welcome to</span> <br />
<h4 className="mb-2 ">
{" "}
<span class="text-blue ms-1">OnField</span>
<span className="text-green">Work</span>
<span class="text-dark">.com</span>
</h4>
</Link>
<p className="mb-4"> <p className="mb-4">
{IsLoginWithOTP {IsLoginWithOTP
? "Enter your email to receive a one-time password (OTP)." ? "Enter your email to receive a one-time password (OTP)."
: "Please sign in to your account and start the adventure"} : "Please sign in to your account and start the adventure"}
</p> </p>
<form id="formAuthentication" onSubmit={handleSubmit(onSubmit)}> <form id="formAuthentication" onSubmit={handleSubmit(onSubmit)}>
{/* Email */} {/* Email */}
<div className="mb-3 text-start"> <div className="mb-3 text-start">
@ -219,7 +229,6 @@ const LoginPage = () => {
</> </>
)} )}
</form> </form>
{/* Footer Text */} {/* Footer Text */}
{!IsLoginWithOTP ? ( {!IsLoginWithOTP ? (
<p className="text-center mt-3"> <p className="text-center mt-3">

View File

@ -37,33 +37,36 @@ const registerSchema = z.object({
const RegisterPage = () => { const RegisterPage = () => {
const [registered, setRegristered] = useState(false); const [registered, setRegristered] = useState(false);
const [industries, setIndustries] = useState([]); const [industries, setIndustries] = useState([]);
const [Loading,setLoading] = useState(false) const [Loading, setLoading] = useState(false);
const { const {
register, register,
handleSubmit, handleSubmit,
formState: { errors },reset formState: { errors },
reset,
} = useForm({ } = useForm({
resolver: zodResolver(registerSchema), resolver: zodResolver(registerSchema),
}); });
const onSubmit = async (data) => { const onSubmit = async (data) => {
try { try {
setLoading(true) setLoading(true);
const response = await MarketRepository.requestDemo(data); const response = await MarketRepository.requestDemo(data);
showToast("Your request has been sent successfully. Please stay in touch!"); showToast(
"Your request has been sent successfully. Please stay in touch!"
);
setRegristered(true); setRegristered(true);
setLoading(false) setLoading(false);
reset() reset();
} catch (error) { } catch (error) {
showToast(error.message, "error"); showToast(error.message, "error");
setLoading(false) setLoading(false);
} }
}; };
useEffect(() => { useEffect(() => {
fetchIndustries(); fetchIndustries();
}, []); }, []);
useEffect(() => { }, [industries]); useEffect(() => {}, [industries]);
const fetchIndustries = async () => { const fetchIndustries = async () => {
try { try {
@ -76,11 +79,20 @@ const RegisterPage = () => {
}; };
return ( return (
<> <>
<div className="col-12 col-lg-5 col-xl-4 d-flex align-items-center p-4 p-sm-5 bg-gray-60"> <div className="col-12 col-lg-5 col-xl-4 d-flex align-items-center p-4 p-sm-5 bg-gray-60">
<div className="w-100" style={{ maxWidth: 420, margin: "0 auto" }}> <div className="w-100" style={{ maxWidth: 420, margin: "0 auto" }}>
<div className="d-flex align-items-center justify-content-center ">
<h4 className="mb-2">Adventure starts here </h4> <img src="/img/brand/marco.png" width="50" />
<Link aria-label="Go to Home Page" to="/">
<span class="app-brand-logo ">
<span class="text-blue fs-4">OnField</span>
<span className="text-green fs-4">Work</span>
<span class="text-dark fs-4">.com</span>
</span>
<br />
</Link>
</div>
<h5 className="mb-2">Adventure starts here </h5>
<p className="mb-3">Make your app management easy and fun!</p> <p className="mb-3">Make your app management easy and fun!</p>
<form <form
@ -88,65 +100,64 @@ const RegisterPage = () => {
className="mb-2" className="mb-2"
onSubmit={handleSubmit(onSubmit)} onSubmit={handleSubmit(onSubmit)}
> >
<div className="row"> <div className="row">
<div className="col-12 col-sm-6 mb-2 text-start"> <div className="col-12 col-sm-6 mb-2 text-start">
<label htmlFor="organizatioinName" className="form-label"> <label htmlFor="organizatioinName" className="form-label">
Organization Name Organization Name
</label> </label>
<input <input
type="text" type="text"
className="form-control form-control-sm" className="form-control form-control-sm"
id="organizatioinName" id="organizatioinName"
{...register("organizatioinName")} {...register("organizatioinName")}
name="organizatioinName" name="organizatioinName"
placeholder="Enter your Organization Name" placeholder="Enter your Organization Name"
autoFocus autoFocus
/> />
{errors.organizatioinName && ( {errors.organizatioinName && (
<div <div
className="danger-text text-start" className="danger-text text-start"
style={{ fontSize: "12px" }} style={{ fontSize: "12px" }}
> >
{errors.organizatioinName.message} {errors.organizatioinName.message}
</div> </div>
)} )}
</div> </div>
<div className="col-12 col-sm-6 mb-2 text-start"> <div className="col-12 col-sm-6 mb-2 text-start">
<label htmlFor="email" className="form-label"> <label htmlFor="email" className="form-label">
Email Email
</label> </label>
<input <input
type="text" type="text"
className="form-control form-control-sm" className="form-control form-control-sm"
id="email" id="email"
name="email" name="email"
placeholder="Enter your email" placeholder="Enter your email"
{...register("email")} {...register("email")}
/> />
{errors.email && ( {errors.email && (
<div <div
className="danger-text text-start" className="danger-text text-start"
style={{ fontSize: "12px" }} style={{ fontSize: "12px" }}
> >
{errors.email.message} {errors.email.message}
</div> </div>
)} )}
</div> </div>
</div> </div>
<div className="mb-2 form-password-toggle text-start"> <div className="mb-2 form-password-toggle text-start">
<label className="form-label" htmlFor="contactperson"> <label className="form-label" htmlFor="contactperson">
Contact Person Contact Person
</label> </label>
<input <input
type="text" type="text"
id="contactperson" id="contactperson"
{...register("contactPerson")} {...register("contactPerson")}
className="form-control form-control-sm" className="form-control form-control-sm"
name="contactPerson" name="contactPerson"
placeholder="Contact Person" placeholder="Contact Person"
aria-describedby="contactperson" aria-describedby="contactperson"
/> />
{errors.contactPerson && ( {errors.contactPerson && (
<div <div
className="danger-text text-start" className="danger-text text-start"
@ -160,15 +171,15 @@ const RegisterPage = () => {
<label className="form-label" htmlFor="contactnumber"> <label className="form-label" htmlFor="contactnumber">
Contact Number Contact Number
</label> </label>
<input <input
type="text" type="text"
id="contactnumber" id="contactnumber"
{...register("contactNumber")} {...register("contactNumber")}
className="form-control form-control-sm" className="form-control form-control-sm"
name="contactNumber" name="contactNumber"
placeholder="Contact Number" placeholder="Contact Number"
aria-describedby="contactnumber" aria-describedby="contactnumber"
/> />
{errors.contactNumber && ( {errors.contactNumber && (
<div <div
className="danger-text text-start" className="danger-text text-start"
@ -182,14 +193,14 @@ const RegisterPage = () => {
<label className="form-label" htmlFor="contactnumber"> <label className="form-label" htmlFor="contactnumber">
About Organization About Organization
</label> </label>
<textarea <textarea
id="about" id="about"
className="form-control" className="form-control"
placeholder="About..." placeholder="About..."
aria-label="about" aria-label="about"
aria-describedby="about" aria-describedby="about"
{...register("about")} {...register("about")}
></textarea> ></textarea>
{errors.about && ( {errors.about && (
<div <div
className="danger-text text-start" className="danger-text text-start"
@ -203,20 +214,20 @@ const RegisterPage = () => {
<label className="form-label" htmlFor="oragnizationSize"> <label className="form-label" htmlFor="oragnizationSize">
Organization Size Organization Size
</label> </label>
<select <select
className="form-select form-select-sm" className="form-select form-select-sm"
id="oragnizationSize" id="oragnizationSize"
name="oragnizationSize" name="oragnizationSize"
{...register("oragnizationSize")} {...register("oragnizationSize")}
aria-label="Default select example" aria-label="Default select example"
> >
<option value="">Number of Employees</option> <option value="">Number of Employees</option>
<option value="1-10">1-10</option> <option value="1-10">1-10</option>
<option value="10-50">10-50</option> <option value="10-50">10-50</option>
<option value="50-100">50-100</option> <option value="50-100">50-100</option>
<option value="100-200">100-200</option> <option value="100-200">100-200</option>
<option value="more than 200">more than 200</option> <option value="more than 200">more than 200</option>
</select> </select>
{errors.oragnizationSize && ( {errors.oragnizationSize && (
<div <div
className="danger-text text-start" className="danger-text text-start"
@ -230,24 +241,24 @@ const RegisterPage = () => {
<label className="form-label" htmlFor="industryId"> <label className="form-label" htmlFor="industryId">
Industry Industry
</label> </label>
<select <select
className="form-select form-select-sm" className="form-select form-select-sm"
id="industryId" id="industryId"
name="industryId" name="industryId"
{...register("industryId")} {...register("industryId")}
aria-label="Default select example" aria-label="Default select example"
> >
<option value="">Select Industry</option> <option value="">Select Industry</option>
{industries.length > 0 ? ( {industries.length > 0 ? (
industries.map((item) => ( industries.map((item) => (
<option value={item.id} key={item.id}> <option value={item.id} key={item.id}>
{item.name} {item.name}
</option> </option>
)) ))
) : ( ) : (
<option disabled>Loading industries...</option> <option disabled>Loading industries...</option>
)} )}
</select> </select>
{errors.industryId && ( {errors.industryId && (
<div <div
className="danger-text text-start" className="danger-text text-start"
@ -276,7 +287,6 @@ const RegisterPage = () => {
privacy policy & terms privacy policy & terms
</Link> </Link>
</label> </label>
</div> </div>
{errors.terms && ( {errors.terms && (
<div <div
@ -291,7 +301,7 @@ const RegisterPage = () => {
aria-label="Click me " aria-label="Click me "
className="btn btn-primary d-grid w-100" className="btn btn-primary d-grid w-100"
> >
{Loading ? "Please Wait..." :" Request Demo"} {Loading ? "Please Wait..." : " Request Demo"}
</button> </button>
</form> </form>
@ -313,4 +323,4 @@ const RegisterPage = () => {
); );
}; };
export default RegisterPage; export default RegisterPage;