Merge remote-tracking branch 'origin/Subscription_Plan' into Purchase_Invoice_Management
|
Before Width: | Height: | Size: 500 KiB After Width: | Height: | Size: 52 KiB |
BIN
public/img/hero/bg-011.jpg
Normal file
|
After Width: | Height: | Size: 500 KiB |
|
Before Width: | Height: | Size: 201 KiB After Width: | Height: | Size: 201 KiB |
|
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 1.8 MiB |
@ -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>
|
||||
|
||||
@ -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>
|
||||
))}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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"
|
||||
/>
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 ? (
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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 --> */}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||