Merge remote-tracking branch 'origin/Subscription_Plan' into Purchase_Invoice_Management

This commit is contained in:
pramod.mahajan 2025-12-16 11:06:57 +05:30
commit bc114b6ae0
23 changed files with 545 additions and 474 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 500 KiB

After

Width:  |  Height:  |  Size: 52 KiB

BIN
public/img/hero/bg-011.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 KiB

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 1.8 MiB

View File

@ -100,7 +100,7 @@ const ExpenseAnalysis = () => {
<div className="text-end text-sm-end">
<FormProvider {...methods}>
<DateRangePicker1 />
<DateRangePicker1 pastDays="30" />
</FormProvider>
</div>
</div>
@ -152,7 +152,9 @@ const ExpenseAnalysis = () => {
className="col-6"
key={idx}
style={{
borderLeft: `3px solid ${flatColors[idx % flatColors.length]}`,
borderLeft: `3px solid ${
flatColors[idx % flatColors.length]
}`,
}}
>
<div className="d-flex flex-column text-start">
@ -177,7 +179,6 @@ const ExpenseAnalysis = () => {
</span>
</div>
</div>
))}
</div>
</div>

View File

@ -7,8 +7,14 @@ import DatePicker from "../common/DatePicker";
import { useAppForm, useAppWatch } from "../../hooks/appHooks/useAppForm";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { setProjectId } from "../../slices/localVariablesSlice";
import { useDispatch } from "react-redux";
import { useNavigate } from "react-router-dom";
const ProjectWiseTeamCount = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const { control } = useAppForm({
resolver: zodResolver(
z.object({
@ -20,6 +26,11 @@ const ProjectWiseTeamCount = () => {
},
});
const goToProject = (projectId) => () => {
dispatch(setProjectId(projectId));
navigate(`/projects/details`);
};
const selectedDate = useAppWatch({ control, name: "date" });
const { data, isLoading, isFetching, isError, error } =
@ -33,7 +44,7 @@ const ProjectWiseTeamCount = () => {
<div className="card h-100 p-3">
{/* Header */}
<div className="d-flex justify-content-between text-start mb-2">
<h5 className="card-title m-0 me-2">Project wise Employee</h5>
<h5 className="card-title m-0 me-2">Attendance by Project</h5>
<DatePicker name="date" control={control} maxDate={new Date()} />
</div>
@ -72,11 +83,21 @@ const ProjectWiseTeamCount = () => {
className="d-flex align-items-center text-wrap my-2 text-start"
style={{ width: "180px" }}
>
<a
onClick={goToProject(item.projectId)}
className="text-heading text-truncate cursor-pointer"
>
{" "}
<span className="text-heading">{item.projectName}</span>
</a>
</div>
</td>
<td className="text-center" style={{ width: 80 }}>{item.teamCount}</td>
<td className="text-center" style={{ width: 80 }} >{item.attendanceCount}</td>
<td className="text-center" style={{ width: 80 }}>
{item.teamCount}
</td>
<td className="text-center" style={{ width: 80 }}>
{item.attendanceCount}
</td>
{/* <td>{percent(item.teamCount, item.attendanceCount)}%</td> */}
</tr>
))}

View File

@ -1,4 +1,4 @@
import React from "react";
import React, { useEffect, useRef, useState } from "react";
import { useParams } from "react-router-dom";
import { useJobsProgression } from "../../hooks/useDashboard_Data";
import { SpinnerLoader } from "../common/Loader";
@ -7,55 +7,90 @@ import { useServiceProject } from "../../hooks/useServiceProject";
const ServiceJobs = () => {
const { projectId } = useParams();
const { data, isLoading, isError } = useJobsProgression(projectId);
const jobs = data || {};
const { data: projectData, isLoading: projectLoading } = useServiceProject(projectId);
const { data: projectData, isLoading: projectLoading } =
useServiceProject(projectId);
const [activeTab, setActiveTab] = useState("tab-new");
// 👇 prevents re-running auto logic after first load
const hasInitializedTab = useRef(false);
const tabMapping = [
{ id: "tab-new", label: "My Jobs", key: "myJobs" },
{ id: "tab-preparing", label: "Assigned", key: "assignedJobs" },
{ id: "tab-shipping", label: "In Progress", key: "inProgressJobs" },
{ id: "tab-preparing", label: "Assigned", key: "assignedJobs" },
];
/* ---------- INITIAL TAB SELECTION ONLY ---------- */
useEffect(() => {
if (hasInitializedTab.current || !jobs) return;
if (jobs.myJobs?.length > 0) {
setActiveTab("tab-new");
} else if (jobs.inProgressJobs?.length > 0) {
setActiveTab("tab-shipping");
} else {
setActiveTab("tab-preparing");
}
hasInitializedTab.current = true;
}, [jobs]);
return (
<div className="">
<div>
<div className="card page-min-h">
{/* Header */}
<div className="card-header d-flex justify-content-between">
<div className="card-title mb-0 text-start">
<h5 className="mb-1 fw-bold">Service Jobs</h5>
<p className="card-subtitle">
{projectLoading ? "Loading..." : projectData?.name || "All Projects"}
{projectLoading
? "Loading..."
: projectData?.name || "All Projects"}
</p>
</div>
</div>
<div className="card-body p-0">
<div className="nav-align-top">
{/* ---------------- Tabs ---------------- */}
<ul className="nav nav-tabs nav-fill rounded-0 timeline-indicator-advanced" role="tablist">
{tabMapping.map((t, index) => (
<li className="nav-item" key={t.id}>
{/* Tabs */}
<ul className="nav nav-tabs nav-fill rounded-0 timeline-indicator-advanced">
{tabMapping.map((tab) => (
<li className="nav-item" key={tab.id}>
<button
className={`nav-link ${index === 0 ? "active" : ""}`}
data-bs-toggle="tab"
data-bs-target={`#${t.id}`}
type="button"
className={`nav-link ${
activeTab === tab.id ? "active" : ""
}`}
onClick={() => setActiveTab(tab.id)}
>
{t.label}
{tab.label}
</button>
</li>
))}
</ul>
{/* ---------------- Tab Content ---------------- */}
{/* Content */}
<div className="tab-content border-0 mx-1 text-start">
{isLoading && (
<div className="text-center" style={{ height: "250px", display: "flex", justifyContent: "center", alignItems: "center" }}>
<div
className="text-center"
style={{
height: "250px",
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<SpinnerLoader />
</div>
)}
{isError && (
<p
className="text-center"
@ -69,19 +104,19 @@ const ServiceJobs = () => {
>
No data found
</p>
)}
{!isLoading &&
!isError &&
tabMapping.map((t, index) => {
const list = jobs[t.key] || [];
tabMapping.map((tab) => {
const list = jobs[tab.key] || [];
return (
<div
key={t.id}
className={`tab-pane fade ${index === 0 ? "show active" : ""}`}
id={t.id}
key={tab.id}
className={`tab-pane fade ${
activeTab === tab.id ? "show active" : ""
}`}
>
{list.length === 0 ? (
<p
@ -96,24 +131,19 @@ const ServiceJobs = () => {
>
No jobs found
</p>
) : (
<div className="job-scroll-wrapper">
{list.map((job, i) => (
<React.Fragment key={i}>
{list.map((job, index) => (
<React.Fragment key={index}>
<ul className="timeline mb-0">
{/* Assigned By */}
<li className="timeline-item ps-6 border-left-dashed">
<span className="timeline-indicator-advanced timeline-indicator-success border-0 shadow-none">
<i className="bx bx-check-circle"></i>
</span>
<div className="timeline-event ps-1">
<div className="timeline-header">
<small className="text-success text-uppercase">
Assigned By
</small>
</div>
<h6 className="my-50">{job.assignedBy}</h6>
<p className="text-body mb-0">
{formatUTCToLocalTime(job.assignedAt)}
@ -121,23 +151,23 @@ const ServiceJobs = () => {
</div>
</li>
{/* Project */}
<li className="timeline-item ps-6 border-transparent">
<span className="timeline-indicator-advanced timeline-indicator-primary border-0 shadow-none">
<i className="bx bx-map"></i>
</span>
<div className="timeline-event ps-1">
<div className="timeline-header">
<small className="text-primary text-uppercase">Project</small>
</div>
<small className="text-primary text-uppercase">
Project
</small>
<h6 className="my-50">{job.project}</h6>
<p className="text-body mb-0">{job.title}</p>
<p className="text-body mb-0">
{job.title}
</p>
</div>
</li>
</ul>
{/* Divider */}
{i < list.length - 1 && (
{index < list.length - 1 && (
<div className="border-1 border-light border-top border-dashed my-4"></div>
)}
</React.Fragment>

View File

@ -13,22 +13,12 @@ const Sidebar = () => {
id="layout-menu"
className="layout-menu menu-vertical menu bg-menu-theme "
>
<div className="app-brand" style={{ paddingLeft: "30px" }}>
<div className="app-brand" style={{ paddingLeft: "15px" }}>
<Link to="/dashboard" className="app-brand-link">
{/* <span className="app-brand-logo rounded-circle app-brand-logo-border">
<img
className="app-brand-logo-sidebar"
src="/img/brand/marco.png"
alt="logo"
aria-label="logo image"
style={{ margin: "5px", paddingRight: "5px" }}
/>
</span> */}
<span className="app-brand-logo demo d-flex align-items-center">
<img
src="/img/brand/marco.png"
width="40"
width="50"
height="40"
alt="OnFieldWork logo"
/>

View File

@ -55,7 +55,7 @@ const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
const { data: assignedServices, isLoading: servicesLoading } =
useProjectAssignedServices(projectId);
const { control } = useForm({
const { control, setValue } = useForm({
defaultValues: {
serviceId: selectedService || "",
},
@ -74,6 +74,22 @@ const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
};
useEffect(() => {
if (!servicesLoading && assignedServices?.length === 1) {
const serviceId = assignedServices[0].id;
// set form value
setValue("serviceId", serviceId, {
shouldDirty: true,
shouldValidate: true,
});
// sync redux
dispatch(setService(serviceId));
}
}, [assignedServices, servicesLoading, setValue, dispatch]);
return (
<>
{showModalBuilding && (
@ -132,31 +148,27 @@ const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
<div className="col-md-4 col-12 dataTables_length text-start py-2 px-2">
<div className="ms-4 mt-n1">
{!servicesLoading && assignedServices?.length > 0 && (
assignedServices.length > 1 ? (
<AppFormController
name="serviceId"
control={control}
render={({ field }) => (
<SelectField
label="Select Service"
options={[{ id: "", name: "All Services" }, ...(assignedServices ?? [])]}
options={[...(assignedServices ?? [])]}
placeholder="Choose a Service"
labelKey="name"
valueKey="id"
value={field.value}
onChange={(val) => {
field.onChange(val);
handleServiceChange(val);
dispatch(setService(val));
}}
isLoading={servicesLoading}
/>
)}
/>
) : (
<h5>{assignedServices[0].name}</h5>
)
)}
</div>
</div>

View File

@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import TeamEmployeeList from "./TeamEmployeeList";
import { useOrganization } from "../../../hooks/useDirectory";
import { useOrganizationsList } from "../../../hooks/useOrganization";
@ -14,26 +14,26 @@ const TeamAssignToProject = ({ closeModal }) => {
const project = useSelectedProject();
const { data, isLoading, isError, error } =
useProjectAssignedOrganizationsName(project);
const { control, watch, formState: { errors } } = useForm({
const { control, watch, setValue, formState: { errors } } = useForm({
defaultValues: { organizationId: "" },
});
useEffect(() => {
if (data?.length === 1) {
setValue("organizationId", data[0].id, {
shouldValidate: true,
shouldDirty: true,
});
}
}, [data, setValue]);
return (
<div className="container">
{/* <p className="fs-5 fs-seminbod ">Assign Employee To Project </p> */}
<h5 className="mb-4">Assign Employee To Project</h5>
<div className="row align-items-center gx-5 text-start">
<div className="col-12 col-md-6 mb-2 mt-5">
{!isLoading && data && (
<>
{data.length === 1 && (
<h5 className="mb-2">{data[0].name}</h5>
)}
{/* If multiple organizations → show dropdown */}
{data.length > 1 && (
<div className="col-12 col-md-6 mb-2 text-start">
<div className="col-12 col-md-6 mb-2">
<AppFormController
name="organizationId"
control={control}
@ -41,7 +41,7 @@ const TeamAssignToProject = ({ closeModal }) => {
render={({ field }) => (
<SelectField
label="Select Organization"
options={[...data]}
options={data ?? []}
placeholder="Choose an Organization"
required
labelKey="name"
@ -54,17 +54,9 @@ const TeamAssignToProject = ({ closeModal }) => {
)}
/>
{errors.organizationId && (
<small className="danger-text">
{errors.organizationId.message}
</small>
<small className="danger-text">{errors.organizationId.message}</small>
)}
</div>
)}
</>
)}
{/* </div> */}
</div>
<div className="col-12 col-md-6 mt-n2">
<div className="d-flex flex-column">
<label htmlFor="search" className="form-label mb-1">

View File

@ -34,7 +34,7 @@ const Teams = () => {
const [selectedEmployee, setSelectedEmployee] = useState(null);
const [deleteEmployee, setDeleteEmplyee] = useState(null);
const [activeEmployee, setActiveEmployee] = useState(false);
const { control, watch } = useForm({
const { control, watch, setValue } = useForm({
defaultValues: {
selectedService: "",
searchTerm: "",
@ -136,6 +136,18 @@ const Teams = () => {
return () => eventBus.off("employee", employeeHandler);
}, [employeeHandler]);
useEffect(() => {
if (!servicesLoading && assignedServices?.length === 1) {
const serviceId = assignedServices[0].id;
setValue("selectedService", serviceId, {
shouldDirty: true,
shouldValidate: true,
});
}
}, [assignedServices, servicesLoading, setValue]);
return (
<>
{AssigTeam && (
@ -162,13 +174,7 @@ const Teams = () => {
<div className="card-body">
<div className="row align-items-center justify-content-between mb-4 g-3">
<div className="col-md-6 col-12 d-flex flex-wrap align-items-center gap-3">
{!servicesLoading && assignedServices && (
<>
{assignedServices.length === 1 && (
<h5 className="mb-2">{assignedServices[0].name}</h5>
)}
{assignedServices.length > 1 && (
{!servicesLoading && assignedServices?.length > 0 && (
<div className="col-12 col-md-6 mb-2 text-start">
<AppFormController
name="selectedService"
@ -176,7 +182,7 @@ const Teams = () => {
render={({ field }) => (
<SelectField
label="Select Service"
options={[{ id: "", name: "All Services" }, ...assignedServices]}
options={[ ...assignedServices]}
placeholder="Choose a Service"
labelKey="name"
valueKey="id"
@ -189,8 +195,7 @@ const Teams = () => {
/>
</div>
)}
</>
)}
<div className="form-check form-switch d-flex align-items-center text-nowrap">

View File

@ -93,6 +93,7 @@ export const DateRangePicker1 = ({
resetSignal,
defaultRange = true,
maxDate = null,
pastDays = 6,
...rest
}) => {
const inputRef = useRef(null);
@ -105,7 +106,7 @@ export const DateRangePicker1 = ({
const applyDefaultDates = () => {
const today = new Date();
const past = new Date();
past.setDate(today.getDate() - 6);
past.setDate(today.getDate() - pastDays);
const format = (d) => flatpickr.formatDate(d, "d-m-Y");
const formattedStart = format(past);

View File

@ -29,11 +29,14 @@ const Progress = ({
color: color,
fontWeight: 400,
offsetY: 7,
formatter: () => `${formatFigure(completed,{notation:"compact"})} / ${formatFigure(total,{notation:"compact"})}`,
formatter: () =>
`${formatFigure(completed, {
notation: "compact",
})} / ${formatFigure(total, { notation: "compact" })}`,
},
style: {
textWrap: "wrap",
}
},
},
},
},
@ -54,7 +57,6 @@ const Progress = ({
};
export default Progress;
// const Progress = ({
// completed = 0,
// inProgress = 0,

View File

@ -9,9 +9,11 @@ const ReportsDonutCard = ({
donutClass = "",
footer = "Team members present on the site",
chartColor,
legend1 = "Completed",
legend2 = "Pending",
}) => {
return (
<div className="border-top card border-primary py-4 px-2">
<div className="border-top card border-primary py-4 px-2 h-100">
<h4 className="reports-card-title">{title}</h4>
<div className="d-flex justify-content-center align-items-center flex-column">
@ -24,7 +26,7 @@ const ReportsDonutCard = ({
completed={value}
total={total}
/>
<ReportsLegend />
<ReportsLegend legend1={legend1} legend2={legend2} />
</div>
</div>
@ -51,7 +53,6 @@ export const ReportsCard = ({
<div className="d-flex justify-content-start align-items-center flex-column">
<div className="d-inline-flex flex-row align-items-center gap-12 gap-md-3">
<ReportsLegend />
</div>
</div>
@ -62,5 +63,3 @@ export const ReportsCard = ({
</div>
);
};

View File

@ -1,19 +1,14 @@
const ReportsLegend = () => {
const ReportsLegend = ({legend1, legend2}) => {
return (
<div className=" d-inline-flex flex-column text-start gap-2 pe-5">
<div className=" d-flex align-items-center gap-2">
<span className="reports-legend-color reports-legend-green"></span>
<span>Completed</span>
<span>{legend1}</span>
</div>
<div className=" d-flex align-items-center gap-2">
<span className="reports-legend-color reports-legend-blue"></span>
<span>In Progress</span>
</div>
<div className=" d-flex align-items-center gap-2">
<span className="reports-legend-color reports-legend-gray"></span>
<span>Pending</span>
<span>{legend2}</span>
</div>
</div>
);

View File

@ -4,6 +4,7 @@ import Progress from "./Progress";
import { formatUTCToLocalTime } from "../../utils/dateUtils";
import { localToUtc } from "../../utils/appUtils";
import ReportsDonutCard, { ReportsCard } from "./ReportsDonutCard";
import { SpinnerLoader } from "../common/Loader";
const ReportDPR = ({ project, date }) => {
const { data, isLoading, isError, error } = useProjectReportByProject(
@ -12,36 +13,50 @@ const ReportDPR = ({ project, date }) => {
);
return (
<>
<div className="card py-2">
<div className="card py-2 mb-5">
<div className="d-flex text-start px-2 mt-2">
Project Status Reported - Generated at{" "}
{formatUTCToLocalTime(data?.date, true)}
</div>
{/* <!-- Status Cards */}
<div className="d-flex justify-content-between flex-wrap gap-5 px-3 py-4">
<div className="row px-5 py-4">
<div className="col-6 col-md-3" style={{ minHeight: "255px" }}>
{" "}
<ReportsDonutCard
title={"TODAY'S ATTENDANCE"}
total={data?.totalEmployees}
value={data?.todaysAttendances}
legend1="Today's Attendance"
legend2="Total Employees"
/>
</div>
<div className="col-6 col-md-3" style={{ minHeight: "255px" }}>
{" "}
<ReportsDonutCard
title={"DAILY TASKS COMPLETED"}
total={data?.totalCompletedTask}
value={data?.totalPlannedWork}
chartColor={"blue"}
footer=""
legend1="Completed Work"
legend2="Planned Work"
/>
</div>
<div className="col-6 col-md-3" style={{ minHeight: "255px" }}>
{" "}
<ReportsDonutCard
title={"PROJECT COMPLETION STATUS"}
total={data?.totalPlannedWork}
value={data?.totalCompletedWork}
chartColor={"green"}
footer=""
legend1="Completed Work"
legend2="Planned Work"
/>
<div className="border-top card border-warning py-4 px-2">
</div>
<div className="col-6 col-md-3" style={{ minHeight: "255px" }}>
<div className="border-top card border-warning py-4 px-2 h-100">
<h5 className="reports-card-title">Attendance Pending Report</h5>
<div className="d-flex flex-column gap-2">
<div className="d-flex justify-content-between">
@ -53,7 +68,10 @@ const ReportDPR = ({ project, date }) => {
</div>
<div className="d-flex justify-content-between">
<span className="text-secondry"> Checking</span>{" "}
<span className="text-secondry"> {data?.checkoutPending}</span>{" "}
<span className="text-secondry">
{" "}
{data?.checkoutPending}
</span>{" "}
</div>
<div className="d-flex justify-content-between">
<span className="text-secondry"> Total Employee</span>{" "}
@ -65,16 +83,21 @@ const ReportDPR = ({ project, date }) => {
</div>
</div>
</div>
<div className="row px-3">
</div>
<div className="row px-5 pb-5">
<div className="col-4">
<div className="reports-card">
<div className="reports-card h-100" style={{ minHeight: "250px" }}>
{/* {/* <!-- Row 1: Header */}
<div>
<h4 className="reports-card-title">Team Strength on Site</h4>
</div>
<table style={{ width: "100%" }}>
<tbody>
{data?.teamOnSite
{data?.teamOnSite?.filter(
(item) => item?.numberofEmployees > 0
).length > 0 ? (
data?.teamOnSite
?.filter((item) => item?.numberofEmployees > 0)
?.map((member, index) => (
<tr key={index}>
@ -86,14 +109,21 @@ const ReportDPR = ({ project, date }) => {
{member?.numberofEmployees}
</td>
</tr>
))}
))
) : (
<tr>
<td colSpan="2" style={{ textAlign: "center" }}>
No Records Available
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
<div className="col-8">
{/* {/* <!-- Activities */}
<div className="reports-card">
<div className="reports-card h-100" style={{ minHeight: "250px" }}>
<h4 className="reports-card-title">Employee In-Out Report </h4>
<table className="reports-table">
<thead>
@ -105,7 +135,8 @@ const ReportDPR = ({ project, date }) => {
</tr>
</thead>
<tbody>
{data?.performedAttendance?.map((att, index) => (
{data?.performedAttendance?.length > 0 ? (
data?.performedAttendance?.map((att, index) => (
<tr key={att.index + att.name}>
<td>{att.name}</td>
<td>{att.roleName}</td>
@ -116,7 +147,22 @@ const ReportDPR = ({ project, date }) => {
: "--"}
</td>
</tr>
))}
))
) : (
<tr>
<td
colSpan="4"
className="text-center py-4 border-0"
style={{ height: "200px" }}
>
{isLoading ? (
<SpinnerLoader />
) : (
<div className="py-8">No Records Available</div>
)}
</td>
</tr>
)}
</tbody>
</table>
</div>

View File

@ -136,6 +136,15 @@ const AttendancePage = () => {
: []),
];
// --- END: Tab Configuration Array
useEffect(() => {
if (!orgLoading && organizations?.length === 1) {
setAppliedFilters((prev) => ({
...prev,
selectedOrganization: organizations[0].id,
}));
}
}, [orgLoading, organizations]);
return (
<>
@ -173,19 +182,21 @@ const AttendancePage = () => {
{/* Tabs header with search and filter */}
<div className="nav-align-top nav-tabs-shadow bg-white border-bottom pt-5">
<div className="row align-items-center g-0 mb-3 mb-md-0 mx-1 mx-sm-5">
{/* Tabs Buttons - Now col-12 col-md-6 */}
<div className="col-12 col-md-6 mt-1">
{/* Tabs Buttons */}
<div className="col-12 col-md mt-1">
<ul className="nav nav-tabs" role="tablist">
{tabsData.map((tab) => (
<li
className={`nav-item ${tab.id === "regularization" && !DoRegularized ? "d-none" : ""
}`}
className={`nav-item ${tab.id === "regularization" && !DoRegularized
? "d-none"
: ""
}`} // Keep the d-none logic for regularization, although it's filtered above
key={tab.id}
>
<button
type="button"
className={`nav-link ${activeTab === tab.id ? "active" : ""} fs-6`}
className={`nav-link ${activeTab === tab.id ? "active" : ""
} fs-6`}
onClick={() => handleTabChange(tab.id)}
data-bs-toggle="tab"
data-bs-target={`#navs-top-${tab.id}`}
@ -197,20 +208,10 @@ const AttendancePage = () => {
</ul>
</div>
{/* Search + Organization Filter - Now col-12 col-md-6 */}
<div className="col-12 col-md-6 mb-2 mt-md-0">
<div className="row g-0">
{/* Organization */}
{/* Search + Organization filter */}
<div className="col-12 col-md-auto mb-2 mt-md-0 ms-md-auto nav">
<div className="row g-2">
<div className="col-12 col-sm-6">
{/* If only 1 organization → show name only */}
{!orgLoading && organizations?.length === 1 && (
<h5 className="m-0 ">{organizations[0].name}</h5>
)}
{/* If multiple organizations → show dropdown */}
{!orgLoading && organizations?.length > 1 && (
<select
className="form-select form-select-sm"
value={appliedFilters.selectedOrganization}
@ -229,19 +230,7 @@ const AttendancePage = () => {
</option>
))}
</select>
)}
{/* Loading case (optional) */}
{orgLoading && (
<div className="form-control form-control-sm disabled">
Loading...
</div>
)}
</div>
{/* Search */}
<div className="col-12 col-sm-6">
<input
type="text"
@ -251,14 +240,11 @@ const AttendancePage = () => {
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
</div>
</div>
</div>
</div>
{/* Tab Content */}
<div className="tab-content attedanceTabs py-0 px-1 px-sm-3 pb-10">
{selectedProject ? (

View File

@ -15,7 +15,7 @@ const TaskPlanning = () => {
const selectedProject = useSelectedProject();
const selectedService = useCurrentService();
const dispatch = useDispatch();
const { control } = useForm({
const { control,setValue } = useForm({
defaultValues: {
serviceFilter: selectedService ?? ""
},
@ -39,6 +39,20 @@ const TaskPlanning = () => {
return <div className="text-center py-5">Loading...</div>;
}
useEffect(() => {
if (!servicesLoading && data?.length === 1) {
const serviceId = data[0].id;
setValue("serviceFilter", serviceId, {
shouldValidate: true,
shouldDirty: true,
});
dispatch(setService(serviceId));
}
}, [servicesLoading, data, setValue, dispatch]);
return (
<div className="container-fluid">
<Breadcrumb
@ -49,26 +63,16 @@ const TaskPlanning = () => {
/>
<div className="card py-2 page-min-h">
<div className="col-sm-4 col-md-3 col-12 px-4 py-2 ms-1 text-start">
{/* When no services available */}
<div className="col-sm-4 col-md-3 col-12 px-4 py-2 text-start">
{data?.length === 0 ? (
<p className="badge bg-label-secondary m-0"></p>
) : (
<>
{/* When exactly 1 service assigned → display heading */}
{data?.length === 1 && (
<h5 className="mb-3">{data[0].name}</h5>
)}
{/* When multiple services → show dropdown */}
{data?.length > 1 && (
<AppFormController
name="serviceFilter"
control={control}
render={({ field }) => (
<SelectField
label="Select Services"
label="Services"
placeholder="All Services"
options={[{ id: "", name: "All Services" }, ...(data ?? [])]}
labelKey="name"
@ -84,9 +88,6 @@ const TaskPlanning = () => {
)}
/>
)}
</>
)}
</div>

View File

@ -46,7 +46,7 @@ const DailyProgrssReport = () => {
filter,
};
const { control } = useForm({
const { control,setValue } = useForm({
defaultValues: {
serviceFilter: ""
}
@ -85,6 +85,22 @@ const DailyProgrssReport = () => {
return updated;
});
};
useEffect(() => {
if (!isLoading && data?.length === 1) {
const serviceId = data[0].id;
// set form value
setValue("serviceFilter", serviceId, {
shouldDirty: true,
shouldValidate: true,
});
// set local state
setService(serviceId);
}
}, [isLoading, data, setValue]);
return (
<div className="container-fluid">
<DailyProgrssContext.Provider value={contextDispatcher}>
@ -120,44 +136,31 @@ const DailyProgrssReport = () => {
/>
<div className="card page-min-h p-5">
<div className="text-start">
{/* Service Heading or Dropdown */}
{!isLoading && data && (
<>
{/* If only 1 service assigned → show heading */}
{data.length === 1 && (
<h5 className="mb-3">{data[0].name}</h5>
)}
{/* If multiple services → show dropdown */}
{data.length > 1 && (
<div className="col-12 col-md-3 mb-3 text-start">
{data?.length > 0 && (
<div className="col-sm-4 col-md-3 col-12 text-start">
<AppFormController
name="serviceFilter"
control={control}
defaultValue={service ?? ""}
render={({ field }) => (
<SelectField
label="Select Service"
options={[{ id: "", name: "All Services" }, ...data]}
placeholder="Choose a Service"
label="Services"
options={[{ id: "", name: "All Services" }, ...(data ?? [])]}
placeholder="Select Service"
labelKey="name"
valueKey="id"
value={field.value}
isLoading={isLoading}
value={field.value}
onChange={(val) => {
field.onChange(val);
setService(val);
}}
className="w-100"
className="m-0"
/>
)}
/>
</div>
)}
</>
)}
</div>
<div>
<TaskReportList filter={filter}

View File

@ -86,7 +86,7 @@ const LandingPage = () => {
<li className="nav-item ms-1">
<a
className="btn btn-sm btn-green btn-ovel-small px-3 my-1"
href="#"
href="/auth/reqest/demo"
>
Request For Demo
</a>
@ -162,7 +162,7 @@ const LandingPage = () => {
Make data-driven decisions with real-time project analytics.
</p>
<a
href="#"
href="/auth/reqest/demo"
className="btn btn-green btn-square-small btn-lg mt-3 p-3"
>
View Demo
@ -185,7 +185,7 @@ const LandingPage = () => {
Eliminate Paper Receipts. Take Control of Your Cash Flow.
</p>
<a
href="#"
href="/auth/reqest/demo"
className="btn btn-green btn-square-small btn-lg mt-3 p-3"
>
View Demo
@ -320,65 +320,6 @@ const LandingPage = () => {
</p>
{/* <SubscriptionPlans/> */}
<SubscriptionPlans></SubscriptionPlans>
{/* <div className="row g-4 justify-content-center hidden">
<div className="col-md-4">
<div className="card pricing-card border-0 shadow-sm">
<div className="card-body">
<h5 className="text-green fw-bold">Starter</h5>
<h2>
499<span className="fs-6 text-muted">/month</span>
</h2>
<ul className="list-unstyled mt-3 mb-4 text-muted">
<li>Up to 10 users</li>
<li>Basic reporting</li>
<li>Project tracking</li>
</ul>
<a href="#" className="btn btn-outline-success">
Request Demo
</a>
</div>
</div>
</div>
<div className="col-md-4">
<div className="card pricing-card border-primary shadow-lg">
<div className="card-body">
<h5 className="text-green fw-bold">Professional</h5>
<h2>
999<span className="fs-6 text-muted">/month</span>
</h2>
<ul className="list-unstyled mt-3 mb-4 text-muted">
<li>Unlimited projects</li>
<li>Expense & attendance tracking</li>
<li>Advanced analytics</li>
</ul>
<a
href="https://ofw.marcoaiot.com/auth/reqest/demo"
className="btn btn-green btn-square-small"
>
Request Demo
</a>
</div>
</div>
</div>
<div className="col-md-4">
<div className="card pricing-card border-0 shadow-sm">
<div className="card-body">
<h5 className="text-green fw-bold">Enterprise</h5>
<h2>Custom</h2>
<ul className="list-unstyled mt-3 mb-4 text-muted">
<li>Dedicated support</li>
<li>Custom integrations</li>
<li>Private cloud hosting</li>
</ul>
<a href="#" className="btn btn-outline-success">
Request Demo
</a>
</div>
</div>
</div>
</div> */}
</div>
</section>
{/* <!-- About --> */}

View File

@ -9,7 +9,8 @@ const SubscriptionPlans = () => {
const [frequency, setFrequency] = useState(1);
const { data, isLoading, isError, error } = useSubscription(frequency);
const [loading, setLoading] = useState(false);
const [isOpen, setIsOpen] = useState(true);
console.log(data);
const frequencyLabel = (freq) => {
switch (freq) {
case 0:
@ -35,8 +36,7 @@ const SubscriptionPlans = () => {
<button
key={idx}
type="button"
className={`btn btn-${
frequency === idx ? "primary" : "outline-secondary"
className={`btn btn-${frequency === idx ? "primary" : "outline-secondary"
}`}
onClick={() => setFrequency(idx)}
>
@ -64,7 +64,7 @@ const SubscriptionPlans = () => {
<p>{error.name}</p>
</div>
) : (
data.map((plan) => (
data.map((plan, index) => (
<div key={plan.id} className="col-xl-4 col-lg-6 col-md-6">
<div className="card h-100 shadow-lg border-0 p-3 text-center p-10">
{/* Header */}
@ -102,24 +102,69 @@ const SubscriptionPlans = () => {
<h6 className="fw-bold text-uppercase border-top pt-3 mb-3 text-center">
Features
</h6>
<ul className="list-unstyled text-start mb-4 ms-7 fs-6">
<div className="accordion" id={`planFeatures-${plan.id}`}>
{plan.features?.modules &&
Object.values(plan.features.modules).map((mod) =>
mod && mod.name ? (
<li
key={mod.id}
className="d-flex align-items-center mb-2"
Object.entries(plan.features.modules)
.sort(([, a], [, b]) => Number(b.enabled) - Number(a.enabled))
.map(([key, mod]) => {
if (!mod || !mod.name) return null;
const isFirst = index === 0;
return (
<div className="accordion-item mb-2" key={`${plan.id}-${mod.id}`}>
<h2 id={`heading-${plan.id}-${mod.id}`} className="accordion-header">
<button
className="accordion-button py-2 d-flex justify-content-between align-items-center"
type="button"
data-bs-toggle="collapse"
data-bs-target={`#collapse-${plan.id}-${mod.id}`}
aria-expanded={`${isFirst ? "true" : "false"}`}
aria-controls={`collapse-${plan.id}-${mod.id}`}
>
<span className="fw-semibold d-flex align-items-center">
{mod.enabled ? (
<i className="fa-regular fa-circle-check text-success me-2"></i>
) : (
<i className="fa-regular fa-circle-xmark text-danger me-2"></i>
)}
{mod.name}
</span>
<i className="bx bx-chevron-down ms-2"></i>
</button>
</h2>
<div
id={`collapse-${plan.id}-${mod.id}`}
className={`accordion-collapse collapse ${isFirst ? "show" : ""}`}
aria-labelledby={`heading-${plan.id}-${mod.id}`}
>
<div className="accordion-body py-2">
{mod.features?.length > 0 ? (
<ul className="list-unstyled ms-2">
{mod.features.map((feat) => (
<li key={feat.id} className="d-flex align-items-start mb-1">
<i
className={`bx bxs-circle ${mod.enabled ? "text-success" : "text-danger"
} me-2 mt-1`}
style={{ fontSize: "8px" }}
></i>
<span>{feat.name}</span>
</li>
) : null
)}
))}
</ul>
) : (
<p className="text-muted small mb-0">No additional features</p>
)}
</div>
</div>
</div>
);
})}
</div>
{/* Button */}
<div className="mt-auto">
@ -138,6 +183,7 @@ const SubscriptionPlans = () => {
</div>
</div>
</div>
))
)}
</div>

View File

@ -1,5 +1,5 @@
import React from 'react'
import LoginWithOtp from './LoginWithOtp'
import React from "react";
import LoginWithOtp from "./LoginWithOtp";
const MainLoginWithOTPPage = () => {
return (
@ -8,7 +8,7 @@ const MainLoginWithOTPPage = () => {
<div className="d-none d-lg-flex col-lg-7 col-xl-8 align-items-center p-5">
<div className="w-100 d-flex justify-content-center">
<img
src="/img/illustrations/worker_03.png"
src="/img/illustrations/registration.jpg"
className="img-fluid"
alt="Login image"
width="70%"
@ -20,7 +20,7 @@ const MainLoginWithOTPPage = () => {
<LoginWithOtp />
</div>
</>
)
}
);
};
export default MainLoginWithOTPPage
export default MainLoginWithOTPPage;

View File

@ -1,5 +1,5 @@
import React from 'react'
import ResetPasswordPage from './ResetPassword'
import React from "react";
import ResetPasswordPage from "./ResetPassword";
const MainResetPasswordPage = () => {
return (
@ -8,7 +8,7 @@ const MainResetPasswordPage = () => {
<div className="d-none d-lg-flex col-lg-7 col-xl-8 align-items-center p-5">
<div className="w-100 d-flex justify-content-center">
<img
src="/img/illustrations/worker_03.png"
src="/img/illustrations/registration.jpg"
className="img-fluid"
alt="Login image"
width="70%"
@ -20,7 +20,7 @@ const MainResetPasswordPage = () => {
<ResetPasswordPage />
</div>
</>
)
}
);
};
export default MainResetPasswordPage
export default MainResetPasswordPage;