Compare commits

..

7 Commits

56 changed files with 669 additions and 1776 deletions

7
package-lock.json generated
View File

@ -18,7 +18,6 @@
"apexcharts": "^4.5.0", "apexcharts": "^4.5.0",
"axios": "^1.7.9", "axios": "^1.7.9",
"axios-retry": "^4.5.0", "axios-retry": "^4.5.0",
"crypto-js": "^4.2.0",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"dotenv": "^16.4.7", "dotenv": "^16.4.7",
"dotenv-webpack": "^8.1.0", "dotenv-webpack": "^8.1.0",
@ -2415,12 +2414,6 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/crypto-js": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
"license": "MIT"
},
"node_modules/csstype": { "node_modules/csstype": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",

View File

@ -21,7 +21,6 @@
"apexcharts": "^4.5.0", "apexcharts": "^4.5.0",
"axios": "^1.7.9", "axios": "^1.7.9",
"axios-retry": "^4.5.0", "axios-retry": "^4.5.0",
"crypto-js": "^4.2.0",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"dotenv": "^16.4.7", "dotenv": "^16.4.7",
"dotenv-webpack": "^8.1.0", "dotenv-webpack": "^8.1.0",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 500 KiB

Binary file not shown.

Before

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

@ -31,7 +31,7 @@ const selectedProjectId = useSelectedProject()
<div className="card-header mb-1 pb-0"> <div className="card-header mb-1 pb-0">
<div className="d-flex flex-wrap justify-content-between align-items-center"> <div className="d-flex flex-wrap justify-content-between align-items-center">
<div className="card-title mb-0 text-start"> <div className="card-title mb-0 text-start">
<h5 class="card-title m-0 me-2">Attendance</h5> <h5 className="mb-1">Attendance</h5>
<p className="card-subtitle">Daily Attendance Data</p> <p className="card-subtitle">Daily Attendance Data</p>
</div> </div>

View File

@ -104,7 +104,7 @@ const AttendanceOverview = () => {
{/* Header */} {/* Header */}
<div className="d-flex mt-2 justify-content-between align-items-center mb-3"> <div className="d-flex mt-2 justify-content-between align-items-center mb-3">
<div className="card-title mb-0 text-start"> <div className="card-title mb-0 text-start">
<h5 className="mb-1 fw-semibold">Attendance Overview</h5> <h5 className="mb-1 fw-bold">Attendance Overview</h5>
<p className="card-subtitle">Role-wise present count</p> <p className="card-subtitle">Role-wise present count</p>
</div> </div>
<div className="d-flex gap-2"> <div className="d-flex gap-2">

View File

@ -78,7 +78,7 @@ const CollectionOverview = ({ data, isLoading }) => {
tooltip: { tooltip: {
custom: ({ series, seriesIndex, dataPointIndex }) => { custom: ({ series, seriesIndex, dataPointIndex }) => {
return ` return `
<div className="px-2 py-1"> <div class="px-2 py-1">
<strong>${labels[dataPointIndex]}</strong><br> <strong>${labels[dataPointIndex]}</strong><br>
${series[seriesIndex][dataPointIndex].toLocaleString()} ${series[seriesIndex][dataPointIndex].toLocaleString()}
</div> </div>
@ -235,7 +235,7 @@ export const TopicBarChart = ({ data,isLoading }) => {
enabled: true, enabled: true,
custom: ({ series, seriesIndex, dataPointIndex }) => { custom: ({ series, seriesIndex, dataPointIndex }) => {
return ` return `
<div className="px-3 py-2"> <div class="px-3 py-2">
<span>${series[seriesIndex][ <span>${series[seriesIndex][
dataPointIndex dataPointIndex
].toLocaleString()}</span> ].toLocaleString()}</span>
@ -256,8 +256,8 @@ export const TopicBarChart = ({ data,isLoading }) => {
return ( return (
<div className="row p-2"> <div className="row p-2">
<div className="col-md-8"> <div className="col-md-8">
<div className="card-header d-flex align-items-center justify-content-between"> <div class="card-header d-flex align-items-center justify-content-between">
<h5 className="card-title m-0 me-2">Collection Overview</h5> <h5 class="card-title m-0 me-2">Collection Overview</h5>
</div> </div>
<div className="w-100 d-flex align-items-center text-start px-6"> <div className="w-100 d-flex align-items-center text-start px-6">
<p className="text-secondary fs-6 m-0">Due Amount</p> <p className="text-secondary fs-6 m-0">Due Amount</p>
@ -274,7 +274,7 @@ export const TopicBarChart = ({ data,isLoading }) => {
</div> </div>
<div className="col-md-4 d-flex flex-column gap-2"> <div className="col-md-4 d-flex flex-column gap-2">
<div className="card-header d-flex align-items-end justify-content-between"></div> <div class="card-header d-flex align-items-end justify-content-between"></div>
<div className="p-1 m-1 text-start"> <div className="p-1 m-1 text-start">
<small className="fw-medium">Overdue Days</small> <small className="fw-medium">Overdue Days</small>
</div> </div>

View File

@ -27,8 +27,6 @@ import {
} from "../../utils/constants"; } from "../../utils/constants";
import CollectionOverview, { TopicBarChart } from "./CollectionOverview"; import CollectionOverview, { TopicBarChart } from "./CollectionOverview";
import { CollectionOverviewSkeleton } from "./CollectionOverviewSkeleton"; import { CollectionOverviewSkeleton } from "./CollectionOverviewSkeleton";
import ProjectWiseTeamCount from "./ProjectWiseTeamCount";
const Dashboard = () => { const Dashboard = () => {
// Get the selected project ID from Redux store // Get the selected project ID from Redux store
@ -43,24 +41,24 @@ const Dashboard = () => {
<div className="container-fluid mt-5"> <div className="container-fluid mt-5">
<div className="row gy-4"> <div className="row gy-4">
{isAllProjectsSelected && ( {isAllProjectsSelected && (
<div className="col-sm-6 col-lg-6"> <div className="col-sm-6 col-lg-4">
<Projects /> <Projects />
</div> </div>
)} )}
<div <div
className={`${!isAllProjectsSelected ? "col-sm-6 col-lg-6" : "col-sm-6 col-lg-6" className={`${!isAllProjectsSelected ? "col-sm-6 col-lg-6" : "col-sm-6 col-lg-4"
}`} }`}
> >
<Teams /> <Teams />
</div> </div>
{!isAllProjectsSelected && ( <div <div
className={`${!isAllProjectsSelected ? "col-sm-6 col-lg-6" : "col-sm-6 col-lg-6" className={`${!isAllProjectsSelected ? "col-sm-6 col-lg-6" : "col-sm-6 col-lg-4"
}`} }`}
> >
<TasksCard /> <TasksCard />
</div>)} </div>
<div className="col-12 col-xl-4 col-md-6"> <div className="col-12 col-xl-4 col-md-6">
<div className="card "> <div className="card ">
<ExpenseStatus /> <ExpenseStatus />
@ -86,13 +84,10 @@ const Dashboard = () => {
</div> </div>
{!isAllProjectsSelected && {!isAllProjectsSelected &&
(canRegularize || canTeamAttendance || canSelfAttendance) && ( (canRegularize || canTeamAttendance || canSelfAttendance) && (
<div className="col-12 col-md-6 mb-sm-0 mb-4"> <div className="col-12 col-md-8 mb-sm-0 mb-4">
<AttendanceOverview /> <AttendanceOverview />
</div> </div>
)} )}
{isAllProjectsSelected && <div className="col-12 col-md-6 mb-sm-0 mb-4">
<ProjectWiseTeamCount />
</div>}
{!isAllProjectsSelected && ( {!isAllProjectsSelected && (
<div className="col-xxl-4 col-lg-4"> <div className="col-xxl-4 col-lg-4">
@ -102,7 +97,7 @@ const Dashboard = () => {
</div> </div>
)} )}
{isAllProjectsSelected && ( {isAllProjectsSelected && (
<div className="col-12 col-md-4 mb-sm-0 mb-4"> <div className="col-12 col-md-6 mb-sm-0 mb-4">
<ServiceJobs /> <ServiceJobs />
</div> </div>
)} )}
@ -123,3 +118,80 @@ const Dashboard = () => {
}; };
export default Dashboard; export default Dashboard;
// <div class="col-12 col-xl-8">
// <div class="card h-100">
// <div class="card-header d-flex align-items-center justify-content-between">
// <h5 class="card-title m-0 me-2">Topic you are interested in</h5>
// <div class="dropdown">
// <button
// class="btn p-0"
// type="button"
// id="topic"
// data-bs-toggle="dropdown"
// aria-haspopup="true"
// aria-expanded="false">
// <i class="bx bx-dots-vertical-rounded bx-lg text-muted"></i>
// </button>
// <div class="dropdown-menu dropdown-menu-end" aria-labelledby="topic">
// <a class="dropdown-item" href="javascript:void(0);">Highest Views</a>
// <a class="dropdown-item" href="javascript:void(0);">See All</a>
// </div>
// </div>
// </div>
// <div class="card-body row g-3">
// <div class="col-md-8">
// <div id="horizontalBarChart"></div>
// </div>
// <div class="col-md-4 d-flex justify-content-around align-items-center">
// <div>
// <div class="d-flex align-items-baseline">
// <span class="text-primary me-2"><i class="bx bxs-circle bx-12px"></i></span>
// <div>
// <p class="mb-0">UI Design</p>
// <h5>35%</h5>
// </div>
// </div>
// <div class="d-flex align-items-baseline my-12">
// <span class="text-success me-2"><i class="bx bxs-circle bx-12px"></i></span>
// <div>
// <p class="mb-0">Music</p>
// <h5>14%</h5>
// </div>
// </div>
// <div class="d-flex align-items-baseline">
// <span class="text-danger me-2"><i class="bx bxs-circle bx-12px"></i></span>
// <div>
// <p class="mb-0">React</p>
// <h5>10%</h5>
// </div>
// </div>
// </div>
// <div>
// <div class="d-flex align-items-baseline">
// <span class="text-info me-2"><i class="bx bxs-circle bx-12px"></i></span>
// <div>
// <p class="mb-0">UX Design</p>
// <h5>20%</h5>
// </div>
// </div>
// <div class="d-flex align-items-baseline my-12">
// <span class="text-secondary me-2"><i class="bx bxs-circle bx-12px"></i></span>
// <div>
// <p class="mb-0">Animation</p>
// <h5>12%</h5>
// </div>
// </div>
// <div class="d-flex align-items-baseline">
// <span class="text-warning me-2"><i class="bx bxs-circle bx-12px"></i></span>
// <div>
// <p class="mb-0">SEO</p>
// <h5>9%</h5>
// </div>
// </div>
// </div>
// </div>
// </div>
// </div>
// </div>

View File

@ -52,18 +52,6 @@ const ExpenseAnalysis = () => {
legend: { show: false }, legend: { show: false },
dataLabels: { enabled: true, formatter: (val) => `${val.toFixed(0)}%` }, dataLabels: { enabled: true, formatter: (val) => `${val.toFixed(0)}%` },
colors: flatColors, colors: flatColors,
tooltip: {
y: {
formatter: function (value) {
return formatCurrency(value);
},
},
x: {
formatter: function (label) {
return label;
},
},
},
plotOptions: { plotOptions: {
pie: { pie: {
donut: { donut: {
@ -100,7 +88,7 @@ const ExpenseAnalysis = () => {
<div className="text-end text-sm-end"> <div className="text-end text-sm-end">
<FormProvider {...methods}> <FormProvider {...methods}>
<DateRangePicker1 pastDays="30" /> <DateRangePicker1 />
</FormProvider> </FormProvider>
</div> </div>
</div> </div>
@ -152,9 +140,7 @@ const ExpenseAnalysis = () => {
className="col-6" className="col-6"
key={idx} key={idx}
style={{ style={{
borderLeft: `3px solid ${ borderLeft: `3px solid ${flatColors[idx % flatColors.length]}`,
flatColors[idx % flatColors.length]
}`,
}} }}
> >
<div className="d-flex flex-column text-start"> <div className="d-flex flex-column text-start">
@ -179,6 +165,7 @@ const ExpenseAnalysis = () => {
</span> </span>
</div> </div>
</div> </div>
))} ))}
</div> </div>
</div> </div>

View File

@ -92,40 +92,45 @@ const ExpenseByProject = () => {
<div className="card shadow-sm h-100 rounded "> <div className="card shadow-sm h-100 rounded ">
{/* Header */} {/* Header */}
<div className="card-header"> <div className="card-header">
<div className="d-block justify-content-between align-items-center mb-1 mt-1"> <div className="d-flex justify-content-between align-items-center mb-1 mt-1">
<div className="text-start"> <div className="text-start">
<h5 className="mb-1 me-6 card-title">Monthly Expense </h5> <h5 className="mb-1 me-6 card-title">Monthly Expense -</h5>
<div className="row w-100"> <p className="card-subtitle m-0">{projectName}</p>
<div className="col-6"> <p className="card-subtitle m-0">{projectName}</p></div> </div>
<div className="col-6 d-flex justify-content-between align-items-center px-0"> <div className="btn-group mb-5 ms-n8">
<button
className="btn btn-sm dropdown-toggle fs-5"
type="button"
<select data-bs-toggle="dropdown"
className="form-select form-select-sm ms-auto mt-sm-0" aria-expanded="false"
value={selectedType}
onChange={(e) => setSelectedType(e.target.value)}
disabled={typeLoading}
style={{ maxWidth: "200px" }}
> >
<option value="">All Categories</option> {viewMode}
{expenseCategories?.map((type) => ( </button>
<option key={type.id} value={type.id}> <ul className="dropdown-menu dropdown-menu-end ">
{type.name} <li>
</option> <button
))} className="dropdown-item"
</select> onClick={() => {
setViewMode("Category");
setSelectedType("");
}}
>
Category
</button>
</li>
<li>
<button
className="dropdown-item"
onClick={() => {
setViewMode("Project");
setSelectedType("");
}}
>
Project
</button>
</li>
</ul>
</div> </div>
</div>
</div>
</div> </div>
{/* Range Buttons + Expense Dropdown */} {/* Range Buttons + Expense Dropdown */}
@ -143,7 +148,22 @@ const ExpenseByProject = () => {
{item} {item}
</button> </button>
))} ))}
{viewMode === "Category" && (
<select
className="form-select form-select-sm ms-auto mb-3 mt-1 mt-sm-0"
value={selectedType}
onChange={(e) => setSelectedType(e.target.value)}
disabled={typeLoading}
style={{ maxWidth: "200px" }}
>
<option value="">All Types</option>
{expenseCategories?.map((type) => (
<option key={type.id} value={type.id}>
{type.name}
</option>
))}
</select>
)}
</div> </div>
</div> </div>

View File

@ -92,7 +92,7 @@ const ProjectProgressChart = ({
<div className="d-flex flex-wrap justify-content-between align-items-start mb-2"> <div className="d-flex flex-wrap justify-content-between align-items-start mb-2">
{/* Left: Title */} {/* Left: Title */}
<div className="card-title text-start"> <div className="card-title text-start">
<h5 className="mb-1 fw-semibold">Project Progress</h5> <h5 className="mb-1 fw-bold">Project Progress</h5>
<p className="card-subtitle">Progress Overview by Project</p> <p className="card-subtitle">Progress Overview by Project</p>
</div> </div>
</div> </div>

View File

@ -1,112 +0,0 @@
import React from "react";
import { useProjectName } from "../../hooks/useProjects";
import { BUCKET_BG_CLASSES } from "../../utils/constants";
import { useAttendaceProjectWiseOveriew } from "../../hooks/useDashboard_Data";
import { AppColors, localToUtc } from "../../utils/appUtils";
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({
date: z.string().optional(),
})
),
defaultValues: {
date: new Date().toISOString().slice(0, 10),
},
});
const goToProject = (projectId) => () => {
dispatch(setProjectId(projectId));
navigate(`/projects/details`);
};
const selectedDate = useAppWatch({ control, name: "date" });
const { data, isLoading, isFetching, isError, error } =
useAttendaceProjectWiseOveriew(localToUtc(selectedDate));
const percent = (teamCount, attendanceCount) => {
return teamCount > 0 ? Math.round((attendanceCount / teamCount) * 100) : 0;
};
return (
<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">Attendance by Project</h5>
<DatePicker name="date" control={control} maxDate={new Date()} />
</div>
{/* Only show spinner for new data, not full component */}
{isFetching && !isLoading && (
<div className="small text-end text-muted">Updating</div>
)}
{/* Table */}
<div className="table-container">
<table className="table table-borderless mb-0">
<thead className="table-header">
<tr>
<th style={{ width: 200 }} className="text-start">
Project
</th>
<th style={{ width: 100 }}>Team Size</th>
<th style={{ width: 80 }} className="text-start">
Logged In
</th>
{/* <th>Percentage</th> */}
</tr>
</thead>
</table>
<div
className="table-body-scroll overflow-auto pe-1"
style={{ maxHeight: "60vh" }}
>
<table className="table table-borderless mb-0">
<tbody>
{(data ?? []).map((item, index) => (
<tr key={item.projectId || index}>
<td style={{ width: 200 }}>
<div
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>{percent(item.teamCount, item.attendanceCount)}%</td> */}
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
);
};
export default ProjectWiseTeamCount;

View File

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

View File

@ -59,28 +59,6 @@ const PreviewDocument = ({ files = [] }) => {
}; };
}; };
const handleWheel = (e) => {
if (isDocumentType) return;
e.preventDefault();
const delta = e.deltaY;
setScale((prev) => {
let newScale = prev;
if (delta < 0) {
// Scroll UP -> Zoom IN
newScale = Math.min(prev + 0.1, MAX_ZOOM);
} else {
// Scroll DOWN -> Zoom OUT
newScale = Math.max(prev - 0.1, MIN_ZOOM);
}
return newScale;
});
};
const handleMouseMove = (e) => { const handleMouseMove = (e) => {
if (!dragging || isDocumentType) return; if (!dragging || isDocumentType) return;
@ -127,8 +105,6 @@ const handleWheel = (e) => {
</div> </div>
<div <div
onWheel={handleWheel}
onMouseDown={handleMouseDown} onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove} onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp} onMouseUp={handleMouseUp}

View File

@ -13,22 +13,37 @@ const Sidebar = () => {
id="layout-menu" id="layout-menu"
className="layout-menu menu-vertical menu bg-menu-theme " className="layout-menu menu-vertical menu bg-menu-theme "
> >
<div className="app-brand" style={{ paddingLeft: "15px" }}> <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">
<img
className="app-brand-logo-sidebar"
src="/img/brand/marco.png"
alt="logo"
aria-label="logo image"
style={{ margin: "5px", paddingRight: "5px" }}
/>
</span> */}
<a
href="/"
className="app-brand-link d-flex align-items-center gap-1 fw-bold navbar-brand "
>
<span className="app-brand-logo demo d-flex align-items-center"> <span className="app-brand-logo demo d-flex align-items-center">
<img <img
src="/img/brand/marco.png" src="/img/brand/marco.png"
width="50" width="40"
height="40" height="40"
alt="OnFieldWork logo" alt="OnFieldWork logo"
/> />
</span> </span>
<span className="app-brand-text"> <span className="app-brand-text">
<span className="text-blue ms-1">OnField</span> <span className="text-primary ">OnField</span>
<span className="text-green">Work</span> <span className="mx-1">Work</span>
<span className="text-dark">.com</span> <span className="text-dark">.com</span>
</span> </span>
</a>
</Link> </Link>
<small className="layout-menu-toggle menu-link text-large ms-auto cursor-pointer"> <small className="layout-menu-toggle menu-link text-large ms-auto cursor-pointer">

View File

@ -33,11 +33,8 @@ const AssignOrg = ({ setStep }) => {
}); });
const isPending = isPendingProject || isPendingTenat; const isPending = isPendingProject || isPendingTenat;
const mergedServices = useMemo(() => { const mergedServices = useMemo(() => {
const master = Array.isArray(masterService?.data) ? masterService.data : []; if (!masterService || !projectServices) return [];
const project = Array.isArray(projectServices) ? projectServices : []; const combined = [...masterService?.data, ...projectServices];
const combined = [...master, ...project];
return combined.filter( return combined.filter(
(item, index, self) => index === self.findIndex((s) => s.id === item.id) (item, index, self) => index === self.findIndex((s) => s.id === item.id)
); );
@ -103,8 +100,7 @@ const AssignOrg = ({ setStep }) => {
alt="logo" alt="logo"
width={40} width={40}
height={40} height={40}
/>{" "} /> <p className="fw-semibold fs-5 mt-2 m-0">{orgData.name}</p>
<p className="fw-semibold fs-5 mt-2 m-0">{orgData.name}</p>
</div> </div>
<div className="text-end"> <div className="text-end">
<button <button
@ -117,10 +113,7 @@ const AssignOrg = ({ setStep }) => {
</div> </div>
</div> </div>
</div> </div>
<div className="d-flex text-secondary mb-3"> <div className="d-flex text-secondary mb-3"> <i className="bx bx-sm bx-info-circle me-2" /> Organization Info</div>
{" "}
<i className="bx bx-sm bx-info-circle me-2" /> Organization Info
</div>
{/* Contact Info */} {/* Contact Info */}
<div className="col-md-12 mb-4"> <div className="col-md-12 mb-4">
<div className="d-flex"> <div className="d-flex">
@ -139,7 +132,7 @@ const AssignOrg = ({ setStep }) => {
className="form-label me-2 mb-0 fw-semibold" className="form-label me-2 mb-0 fw-semibold"
style={{ minWidth: "130px" }} style={{ minWidth: "130px" }}
> >
<i className="bx bx-sm me-1 bx-phone"></i> Contact Number : <i className='bx bx-sm me-1 bx-phone'></i> Contact Number :
</label> </label>
<div className="text-muted">{orgData.contactNumber}</div> <div className="text-muted">{orgData.contactNumber}</div>
</div> </div>
@ -150,7 +143,7 @@ const AssignOrg = ({ setStep }) => {
className="form-label me-2 mb-0 fw-semibold" className="form-label me-2 mb-0 fw-semibold"
style={{ minWidth: "130px" }} style={{ minWidth: "130px" }}
> >
<i className="bx bx-sm me-1 bx-envelope"></i> Email Address : <i className='bx bx-sm me-1 bx-envelope'></i> Email Address :
</label> </label>
<div className="text-muted text-wrap">{orgData.email}</div> <div className="text-muted text-wrap">{orgData.email}</div>
</div> </div>
@ -173,7 +166,7 @@ const AssignOrg = ({ setStep }) => {
className="form-label me-1 mb-0 fw-semibold" className="form-label me-1 mb-0 fw-semibold"
style={{ minWidth: "130px" }} style={{ minWidth: "130px" }}
> >
<i className="bx bx-sm me-1 bx-map"></i> Address : <i className='bx bx-sm me-1 bx-map'></i> Address :
</label> </label>
<div className="text-muted text-start">{orgData.address}</div> <div className="text-muted text-start">{orgData.address}</div>
</div> </div>
@ -187,11 +180,7 @@ const AssignOrg = ({ setStep }) => {
<> <>
{/* Organization Type */} {/* Organization Type */}
<div className="mb-3 text-start"> <div className="mb-3 text-start">
<Label <Label htmlFor="organizationTypeId" className="mb-3 fw-semibold" required>
htmlFor="organizationTypeId"
className="mb-3 fw-semibold"
required
>
Organization Type Organization Type
</Label> </Label>
<div className="d-flex flex-wrap gap-3 mt-1"> <div className="d-flex flex-wrap gap-3 mt-1">
@ -225,11 +214,7 @@ const AssignOrg = ({ setStep }) => {
{/* Services */} {/* Services */}
<div className="mb-3"> <div className="mb-3">
<Label <Label htmlFor="serviceIds" className="mb-3 fw-semibold" required>
htmlFor="serviceIds"
className="mb-3 fw-semibold"
required
>
Select Services Select Services
</Label> </Label>
{mergedServices?.map((service) => ( {mergedServices?.map((service) => (

View File

@ -237,116 +237,13 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
))} ))}
</span> </span>
</p> </p>
<div
className="col-12 col-md-4 d-flex flex-row gap-3 align-items-center mt-4 mb-6 justify-content-end"
ref={dropdownRef}
>
<div className="d-flex justify-content-between align-items-center w-100">
{/* Left Side → Heading + Search */}
<div className="d-flex flex-column w-100">
<label className=" text-start small mb-1">Search Employee</label>
<input
type="text"
className="form-control form-control-sm"
placeholder="Search employees or roles..."
value={searchTerm}
onChange={handleSearchChange}
/>
</div>
{/* Right Side → Dropdown */}
<div className="dropdown position-relative d-inline-block ms-2 mt-4">
<a
className={`dropdown-toggle hide-arrow cursor-pointer ${selectedRoles.includes("all") || selectedRoles.length === 0
? "text-secondary"
: "text-primary"
}`}
onClick={() => setOpen(!open)}
>
<i className="bx bx-slider-alt ms-2"></i>
</a>
{/* Badge */}
{selectedRolesCount > 0 && (
<span
className="position-absolute top-0 start-100 translate-middle badge rounded-circle bg-warning text-white text-tiny"
style={{
fontSize: "0.65rem",
minWidth: "18px",
height: "18px",
padding: "0",
lineHeight: "18px",
textAlign: "center",
zIndex: 10,
}}
>
{selectedRolesCount}
</span>
)}
{/* Dropdown Menu */}
{open && (
<ul
className="dropdown-menu show p-2 text-capitalize"
style={{ maxHeight: "300px", overflowY: "auto" }}
>
{/* All Roles */}
<li key="all">
<div className="form-check dropdown-item py-0">
<input
className="form-check-input"
type="checkbox"
id="checkboxAllRoles"
value="all"
checked={selectedRoles.includes("all")}
onChange={(e) => handleRoleChange(e, e.target.value)}
/>
<label
className="form-check-label ms-2"
htmlFor="checkboxAllRoles"
>
All Roles
</label>
</div>
</li>
{/* Dynamic Roles */}
{jobRolesForDropdown?.map((role) => (
<li key={role.id}>
<div className="form-check dropdown-item py-0">
<input
className="form-check-input"
type="checkbox"
id={`checkboxRole-${role.id}`}
value={role.id}
checked={selectedRoles.includes(String(role.id))}
onChange={(e) => handleRoleChange(e, e.target.value)}
/>
<label
className="form-check-label ms-2"
htmlFor={`checkboxRole-${role.id}`}
>
{role.name}
</label>
</div>
</li>
))}
</ul>
)}
</div>
</div>
</div>
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<div className="form-label text-start"> <div className="form-label text-start">
<div className="row mb-1"> <div className="row mb-1">
<div className="col-12"> <div className="col-12">
<div className="row text-start"> <div className="row text-start">
<div className="col-12 col-md-8 d-flex flex-row gap-3 align-items-center mt-4 d-none"> <div className="col-12 col-md-8 d-flex flex-row gap-3 align-items-center mt-4">
<div> <div>
<AppFormController <AppFormController
name="organizationId" name="organizationId"
@ -399,7 +296,109 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
)} )}
</div> </div>
</div> </div>
<div
className="col-12 col-md-4 d-flex flex-row gap-3 align-items-center justify-content-end"
ref={dropdownRef}
>
{/* Dropdown */}
<div className="dropdown position-relative d-inline-block">
<a
className={`dropdown-toggle hide-arrow cursor-pointer ${selectedRoles.includes("all") ||
selectedRoles.length === 0
? "text-secondary"
: "text-primary"
}`}
onClick={() => setOpen(!open)}
>
<i className="bx bx-slider-alt ms-2"></i>
</a>
{/* Badge */}
{selectedRolesCount > 0 && (
<span
className="position-absolute top-0 start-100 translate-middle badge rounded-circle bg-warning text-white text-tiny"
style={{
fontSize: "0.65rem",
minWidth: "18px",
height: "18px",
padding: "0",
lineHeight: "18px",
textAlign: "center",
zIndex: 10,
}}
>
{selectedRolesCount}
</span>
)}
{/* Dropdown Menu */}
{open && (
<ul
className="dropdown-menu show p-2 text-capitalize"
style={{ maxHeight: "300px", overflowY: "auto" }}
>
{/* All Roles */}
<li key="all">
<div className="form-check dropdown-item py-0">
<input
className="form-check-input"
type="checkbox"
id="checkboxAllRoles"
value="all"
checked={selectedRoles.includes("all")}
onChange={(e) =>
handleRoleChange(e, e.target.value)
}
/>
<label
className="form-check-label ms-2"
htmlFor="checkboxAllRoles"
>
All Roles
</label>
</div>
</li>
{/* Dynamic Roles */}
{jobRolesForDropdown?.map((role) => (
<li key={role.id}>
<div className="form-check dropdown-item py-0">
<input
className="form-check-input"
type="checkbox"
id={`checkboxRole-${role.id}`}
value={role.id}
checked={selectedRoles.includes(
String(role.id)
)}
onChange={(e) =>
handleRoleChange(e, e.target.value)
}
/>
<label
className="form-check-label ms-2"
htmlFor={`checkboxRole-${role.id}`}
>
{role.name}
</label>
</div>
</li>
))}
</ul>
)}
</div>
{/* Search Box */}
<div>
<input
type="text"
className="form-control form-control-sm ms-auto mb-2 mt-2"
placeholder="Search employees or roles..."
value={searchTerm}
onChange={handleSearchChange}
/>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -8,7 +8,7 @@ import {
useGroups, useGroups,
useWorkCategoriesMaster, useWorkCategoriesMaster,
} from "../../../hooks/masterHook/useMaster"; } from "../../../hooks/masterHook/useMaster";
import { useCurrentService, useManageTask, useProjectAssignedOrganizationsName, useProjectAssignedServices } from "../../../hooks/useProjects"; import { useManageTask, useProjectAssignedOrganizationsName, useProjectAssignedServices } from "../../../hooks/useProjects";
import showToast from "../../../services/toastService"; import showToast from "../../../services/toastService";
import Label from "../../common/Label"; import Label from "../../common/Label";
import { useSelectedProject } from "../../../slices/apiDataManager"; import { useSelectedProject } from "../../../slices/apiDataManager";
@ -28,16 +28,12 @@ const taskSchema = z.object({
comment: z.string(), comment: z.string(),
}); });
const defaultModel = {
const TaskModel = ({ project, onSubmit, onClose }) => {
const currentService = useCurrentService()
const defaultModel = {
id: null, id: null,
buildingID: "", buildingID: "",
floorId: "", floorId: "",
workAreaId: "", workAreaId: "",
serviceId: currentService ?? "", serviceId: "",
activityGroupId: "", activityGroupId: "",
activityID: "", activityID: "",
workCategoryId: "", workCategoryId: "",
@ -45,6 +41,8 @@ const TaskModel = ({ project, onSubmit, onClose }) => {
completedWork: 0, completedWork: 0,
comment: "", comment: "",
}; };
const TaskModel = ({ project, onSubmit, onClose }) => {
// const { activities, loading: activityLoading } = useActivitiesMaster(); // const { activities, loading: activityLoading } = useActivitiesMaster();
const { categories, categoryLoading } = useWorkCategoriesMaster(); const { categories, categoryLoading } = useWorkCategoriesMaster();

View File

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

View File

@ -159,8 +159,7 @@ const ProjectListView = ({ data, currentPage, totalPages, paginate }) => {
</td> </td>
))} ))}
<td <td
className={`mx-2 ${ className={`mx-2 ${canManageProject ? "d-sm-table-cell" : "d-none"
canManageProject ? "d-sm-table-cell" : "d-none"
}`} }`}
> >
<div className="dropdown z-2"> <div className="dropdown z-2">
@ -239,7 +238,7 @@ const ProjectListView = ({ data, currentPage, totalPages, paginate }) => {
<Pagination <Pagination
currentPage={currentPage} currentPage={currentPage}
totalPages={totalPages} totalPages={totalPages}
onPageChange={paginate} paginate={paginate}
/> />
</div> </div>
); );

View File

@ -170,7 +170,7 @@ const ProjectStatistics = ({ project }) => {
<h5 className="card-action-title mb-0"> <h5 className="card-action-title mb-0">
{" "} {" "}
<i className="fa fa-line-chart rounded-circle text-primary"></i> <i className="fa fa-line-chart rounded-circle text-primary"></i>
<span className="ms-2 fw-semibold">Project Statistics</span> <span className="ms-2 fw-bold">Project Statistics</span>
</h5> </h5>
</div> </div>
<div className="card-body"> <div className="card-body">

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react"; import React, { useState } from "react";
import TeamEmployeeList from "./TeamEmployeeList"; import TeamEmployeeList from "./TeamEmployeeList";
import { useOrganization } from "../../../hooks/useDirectory"; import { useOrganization } from "../../../hooks/useDirectory";
import { useOrganizationsList } from "../../../hooks/useOrganization"; import { useOrganizationsList } from "../../../hooks/useOrganization";
@ -14,19 +14,9 @@ const TeamAssignToProject = ({ closeModal }) => {
const project = useSelectedProject(); const project = useSelectedProject();
const { data, isLoading, isError, error } = const { data, isLoading, isError, error } =
useProjectAssignedOrganizationsName(project); useProjectAssignedOrganizationsName(project);
const { control, watch, setValue, formState: { errors } } = useForm({ const { control, watch, formState: { errors } } = useForm({
defaultValues: { organizationId: "" }, defaultValues: { organizationId: "" },
}); });
useEffect(() => {
if (data?.length === 1) {
setValue("organizationId", data[0].id, {
shouldValidate: true,
shouldDirty: true,
});
}
}, [data, setValue]);
return ( return (
<div className="container"> <div className="container">
{/* <p className="fs-5 fs-seminbod ">Assign Employee To Project </p> */} {/* <p className="fs-5 fs-seminbod ">Assign Employee To Project </p> */}
@ -57,7 +47,7 @@ const TeamAssignToProject = ({ closeModal }) => {
<small className="danger-text">{errors.organizationId.message}</small> <small className="danger-text">{errors.organizationId.message}</small>
)} )}
</div> </div>
<div className="col-12 col-md-6 mt-n2"> <div className="col-12 col-md-6 mt-n5">
<div className="d-flex flex-column"> <div className="d-flex flex-column">
<label htmlFor="search" className="form-label mb-1"> <label htmlFor="search" className="form-label mb-1">
Search Employee Search Employee

View File

@ -162,23 +162,15 @@ const TeamEmployeeList = ({ organizationId, searchTerm, closeModal }) => {
<table className="table" style={{ maxHeight: "80px", overflowY: "auto" }}> <table className="table" style={{ maxHeight: "80px", overflowY: "auto" }}>
<thead className=" position-sticky top-0"> <thead className=" position-sticky top-0">
<tr> <tr>
<th>Select</th>
<th>Employee</th> <th>Employee</th>
<th>Service</th> <th>Service</th>
<th>Job Role</th> <th>Job Role</th>
<th>Select</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{employees.map((emp, index) => ( {employees.map((emp, index) => (
<tr key={emp.id}> <tr key={emp.id}>
<td>
<input
type="checkbox"
className="form-check-input"
checked={emp.isChecked}
onChange={() => handleCheckboxChange(index)}
/>
</td>
<td> <td>
<div className="d-flex align-items-center"> <div className="d-flex align-items-center">
<Avatar firstName={emp.firstName} lastName={emp.lastName} /> <Avatar firstName={emp.firstName} lastName={emp.lastName} />
@ -187,7 +179,6 @@ const TeamEmployeeList = ({ organizationId, searchTerm, closeModal }) => {
</span> </span>
</div> </div>
</td> </td>
<td> <td>
<select <select
value={emp.serviceId} value={emp.serviceId}
@ -230,7 +221,14 @@ const TeamEmployeeList = ({ organizationId, searchTerm, closeModal }) => {
<div className="danger-text">{emp.errors.jobRole}</div> <div className="danger-text">{emp.errors.jobRole}</div>
)} )}
</td> </td>
<td>
<input
type="checkbox"
className="form-check-input"
checked={emp.isChecked}
onChange={() => handleCheckboxChange(index)}
/>
</td>
</tr> </tr>
))} ))}
</tbody> </tbody>

View File

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

View File

@ -56,7 +56,7 @@ const OrganizationInfo = ({ onNext, onPrev, onSubmitTenant }) => {
// onSubmitTenant(data); // onSubmitTenant(data);
// onNext(); // onNext();
const tenantPayload = { ...data, onBoardingDate: moment.utc(data.onBoardingDate, "DD-MM-YYYY").toISOString() } const tenantPayload = { ...data, onBoardingDate: moment.utc(data.onBoardingDate, "DD-MM-YYYY").toISOString() }
CreateTenant(tenantPayload); // CreateTenant(tenantPayload);
} }
}; };

View File

@ -3,7 +3,6 @@ import { useController } from "react-hook-form";
const DatePicker = ({ const DatePicker = ({
name, name,
defaultDate,
control, control,
size="sm", size="sm",
placeholder = "DD-MM-YYYY", placeholder = "DD-MM-YYYY",

View File

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

View File

@ -2,17 +2,10 @@ import React from "react";
import { useDeliverChallane } from "../../hooks/usePurchase"; import { useDeliverChallane } from "../../hooks/usePurchase";
import { SpinnerLoader } from "../common/Loader"; import { SpinnerLoader } from "../common/Loader";
import { formatUTCToLocalTime } from "../../utils/dateUtils"; import { formatUTCToLocalTime } from "../../utils/dateUtils";
import { FileView } from "../Expenses/Filelist"; // Assuming FileView is the component showing the file icon/name import { FileView } from "../Expenses/Filelist";
import { usePurchaseContext } from "../../pages/purchase/PurchasePage";
import { getIconByFileType } from "../../utils/appUtils";
// Assuming you have an Error component imported somewhere else
// import Error from "../common/Error";
const DeliverChallanList = ({ purchaseId, viewDocuments }) => { const DeliverChallanList = ({ purchaseId, viewDocuments }) => {
const { setDocumentView } = usePurchaseContext();
const { data, isLoading, isError, error } = useDeliverChallane(purchaseId); const { data, isLoading, isError, error } = useDeliverChallane(purchaseId);
if (isLoading) { if (isLoading) {
return ( return (
<div className="d-flex justify-content-center align-items-center text-center vh-50"> <div className="d-flex justify-content-center align-items-center text-center vh-50">
@ -24,12 +17,10 @@ const DeliverChallanList = ({ purchaseId, viewDocuments }) => {
if (isError) { if (isError) {
return ( return (
<div className="py-3"> <div className="py-3">
{/* Assuming Error component is used here */}
<Error error={error} /> <Error error={error} />
</div> </div>
); );
} }
if (!isLoading && data.length === 0) if (!isLoading && data.length === 0)
return ( return (
<div className="d-flex justify-content-center align-items-center text-center vh-50"> <div className="d-flex justify-content-center align-items-center text-center vh-50">
@ -64,29 +55,8 @@ const DeliverChallanList = ({ purchaseId, viewDocuments }) => {
{item.description || "-"} {item.description || "-"}
</p> </p>
{/* Check if attachment exists and open document view on click */}
{item.attachment?.preSignedUrl && ( {item.attachment?.preSignedUrl && (
<div <FileView file={item.attachment} viewFile={viewDocuments} />
className="d-flex align-items-center cusor-pointer mt-2"
onClick={() => {
setDocumentView({
IsOpen: true,
Images: [item.attachment],
});
}}
>
{/* Replicating the display style used in ViewExpense for single file attachment */}
<i
className={`bx ${getIconByFileType(item.attachment.contentType)}`}
style={{ fontSize: "30px" }}
></i>
<small
className="text-start text-tiny text-truncate w-100 ms-1"
title={item.attachment.fileName}
>
{item.attachment.fileName}
</small>
</div>
)} )}
</div> </div>
</div> </div>

View File

@ -1,31 +0,0 @@
const ActivitiesTable = ({ date, rows }) => {
return (
<div className="reports-activities">
<h2>Activities (Tasks) Performed {date}</h2>
<table className="reports-table">
<thead>
<tr>
<th>NAME</th>
<th>JOB ROLE</th>
<th>CHECK IN</th>
<th>CHECK OUT</th>
</tr>
</thead>
<tbody>
{rows.map((row, i) => (
<tr key={i}>
<td>{row.name}</td>
<td>{row.role}</td>
<td>{row.in}</td>
<td>{row.out}</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
export default ActivitiesTable;

View File

@ -1,121 +0,0 @@
import React from "react";
import Chart from "react-apexcharts";
import { formatFigure } from "../../utils/appUtils";
const Progress = ({
color = "#00aaff",
series = 0,
total = 0,
height = 100,
width = 100,
completed = 0,
}) => {
const options = {
chart: {
type: "radialBar",
},
colors: [color],
plotOptions: {
radialBar: {
hollow: { size: "55%" },
track: { background: "#f1f1f1" },
dataLabels: {
name: { show: false },
value: {
show: true,
fontSize: "13px",
width: "12px",
textWrap: "wrap",
color: color,
fontWeight: 400,
offsetY: 7,
formatter: () =>
`${formatFigure(completed, {
notation: "compact",
})} / ${formatFigure(total, { notation: "compact" })}`,
},
style: {
textWrap: "wrap",
},
},
},
},
stroke: { lineCap: "round" },
};
return (
<div style={{ width: "fit-content" }}>
<Chart
options={options}
series={[Number(series)]}
type="radialBar"
height={height}
width={width}
/>
</div>
);
};
export default Progress;
// const Progress = ({
// completed = 0,
// inProgress = 0,
// total = 100,
// height = 140,
// width = 140,
// }) => {
// const completedPercent =
// total > 0 ? Math.round((completed / total) * 100) : 0;
// const progressPercent =
// total > 0 ? Math.round((completed / total) * 100) : 0;
// const options = {
// chart: {
// type: "radialBar",
// },
// colors: ["#28a745", "#0d6efd"],
// plotOptions: {
// radialBar: {
// hollow: {
// size: "35%",
// },
// track: {
// margin: 4,
// },
// dataLabels: {
// name: { show: false },
// value: {
// show: true,
// fontSize: "14px",
// fontWeight: 600,
// offsetY: 5,
// formatter: () => `${completed + inProgress}/${total}`,
// },
// },
// },
// },
// labels: ["Completed", "In Progress"],
// stroke: {
// lineCap: "round",
// },
// };
// return (
// <div style={{ width: "fit-content" }}>
// <Chart
// options={options}
// series={[completedPercent, progressPercent]}
// type="radialBar"
// height={height}
// width={width}
// />
// </div>
// );
// };

View File

@ -1,65 +0,0 @@
import { getCompletionPercentage } from "../../utils/dateUtils";
import Progress from "./Progress";
import ReportsLegend from "./ReportsLegend";
const ReportsDonutCard = ({
title,
value,
total,
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 h-100">
<h4 className="reports-card-title">{title}</h4>
<div className="d-flex justify-content-center align-items-center flex-column">
<div className="d-inline-flex flex-row align-items-center gap-12 gap-md-3">
<Progress
color={chartColor}
width={120}
height={120}
series={getCompletionPercentage(value, total)}
completed={value}
total={total}
/>
<ReportsLegend legend1={legend1} legend2={legend2} />
</div>
</div>
<div className="text-center p-2">
<p className="text-muted mb-0">{footer}</p>
</div>
</div>
);
};
export default ReportsDonutCard;
export const ReportsCard = ({
title,
value,
total,
donutClass = "",
footer = "Team members present on the site",
chartColor,
}) => {
return (
<div className="border-top card border-primary py-4 px-2">
<h4 className="reports-card-title">{title}</h4>
<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>
<div className="text-center p-2">
<p className="text-muted mb-0">{footer}</p>
</div>
</div>
);
};

View File

@ -1,17 +0,0 @@
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>{legend1}</span>
</div>
<div className=" d-flex align-items-center gap-2">
<span className="reports-legend-color reports-legend-blue"></span>
<span>{legend2}</span>
</div>
</div>
);
};
export default ReportsLegend;

View File

@ -1,20 +0,0 @@
const TeamStrengthCard = ({ data }) => {
return (
<div className="reports-card">
<h4 className="reports-card-title">Team Strength on Site</h4>
<table style={{ width: "100%" }}>
<tbody>
{data.map((item, i) => (
<tr key={i}>
<td style={{ textAlign: "left" }}>{item.role}</td>
<td style={{ textAlign: "right" }}>{item.count}</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
export default TeamStrengthCard;

View File

@ -1,176 +0,0 @@
import React from "react";
import { useProjectReportByProject } from "../../hooks/useReports";
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(
project,
localToUtc(date)
);
return (
<>
<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="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>
<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">
<span className="text-secondry"> Regualrization</span>{" "}
<span className="text-secondry">
{" "}
{data?.regularizationPending}
</span>{" "}
</div>
<div className="d-flex justify-content-between">
<span className="text-secondry"> Checking</span>{" "}
<span className="text-secondry">
{" "}
{data?.checkoutPending}
</span>{" "}
</div>
<div className="d-flex justify-content-between">
<span className="text-secondry"> Total Employee</span>{" "}
<span className="text-secondry">
{" "}
{data?.todaysAttendances}
</span>{" "}
</div>
</div>
</div>
</div>
</div>
<div className="row px-5 pb-5">
<div className="col-4">
<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?.filter(
(item) => item?.numberofEmployees > 0
).length > 0 ? (
data?.teamOnSite
?.filter((item) => item?.numberofEmployees > 0)
?.map((member, index) => (
<tr key={index}>
<td style={{ textAlign: "left" }}>
{member?.roleName}
</td>
<td style={{ textAlign: "right" }}>
{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 h-100" style={{ minHeight: "250px" }}>
<h4 className="reports-card-title">Employee In-Out Report </h4>
<table className="reports-table">
<thead>
<tr>
<th>NAME</th>
<th>JOB ROLE</th>
<th>CHECK IN</th>
<th>CHECK OUT</th>
</tr>
</thead>
<tbody>
{data?.performedAttendance?.length > 0 ? (
data?.performedAttendance?.map((att, index) => (
<tr key={att.index + att.name}>
<td>{att.name}</td>
<td>{att.roleName}</td>
<td>{formatUTCToLocalTime(att.inTime, true)}</td>
<td>
{att.outTime
? formatUTCToLocalTime(att.outTime, true)
: "--"}
</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>
</div>
</div>
</div>
</>
);
};
export default ReportDPR;

View File

@ -1,7 +1,6 @@
import { useForm, Controller, FormProvider, useFormContext, useWatch } from "react-hook-form"; import { useForm, Controller, FormProvider, useFormContext } from "react-hook-form";
export const useAppForm = (config) => useForm(config); export const useAppForm = (config) => useForm(config);
export const AppFormProvider = FormProvider; export const AppFormProvider = FormProvider;
export const AppFormController = Controller; export const AppFormController = Controller;
export const useAppFormContext = useFormContext; export const useAppFormContext = useFormContext;
export const useAppWatch = useWatch;

View File

@ -1,6 +1,6 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import GlobalRepository from "../repositories/GlobalRepository"; import GlobalRepository from "../repositories/GlobalRepository";
import { keepPreviousData, useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
export const useDashboard_Data = ({ days, FromDate, projectId }) => { export const useDashboard_Data = ({ days, FromDate, projectId }) => {
const [dashboard_data, setDashboard_Data] = useState([]); const [dashboard_data, setDashboard_Data] = useState([]);
@ -212,16 +212,3 @@ export const useJobsProgression = (projectId) => {
}, },
}); });
}; };
export const useAttendaceProjectWiseOveriew = (date) => {
return useQuery({
queryKey: ["attendaceOverview", date],
queryFn: async () => {
const resp = await GlobalRepository.getAttendanceProjectWiseOverview(
date
);
return resp.data;
},
keepPreviousData: true,
});
};

View File

@ -1,16 +0,0 @@
import { useQuery } from "@tanstack/react-query";
import { ReportsRepository } from "../repositories/GlobalRepository";
export const useProjectReportByProject = (projectId, date) => {
return useQuery({
queryKey: ["daily_report", projectId, date],
queryFn: async () => {
const resp = await ReportsRepository.getDailyProgressByProject(
projectId,
date
);
return resp.data;
},
enabled: !!projectId && !!date,
});
};

View File

@ -136,15 +136,6 @@ const AttendancePage = () => {
: []), : []),
]; ];
// --- END: Tab Configuration Array // --- END: Tab Configuration Array
useEffect(() => {
if (!orgLoading && organizations?.length === 1) {
setAppliedFilters((prev) => ({
...prev,
selectedOrganization: organizations[0].id,
}));
}
}, [orgLoading, organizations]);
return ( return (
<> <>
@ -187,7 +178,8 @@ const AttendancePage = () => {
<ul className="nav nav-tabs" role="tablist"> <ul className="nav nav-tabs" role="tablist">
{tabsData.map((tab) => ( {tabsData.map((tab) => (
<li <li
className={`nav-item ${tab.id === "regularization" && !DoRegularized className={`nav-item ${
tab.id === "regularization" && !DoRegularized
? "d-none" ? "d-none"
: "" : ""
}`} // Keep the d-none logic for regularization, although it's filtered above }`} // Keep the d-none logic for regularization, although it's filtered above
@ -195,7 +187,8 @@ const AttendancePage = () => {
> >
<button <button
type="button" type="button"
className={`nav-link ${activeTab === tab.id ? "active" : "" className={`nav-link ${
activeTab === tab.id ? "active" : ""
} fs-6`} } fs-6`}
onClick={() => handleTabChange(tab.id)} onClick={() => handleTabChange(tab.id)}
data-bs-toggle="tab" data-bs-toggle="tab"
@ -252,7 +245,8 @@ const AttendancePage = () => {
{tabsData.map((tab) => ( {tabsData.map((tab) => (
<div <div
key={tab.id} key={tab.id}
className={`tab-pane fade ${activeTab === tab.id ? "show active" : "" className={`tab-pane fade ${
activeTab === tab.id ? "show active" : ""
} py-0 ${tab.id === "all" ? "mx-2" : "p-3"}`} } py-0 ${tab.id === "all" ? "mx-2" : "p-3"}`}
id={`navs-top-${tab.id}`} id={`navs-top-${tab.id}`}
role="tabpanel" role="tabpanel"

View File

@ -15,7 +15,7 @@ const TaskPlanning = () => {
const selectedProject = useSelectedProject(); const selectedProject = useSelectedProject();
const selectedService = useCurrentService(); const selectedService = useCurrentService();
const dispatch = useDispatch(); const dispatch = useDispatch();
const { control,setValue } = useForm({ const { control } = useForm({
defaultValues: { defaultValues: {
serviceFilter: selectedService ?? "" serviceFilter: selectedService ?? ""
}, },
@ -39,20 +39,6 @@ const TaskPlanning = () => {
return <div className="text-center py-5">Loading...</div>; 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 ( return (
<div className="container-fluid"> <div className="container-fluid">
<Breadcrumb <Breadcrumb
@ -65,7 +51,7 @@ const TaskPlanning = () => {
<div className="card py-2 page-min-h"> <div className="card py-2 page-min-h">
<div className="col-sm-4 col-md-3 col-12 px-4 py-2 text-start"> <div className="col-sm-4 col-md-3 col-12 px-4 py-2 text-start">
{data?.length === 0 ? ( {data?.length === 0 ? (
<p className="badge bg-label-secondary m-0"></p> <p className="badge bg-label-secondary m-0">Service not assigned</p>
) : ( ) : (
<AppFormController <AppFormController
name="serviceFilter" name="serviceFilter"
@ -92,7 +78,6 @@ const TaskPlanning = () => {
{/* Planning Component */} {/* Planning Component */}
{selectedProject ? ( {selectedProject ? (
<InfraPlanning /> <InfraPlanning />

View File

@ -46,14 +46,14 @@ const DailyProgrssReport = () => {
filter, filter,
}; };
const { control,setValue } = useForm({ const { control } = useForm({
defaultValues: { defaultValues: {
serviceFilter: "" serviceFilter: ""
} }
}); });
const clearFilter = () => { const clearFilter = () => {
updatedRef.current?.onClear(); updatedRef.current?.onClear();
}; };
const handleFilter = (filterObj) => { const handleFilter = (filterObj) => {
setFilter(filterObj) setFilter(filterObj)
} }
@ -85,22 +85,6 @@ const DailyProgrssReport = () => {
return updated; 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 ( return (
<div className="container-fluid"> <div className="container-fluid">
<DailyProgrssContext.Provider value={contextDispatcher}> <DailyProgrssContext.Provider value={contextDispatcher}>
@ -145,7 +129,7 @@ const DailyProgrssReport = () => {
render={({ field }) => ( render={({ field }) => (
<SelectField <SelectField
label="Services" label="Services"
options={[{ id: "", name: "All Services" }, ...(data ?? [])]} options={[{ id: "", name: "All Projects" }, ...(data ?? [])]}
placeholder="Select Service" placeholder="Select Service"
labelKey="name" labelKey="name"
valueKey="id" valueKey="id"

View File

@ -86,7 +86,7 @@ const LandingPage = () => {
<li className="nav-item ms-1"> <li className="nav-item ms-1">
<a <a
className="btn btn-sm btn-green btn-ovel-small px-3 my-1" className="btn btn-sm btn-green btn-ovel-small px-3 my-1"
href="/auth/reqest/demo" href="#"
> >
Request For Demo Request For Demo
</a> </a>
@ -162,7 +162,7 @@ const LandingPage = () => {
Make data-driven decisions with real-time project analytics. Make data-driven decisions with real-time project analytics.
</p> </p>
<a <a
href="/auth/reqest/demo" href="#"
className="btn btn-green btn-square-small btn-lg mt-3 p-3" className="btn btn-green btn-square-small btn-lg mt-3 p-3"
> >
View Demo View Demo
@ -185,7 +185,7 @@ const LandingPage = () => {
Eliminate Paper Receipts. Take Control of Your Cash Flow. Eliminate Paper Receipts. Take Control of Your Cash Flow.
</p> </p>
<a <a
href="/auth/reqest/demo" href="#"
className="btn btn-green btn-square-small btn-lg mt-3 p-3" className="btn btn-green btn-square-small btn-lg mt-3 p-3"
> >
View Demo View Demo
@ -320,6 +320,65 @@ const LandingPage = () => {
</p> </p>
{/* <SubscriptionPlans/> */} {/* <SubscriptionPlans/> */}
<SubscriptionPlans></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> </div>
</section> </section>
{/* <!-- About --> */} {/* <!-- About --> */}

View File

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

View File

@ -20,7 +20,7 @@ const ServiceProjectDisplay = ({ listView, selectedStatuses, searchTerm }) => {
const { manageServiceProject, setManageServiceProject } = useProjectContext(); const { manageServiceProject, setManageServiceProject } = useProjectContext();
const debouncedSearch = useDebounce(searchTerm, 500); const debouncedSearch = useDebounce(searchTerm, 500);
const { data, isLoading, isError, error } = useServiceProjects( const { data, isLoading, isError, error } = useServiceProjects(
9, ITEMS_PER_PAGE,
currentPage, currentPage,
debouncedSearch debouncedSearch
); );

View File

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

View File

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

View File

@ -41,7 +41,6 @@ import Pagination from "../../components/common/Pagination";
import handleEmployeeExport from "../../components/Employee/handleEmployeeExport"; import handleEmployeeExport from "../../components/Employee/handleEmployeeExport";
import { SpinnerLoader } from "../../components/common/Loader"; import { SpinnerLoader } from "../../components/common/Loader";
import ManageReporting from "../../components/Employee/ManageReporting"; import ManageReporting from "../../components/Employee/ManageReporting";
import { capitalizeFirstLetter } from "../../utils/appUtils";
const EmployeeList = () => { const EmployeeList = () => {
const selectedProjectId = useSelector( const selectedProjectId = useSelector(
@ -74,11 +73,9 @@ const EmployeeList = () => {
const [selectedEmployee, setSelectedEmployee] = useState(null); const [selectedEmployee, setSelectedEmployee] = useState(null);
const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [selectedEmpFordelete, setSelectedEmpFordelete] = useState(null); const [selectedEmpFordelete, setSelectedEmpFordelete] = useState(null);
const [showManageReportingModal, setShowManageReportingModal] = const [showManageReportingModal, setShowManageReportingModal] = useState(false);
useState(false);
const [employeeLodaing, setemployeeLodaing] = useState(false); const [employeeLodaing, setemployeeLodaing] = useState(false);
const ViewTeamMember = useHasUserPermission(VIEW_TEAM_MEMBERS); const ViewTeamMember = useHasUserPermission(VIEW_TEAM_MEMBERS);
const ViewAllEmployee = useHasUserPermission(VIEW_ALL_EMPLOYEES);
const { mutate: suspendEmployee, isPending: empLodaing } = useSuspendEmployee( const { mutate: suspendEmployee, isPending: empLodaing } = useSuspendEmployee(
{ {
setIsDeleteModalOpen, setIsDeleteModalOpen,
@ -142,15 +139,11 @@ const EmployeeList = () => {
const tableRef = useRef(null); const tableRef = useRef(null);
const handleExport = (type) => { const handleExport = (type) => {
handleEmployeeExport( handleEmployeeExport(type, employeeList, filteredData, searchText, tableRef);
type,
employeeList,
filteredData,
searchText,
tableRef
);
}; };
const handleAllEmployeesToggle = (e) => { const handleAllEmployeesToggle = (e) => {
const isChecked = e.target.checked; const isChecked = e.target.checked;
setShowInactive(false); setShowInactive(false);
@ -179,11 +172,9 @@ const EmployeeList = () => {
useEffect(() => { useEffect(() => {
if (!loading && Array.isArray(employees)) { if (!loading && Array.isArray(employees)) {
const sorted = [...employees].sort((a, b) => { const sorted = [...employees].sort((a, b) => {
const nameA = `${a.firstName || ""}${a.middleName || ""}${ const nameA = `${a.firstName || ""}${a.middleName || ""}${a.lastName || ""
a.lastName || ""
}`.toLowerCase(); }`.toLowerCase();
const nameB = `${b.firstName || ""}${b.middleName || ""}${ const nameB = `${b.firstName || ""}${b.middleName || ""}${b.lastName || ""
b.lastName || ""
}`.toLowerCase(); }`.toLowerCase();
return nameA?.localeCompare(nameB); return nameA?.localeCompare(nameB);
}); });
@ -274,8 +265,7 @@ const EmployeeList = () => {
? "Suspend Employee" ? "Suspend Employee"
: "Reactivate Employee" : "Reactivate Employee"
} }
message={`Are you sure you want to ${ message={`Are you sure you want to ${selectedEmpFordelete?.isActive ? "suspend" : "reactivate"
selectedEmpFordelete?.isActive ? "suspend" : "reactivate"
} this employee?`} } this employee?`}
onSubmit={(id) => onSubmit={(id) =>
suspendEmployee({ suspendEmployee({
@ -297,7 +287,7 @@ const EmployeeList = () => {
]} ]}
></Breadcrumb> ></Breadcrumb>
{ViewTeamMember || ViewAllEmployee ? ( {ViewTeamMember ? (
// <div className="row"> // <div className="row">
<div className="card p-1 page-min-h"> <div className="card p-1 page-min-h">
<div className="card-datatable table-responsive pt-5 mx-5 py-10"> <div className="card-datatable table-responsive pt-5 mx-5 py-10">
@ -351,10 +341,8 @@ const EmployeeList = () => {
> >
<i className="bx bx-dots-vertical-rounded bx-md"></i> <i className="bx bx-dots-vertical-rounded bx-md"></i>
</button> </button>
<ul <ul className="dropdown-menu dropdown-menu-end shadow-sm " style={{ minWidth: "220px" }}>
className="dropdown-menu dropdown-menu-end shadow-sm "
style={{ minWidth: "220px" }}
>
<li className="dropdown-item d-flex align-items-center justify-content-between"> <li className="dropdown-item d-flex align-items-center justify-content-between">
<div className="form-check form-switch mb-0"> <div className="form-check form-switch mb-0">
<input <input
@ -363,53 +351,39 @@ const EmployeeList = () => {
role="switch" role="switch"
id="inactiveEmployeesCheckboxMenu" id="inactiveEmployeesCheckboxMenu"
checked={showInactive} checked={showInactive}
onChange={(e) => onChange={(e) => setShowInactive(e.target.checked)}
setShowInactive(e.target.checked)
}
/> />
</div> </div>
<span className="ms-0">Show Inactive Employees</span> <span className="ms-0">Show Inactive Employees</span>
</li> </li>
<li>
<hr className="dropdown-divider" /> <li><hr className="dropdown-divider" /></li>
</li>
<li> <li>
<button <button className="dropdown-item" onClick={() => handleExport("print")}>
className="dropdown-item"
onClick={() => handleExport("print")}
>
<i className="bx bx-printer me-2"></i> Print <i className="bx bx-printer me-2"></i> Print
</button> </button>
</li> </li>
<li> <li>
<button <button className="dropdown-item" onClick={() => handleExport("csv")}>
className="dropdown-item"
onClick={() => handleExport("csv")}
>
<i className="bx bx-file me-2"></i> CSV <i className="bx bx-file me-2"></i> CSV
</button> </button>
</li> </li>
<li> <li>
<button <button className="dropdown-item" onClick={() => handleExport("excel")}>
className="dropdown-item"
onClick={() => handleExport("excel")}
>
<i className="bx bxs-file-export me-2"></i> Excel <i className="bx bxs-file-export me-2"></i> Excel
</button> </button>
</li> </li>
<li> <li>
<button <button className="dropdown-item" onClick={() => handleExport("pdf")}>
className="dropdown-item"
onClick={() => handleExport("pdf")}
>
<i className="bx bxs-file-pdf me-2"></i> PDF <i className="bx bxs-file-pdf me-2"></i> PDF
</button> </button>
</li> </li>
</ul> </ul>
</div> </div>
</div> </div>
</div> </div>
<table <table
@ -486,8 +460,7 @@ const EmployeeList = () => {
Status Status
</th> </th>
<th <th
className={`sorting_disabled ${ className={`sorting_disabled ${!Manage_Employee && "d-none"
!Manage_Employee && "d-none"
}`} }`}
rowSpan="1" rowSpan="1"
colSpan="1" colSpan="1"
@ -502,25 +475,29 @@ const EmployeeList = () => {
{loading && ( {loading && (
<tr> <tr>
<td colSpan={8} className="text-center py-5"> <td colSpan={8} className="text-center py-5">
<div <div className="d-flex justify-content-center align-items-center py-5" style={{ minHeight: "50vh" }}>
className="d-flex justify-content-center align-items-center py-5"
style={{ minHeight: "50vh" }}
>
<SpinnerLoader /> <SpinnerLoader />
</div> </div>
</td> </td>
</tr> </tr>
)} )}
{!loading && displayData?.length === 0 && !searchText ? (
{!loading &&
displayData?.length === 0 &&
(!searchText) ? (
<tr> <tr>
<td colSpan={8} className="border-0 py-3"> <td colSpan={8} className="border-0 py-3">
<div className="py-4">No Data Found</div> <div className="py-4">
No Data Found
</div>
</td> </td>
</tr> </tr>
) : null} ) : null}
{!loading && displayData?.length === 0 && searchText ? ( {!loading &&
displayData?.length === 0 &&
(searchText) ? (
<tr> <tr>
<td colSpan={8} className="border-0 py-3"> <td colSpan={8} className="border-0 py-3">
<div className="py-4"> <div className="py-4">
@ -549,9 +526,8 @@ const EmployeeList = () => {
className="text-heading text-truncate cursor-pointer" className="text-heading text-truncate cursor-pointer"
> >
<span className="fw-normal"> <span className="fw-normal">
{capitalizeFirstLetter(item.firstName)}{" "} {item.firstName} {item.middleName}{" "}
{capitalizeFirstLetter(item.middleName)}{" "} {item.lastName}
{capitalizeFirstLetter(item.lastName)}
</span> </span>
</a> </a>
</div> </div>
@ -564,9 +540,7 @@ const EmployeeList = () => {
{item.email} {item.email}
</span> </span>
) : ( ) : (
<small className="d-block text-start text-muted small"> <span className="d-block text-start text-muted fst-italic">NA</span>
-
</small>
)} )}
</td> </td>
@ -587,9 +561,7 @@ const EmployeeList = () => {
{item.joiningDate ? ( {item.joiningDate ? (
moment(item.joiningDate).format("DD-MMM-YYYY") moment(item.joiningDate).format("DD-MMM-YYYY")
) : ( ) : (
<span className="d-block text-center text-muted fst-italic"> <span className="d-block text-center text-muted fst-italic">NA</span>
NA
</span>
)} )}
</td> </td>

View File

@ -1,10 +1,4 @@
import React, { import React, { createContext, useContext, useEffect, useRef, useState } from "react";
createContext,
useContext,
useEffect,
useRef,
useState,
} from "react";
import Breadcrumb from "../../components/common/Breadcrumb"; import Breadcrumb from "../../components/common/Breadcrumb";
import { import {
ITEMS_PER_PAGE, ITEMS_PER_PAGE,
@ -74,7 +68,7 @@ const ProjectPage = () => {
const handleToggleProject = (value) => { const handleToggleProject = (value) => {
setCoreProjects(value); setCoreProjects(value);
sessionStorage.setItem("whichProjectDisplay", value ? "true" : "false"); sessionStorage.setItem("whichProjectDisplay", String(value));
}; };
useEffect(() => { useEffect(() => {
@ -106,8 +100,7 @@ const ProjectPage = () => {
{/* Service Project Button */} {/* Service Project Button */}
<button <button
type="button" type="button"
className={`btn px-2 py-1 rounded-0 text-tiny ${ className={`btn px-2 py-1 rounded-0 text-tiny ${!coreProjects ? "btn-primary text-white" : ""
!coreProjects ? "btn-primary text-white" : ""
}`} }`}
onClick={() => handleToggleProject(false)} onClick={() => handleToggleProject(false)}
> >
@ -116,8 +109,7 @@ const ProjectPage = () => {
{/* Organization Project Button */} {/* Organization Project Button */}
<button <button
type="button" type="button"
className={`btn px-2 py-1 rounded-0 text-tiny ${ className={`btn px-2 py-1 rounded-0 text-tiny ${coreProjects ? "btn-primary text-white" : ""
coreProjects ? "btn-primary text-white" : ""
}`} }`}
onClick={() => handleToggleProject(true)} onClick={() => handleToggleProject(true)}
> >
@ -146,8 +138,7 @@ const ProjectPage = () => {
<div className="d-flex gap-2"> <div className="d-flex gap-2">
<button <button
type="button" type="button"
className={`btn btn-sm p-1 ${ className={`btn btn-sm p-1 ${!listView ? "btn-primary" : "btn-outline-primary"
!listView ? "btn-primary" : "btn-outline-primary"
}`} }`}
onClick={() => setListView(false)} onClick={() => setListView(false)}
title="Card View" title="Card View"
@ -157,8 +148,7 @@ const ProjectPage = () => {
<button <button
type="button" type="button"
className={`btn btn-sm p-1 ${ className={`btn btn-sm p-1 ${listView ? "btn-primary" : "btn-outline-primary"
listView ? "btn-primary" : "btn-outline-primary"
}`} }`}
onClick={() => setListView(true)} onClick={() => setListView(true)}
title="List View" title="List View"
@ -174,16 +164,12 @@ const ProjectPage = () => {
onClick={() => setOpen(!open)} onClick={() => setOpen(!open)}
> >
<i <i
className={`bx bx-slider-alt fs-5 ${ className={`bx bx-slider-alt fs-5 ${selectedStatuses.length !== PROJECT_STATUS.length ? "text-primary" : ""
selectedStatuses.length !== PROJECT_STATUS.length
? "text-primary"
: ""
}`} }`}
></i> ></i>
{selectedStatuses.length !== PROJECT_STATUS.length && ( {selectedStatuses.length !== PROJECT_STATUS.length && (
<span <span className="badge bg-warning text-white rounded-pill position-absolute"
className="badge bg-warning text-white rounded-pill position-absolute"
style={{ style={{
top: "-4px", top: "-4px",
right: "-4px", right: "-4px",
@ -225,7 +211,10 @@ const ProjectPage = () => {
)} )}
</div> </div>
{(HasManageProject || !coreProjects) && (
{HasManageProject && (
<button <button
type="button" type="button"
className="btn btn-primary btn-sm d-flex align-items-center my-2" className="btn btn-primary btn-sm d-flex align-items-center my-2"

View File

@ -29,11 +29,25 @@ const ProjectsDisplay = ({
const [projectList, setProjectList] = useState([]); const [projectList, setProjectList] = useState([]);
const debouncedSearch = useDebounce(searchTerm, 500); const debouncedSearch = useDebounce(searchTerm, 500);
const { data, isLoading, isError, error } = useProjects( const { data, isLoading, isError, error } = useProjects(
9, ITEMS_PER_PAGE,
currentPage, currentPage,
debouncedSearch debouncedSearch
); );
const filteredProjects =
data?.data?.filter((project) => {
const statusId =
project.projectStatusId ?? project?.status?.id ?? project?.statusId;
const matchesStatus = selectedStatuses.includes(statusId);
const matchesSearch = project?.name
?.toLowerCase()
?.includes(searchTerm?.toLowerCase());
return matchesStatus && matchesSearch;
}) ?? [];
const paginate = (page) => { const paginate = (page) => {
if (page >= 1 && page <= (data?.totalPages ?? 1)) { if (page >= 1 && page <= (data?.totalPages ?? 1)) {
setCurrentPage(page); setCurrentPage(page);
@ -54,7 +68,7 @@ const ProjectsDisplay = ({
.filter((statusId) => grouped[statusId]) .filter((statusId) => grouped[statusId])
.flatMap((statusId) => .flatMap((statusId) =>
grouped[statusId].sort((a, b) => grouped[statusId].sort((a, b) =>
a?.shortName?.toLowerCase()?.localeCompare(b?.shortName?.toLowerCase()) a?.name?.toLowerCase()?.localeCompare(b?.name?.toLowerCase())
) )
); );

View File

@ -1,282 +0,0 @@
/* .reports-container {
margin: 20px auto;
background: #fff;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
} */
.reports-header {
color: #fff;
padding: 20px;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
}
.reports-header h1 {
font-size: 22px;
margin: 0;
}
.reports-header .project-info {
font-size: 14px;
text-align: right;
}
.reports-status-note {
font-size: 12px;
color: #555;
padding: 15px 20px 0 20px;
}
.reports-status-cards {
display: flex;
justify-content: space-between;
gap: 15px;
padding: 20px;
flex-wrap: wrap;
}
.reports-card {
flex: 1;
min-width: 200px;
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
text-align: center;
background: #fff;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
/* <-- added shadow */
transition: transform 0.2s ease, box-shadow 0.2s ease;
border-top: 1px solid #e63946;
}
.reports-card:hover {
transform: translateY(-3px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.reports-card h3 {
font-size: 14px;
margin: 0 0 10px 0;
}
.reports-card p {
margin: 5px 0;
}
.reports-card .value {
font-size: 22px;
font-weight: bold;
}
.reports-card-title {
font-size: 0.9rem;
text-transform: uppercase;
font-weight: 600;
color: #6c757d;
}
.reports-attendance {
color: #b10000;
}
.reports-tasks {
color: #007bff;
}
.reports-completion {
color: #28a745;
}
.reports-activities {
padding: 20px;
}
.reports-activities h2 {
font-size: 18px;
margin-bottom: 10px;
}
.reports-table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
}
.reports-table th,
.reports-table td {
border: 1px solid #ddd;
padding: 8px;
text-align: center;
}
.reports-table th {
background: #f0f0f0;
}
/* .footer {
background: #b10000;
color: #fff;
text-align: center;
padding: 15px;
font-size: 12px;
}
.footer a {
color: #fff;
margin: 0 8px;
text-decoration: none;
} */
/* Responsive */
@media (max-width: 600px) {
.reports-header {
flex-direction: column;
text-align: center;
}
.reports-header .reports-project-info {
text-align: center;
margin-top: 10px;
}
.reports-status-cards {
flex-direction: column;
}
}
.reports-legend {
margin-top: 10px;
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 8px;
font-size: 12px;
color: #555;
}
.reports-legend-item {
display: flex;
align-items: center;
gap: 4px;
}
.reports-legend-color {
width: 10px;
height: 10px;
border-radius: 2px;
display: inline-block;
}
.reports-legend-red {
background: #b10000;
}
.reports-legend-blue {
background: #007bff;
}
.reports-legend-green {
background: #28a745;
}
.reports-legend-gray {
background: #ccc;
}
.reports-donut {
--percentage: 65;
/* Change this per chart */
--danger: #e63946;
--primary: #007bff;
--warning: #ffc107;
--success: #198754;
/* Fill color */
--track: #e9ecef;
/* Background track */
--size: 120px;
/* Default size */
--thickness: 20px;
/* Default thickness */
width: var(--size);
height: var(--size);
border-radius: 50%;
background: conic-gradient(
var(--danger) calc(var(--percentage) * 1%),
var(--track) 0
);
position: relative;
display: flex;
align-items: center;
justify-content: center;
font-family: Arial, sans-serif;
font-weight: bold;
color: #333;
}
.reports-donut::before {
content: "";
position: absolute;
width: calc(var(--size) - var(--thickness));
height: calc(var(--size) - var(--thickness));
border-radius: 50%;
background: #fff;
/* Inner cut-out */
}
.reports-donut span {
position: absolute;
font-size: calc(var(--size) / 6);
}
/* Variants */
.reports-donut.thin {
--size: 80px;
--thickness: 12px;
}
.reports-donut.medium {
--size: 120px;
--thickness: 25px;
}
.reports-donut.large {
--size: 180px;
--thickness: 35px;
}
/* Color variants */
.reports-donut-success {
background: conic-gradient(
var(--success) calc(var(--percentage) * 1%),
var(--track) 0
);
color: var(--success);
}
.reports-donut-warning {
background: conic-gradient(
var(--warning) calc(var(--percentage) * 1%),
var(--track) 0
);
color: var(--warning);
}
.reports-donut-danger {
background: conic-gradient(
var(--danger) calc(var(--percentage) * 1%),
var(--track) 0
);
color: var(--danger);
}
.reports-donut-primary {
background: conic-gradient(
var(--primary) calc(var(--percentage) * 1%),
var(--track) 0
);
color: var(--primary);
}

View File

@ -1,69 +1,8 @@
import React, { useEffect, useState } from "react"; import React from "react";
import { ComingSoonPage } from "../Misc/ComingSoonPage"; import { ComingSoonPage } from "../Misc/ComingSoonPage";
import ReportDPR from "../../components/reports/report-dpr";
import "./Reports.css";
import Breadcrumb from "../../components/common/Breadcrumb";
import { useProjectName } from "../../hooks/useProjects";
import DatePicker from "../../components/common/DatePicker";
import { useForm } from "react-hook-form";
const Reports = () => { const Reports = () => {
const [selectedProject, setSelectedProject] = useState(); return <ComingSoonPage></ComingSoonPage>;
const { projectNames, isError, loading } = useProjectName(true);
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const { control, watch } = useForm({
defaultValues: {
startDate: yesterday.toISOString().split("T")[0],
},
});
const selelectedDate = watch("startDate");
useEffect(()=>{
if(!selectedProject && projectNames){
setSelectedProject(projectNames[0]?.id)
}
},[projectNames])
return (
<div className="container-fluid">
<Breadcrumb
data={[
{ label: "Home", link: "/dashboard" },
{ label: "Daily Progress Report", link: null },
]}
/>
<div className="card my-3 px-sm-4 px-0">
<div className="card-body py-2 px-1 mx-2">
<div className="row align-items-center mb-0">
<div className="col-12 col-md-6 ">
<div className="w-max">
<select
className="form-select form-select-sm"
onChange={(e) => setSelectedProject(e.target.value)}
>
{projectNames?.map((project) => (
<option key={project.id} value={project.id}>
{project.name}
</option>
))}
</select>
</div>
</div>
<div className="col-12 col-md-6 d-flex justify-content-end">
<DatePicker
name="startDate"
control={control}
placeholder="Select Date"
maxDate={new Date()}
/>
</div>
</div>
</div>
</div>
<ReportDPR project={selectedProject} date={selelectedDate} />
</div>
);
}; };
export default Reports; export default Reports;

View File

@ -1,11 +1,7 @@
import { api } from "../utils/axiosClient"; import { api } from "../utils/axiosClient";
const GlobalRepository = { const GlobalRepository = {
getDashboardProgressionData: ({ getDashboardProgressionData: ({ days = '', FromDate = '', projectId = '' }) => {
days = "",
FromDate = "",
projectId = "",
}) => {
let params; let params;
if (projectId == null) { if (projectId == null) {
params = new URLSearchParams({ params = new URLSearchParams({
@ -22,13 +18,12 @@ const GlobalRepository = {
return api.get(`/api/Dashboard/Progression?${params.toString()}`); return api.get(`/api/Dashboard/Progression?${params.toString()}`);
}, },
getProjectCompletionStatus: () => getProjectCompletionStatus:()=>api.get(`/api/Dashboard/project-completion-status`),
api.get(`/api/Dashboard/project-completion-status`),
getDashboardAttendanceData: (date, projectId) => { getDashboardAttendanceData: (date, projectId) => {
return api.get(
`/api/Dashboard/project-attendance/${projectId}?date=${date}` return api.get(`/api/Dashboard/project-attendance/${projectId}?date=${date}`);
);
}, },
getDashboardProjectsCardData: () => { getDashboardProjectsCardData: () => {
return api.get(`/api/Dashboard/projects`); return api.get(`/api/Dashboard/projects`);
@ -49,7 +44,7 @@ const GlobalRepository = {
}, },
getExpenseData: (projectId, startDate, endDate) => { getExpenseData: (projectId, startDate, endDate) => {
let url = `api/Dashboard/expense/type`; let url = `api/Dashboard/expense/type`
const queryParams = []; const queryParams = [];
if (projectId) { if (projectId) {
queryParams.push(`projectId=${projectId}`); queryParams.push(`projectId=${projectId}`);
@ -67,15 +62,10 @@ const GlobalRepository = {
return api.get(url); return api.get(url);
}, },
getExpenseStatus: (projectId) => getExpenseStatus: (projectId) => api.get(`/api/Dashboard/expense/pendings${projectId ? `?projectId=${projectId}` : ""}`),
api.get(
`/api/Dashboard/expense/pendings${
projectId ? `?projectId=${projectId}` : ""
}`
),
getExpenseDataByProject: (projectId, categoryId, months) => { getExpenseDataByProject: (projectId, categoryId, months) => {
let url = `api/Dashboard/expense/monthly`; let url = `api/Dashboard/expense/monthly`
const queryParams = []; const queryParams = [];
if (projectId) { if (projectId) {
queryParams.push(`projectId=${projectId}`); queryParams.push(`projectId=${projectId}`);
@ -92,26 +82,13 @@ const GlobalRepository = {
return api.get(url); return api.get(url);
}, },
getAttendanceOverview: (projectId, days) => getAttendanceOverview: (projectId, days) => api.get(`/api/dashboard/attendance-overview/${projectId}?days=${days}`),
api.get(`/api/dashboard/attendance-overview/${projectId}?days=${days}`),
getCollectionOverview: (projectId) => getCollectionOverview:(projectId) =>api.get(`/api/Dashboard/collection-overview`),
api.get(`/api/Dashboard/collection-overview`),
getJobsProgression: (projectId) => getJobsProgression: (projectId) => api.get(`/api/Dashboard/job/progression${projectId ? `?projectId=${projectId}` : ""}`),
api.get(
`/api/Dashboard/job/progression${
projectId ? `?projectId=${projectId}` : ""
}`
),
getAttendanceProjectWiseOverview: (date) =>
api.get(`/api/dashboard/project/attendance-overview?date=${date}`),
}; };
export default GlobalRepository; export default GlobalRepository;
export const ReportsRepository = {
getDailyProgressByProject: (projectId,date) =>
api.get(`/api/Market/get/project/report/${projectId}?date=${date}`),
};

View File

@ -1,64 +0,0 @@
import CryptoJS from 'crypto-js';
// The key from your C# Middleware
// In a real app, prefer storing this in process.env.REACT_APP_ENCRYPTION_KEY
const KEY_BASE64 = "u4J7p9Qx2hF5vYtLz8Kq3mN1sG0bRwXyZcD6eH8jFQw=";
/**
* Decrypts the specific format sent by the C# EncryptionMiddleware.
* Format: Base64([IV (16 bytes)] + [Encrypted Data])
* * @param {string} encryptedBase64Str - The raw response text from the API
* @returns {any} - The parsed JSON object or string
*/
export const decryptResponse = (encryptedBase64Str) => {
try {
// 1. Parse the Key
const key = CryptoJS.enc.Base64.parse(KEY_BASE64);
// 2. Parse the incoming Base64 string to a WordArray
const fullWordArray = CryptoJS.enc.Base64.parse(encryptedBase64Str);
// 3. Convert to Hex to easily slice the IV (16 bytes = 32 hex chars)
// This is safer than manipulating WordArray indices directly
const fullHex = CryptoJS.enc.Hex.stringify(fullWordArray);
// 4. Extract IV (First 16 bytes / 32 hex characters)
const ivHex = fullHex.substring(0, 32);
const iv = CryptoJS.enc.Hex.parse(ivHex);
// 5. Extract Ciphertext (The rest of the string)
const cipherTextHex = fullHex.substring(32);
const cipherParams = CryptoJS.lib.CipherParams.create({
ciphertext: CryptoJS.enc.Hex.parse(cipherTextHex)
});
// 6. Decrypt
const decrypted = CryptoJS.AES.decrypt(
cipherParams,
key,
{
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}
);
// 7. Convert to UTF-8 String
const decryptedString = decrypted.toString(CryptoJS.enc.Utf8);
if (!decryptedString) {
throw new Error("Decryption produced empty result (Wrong Key/IV?)");
}
// 8. Try to parse JSON, otherwise return plain string
try {
return JSON.parse(decryptedString);
} catch {
return decryptedString;
}
} catch (error) {
console.error("Decryption Failed:", error);
return null;
}
};

View File

@ -24,11 +24,6 @@ export const AppColorconfig = {
borderColor: "#eceef1", borderColor: "#eceef1",
}, },
}; };
export const AppColors = [
"primary","secodary","warning","info","danger"
]
export const getColorNameFromHex = (hex) => { export const getColorNameFromHex = (hex) => {
const normalizedHex = hex?.replace(/'/g, "").toLowerCase(); const normalizedHex = hex?.replace(/'/g, "").toLowerCase();
const colors = AppColorconfig.colors; const colors = AppColorconfig.colors;
@ -267,7 +262,3 @@ export function calculateTDSPercentage(baseAmount = 0, taxAmount = 0, tdsPercent
} }
export function capitalizeFirstLetter(str) {
if (!str) return "";
return str.charAt(0).toUpperCase() + str.slice(1);
}

View File

@ -4,7 +4,6 @@ import axiosRetry from "axios-retry";
import showToast from "../services/toastService"; import showToast from "../services/toastService";
import { startSignalR, stopSignalR } from "../services/signalRService"; import { startSignalR, stopSignalR } from "../services/signalRService";
import { BASE_URL } from "./constants"; import { BASE_URL } from "./constants";
import { decryptResponse } from "../services/encryption";
const base_Url = BASE_URL; const base_Url = BASE_URL;
export const axiosClient = axios.create({ export const axiosClient = axios.create({
@ -135,7 +134,7 @@ const apiRequest = async (method, url, data = {}, config = {}) => {
params: method === "get" ? data : undefined, params: method === "get" ? data : undefined,
...config, ...config,
}); });
return decryptResponse(response.data); return response.data;
} catch (error) { } catch (error) {
throw error; throw error;
} }
@ -184,6 +183,7 @@ export const api = {
headers: { ...customHeaders }, headers: { ...customHeaders },
authRequired: true, authRequired: true,
}), }),
}; };
// Redirect helper // Redirect helper