Compare commits
7 Commits
main
...
Adding_Chi
| Author | SHA1 | Date | |
|---|---|---|---|
| 3d6c833ec4 | |||
| c8b567409f | |||
| 1a2f841159 | |||
| bd3bc30e97 | |||
| def5ef71f6 | |||
| 411e3cae84 | |||
| d88bafe542 |
7
package-lock.json
generated
@ -18,7 +18,6 @@
|
||||
"apexcharts": "^4.5.0",
|
||||
"axios": "^1.7.9",
|
||||
"axios-retry": "^4.5.0",
|
||||
"crypto-js": "^4.2.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"dotenv": "^16.4.7",
|
||||
"dotenv-webpack": "^8.1.0",
|
||||
@ -2415,12 +2414,6 @@
|
||||
"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": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
|
||||
@ -21,7 +21,6 @@
|
||||
"apexcharts": "^4.5.0",
|
||||
"axios": "^1.7.9",
|
||||
"axios-retry": "^4.5.0",
|
||||
"crypto-js": "^4.2.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"dotenv": "^16.4.7",
|
||||
"dotenv-webpack": "^8.1.0",
|
||||
|
||||
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 500 KiB |
|
Before Width: | Height: | Size: 500 KiB |
|
Before Width: | Height: | Size: 201 KiB After Width: | Height: | Size: 201 KiB |
|
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 1.8 MiB |
@ -31,7 +31,7 @@ const selectedProjectId = useSelectedProject()
|
||||
<div className="card-header mb-1 pb-0">
|
||||
<div className="d-flex flex-wrap justify-content-between align-items-center">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
|
||||
@ -104,7 +104,7 @@ const AttendanceOverview = () => {
|
||||
{/* Header */}
|
||||
<div className="d-flex mt-2 justify-content-between align-items-center mb-3">
|
||||
<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>
|
||||
</div>
|
||||
<div className="d-flex gap-2">
|
||||
|
||||
@ -78,7 +78,7 @@ const CollectionOverview = ({ data, isLoading }) => {
|
||||
tooltip: {
|
||||
custom: ({ series, seriesIndex, dataPointIndex }) => {
|
||||
return `
|
||||
<div className="px-2 py-1">
|
||||
<div class="px-2 py-1">
|
||||
<strong>${labels[dataPointIndex]}</strong><br>
|
||||
₹${series[seriesIndex][dataPointIndex].toLocaleString()}
|
||||
</div>
|
||||
@ -235,7 +235,7 @@ export const TopicBarChart = ({ data,isLoading }) => {
|
||||
enabled: true,
|
||||
custom: ({ series, seriesIndex, dataPointIndex }) => {
|
||||
return `
|
||||
<div className="px-3 py-2">
|
||||
<div class="px-3 py-2">
|
||||
<span>₹${series[seriesIndex][
|
||||
dataPointIndex
|
||||
].toLocaleString()}</span>
|
||||
@ -256,8 +256,8 @@ export const TopicBarChart = ({ data,isLoading }) => {
|
||||
return (
|
||||
<div className="row p-2">
|
||||
<div className="col-md-8">
|
||||
<div className="card-header d-flex align-items-center justify-content-between">
|
||||
<h5 className="card-title m-0 me-2">Collection Overview</h5>
|
||||
<div class="card-header d-flex align-items-center justify-content-between">
|
||||
<h5 class="card-title m-0 me-2">Collection Overview</h5>
|
||||
</div>
|
||||
<div className="w-100 d-flex align-items-center text-start px-6">
|
||||
<p className="text-secondary fs-6 m-0">Due Amount</p>
|
||||
@ -274,7 +274,7 @@ export const TopicBarChart = ({ data,isLoading }) => {
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<small className="fw-medium">Overdue Days</small>
|
||||
</div>
|
||||
|
||||
@ -27,8 +27,6 @@ import {
|
||||
} from "../../utils/constants";
|
||||
import CollectionOverview, { TopicBarChart } from "./CollectionOverview";
|
||||
import { CollectionOverviewSkeleton } from "./CollectionOverviewSkeleton";
|
||||
import ProjectWiseTeamCount from "./ProjectWiseTeamCount";
|
||||
|
||||
|
||||
const Dashboard = () => {
|
||||
// Get the selected project ID from Redux store
|
||||
@ -43,24 +41,24 @@ const Dashboard = () => {
|
||||
<div className="container-fluid mt-5">
|
||||
<div className="row gy-4">
|
||||
{isAllProjectsSelected && (
|
||||
<div className="col-sm-6 col-lg-6">
|
||||
<div className="col-sm-6 col-lg-4">
|
||||
<Projects />
|
||||
</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 />
|
||||
</div>
|
||||
|
||||
{!isAllProjectsSelected && ( <div
|
||||
className={`${!isAllProjectsSelected ? "col-sm-6 col-lg-6" : "col-sm-6 col-lg-6"
|
||||
<div
|
||||
className={`${!isAllProjectsSelected ? "col-sm-6 col-lg-6" : "col-sm-6 col-lg-4"
|
||||
}`}
|
||||
>
|
||||
<TasksCard />
|
||||
</div>)}
|
||||
</div>
|
||||
<div className="col-12 col-xl-4 col-md-6">
|
||||
<div className="card ">
|
||||
<ExpenseStatus />
|
||||
@ -86,13 +84,10 @@ const Dashboard = () => {
|
||||
</div>
|
||||
{!isAllProjectsSelected &&
|
||||
(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 />
|
||||
</div>
|
||||
)}
|
||||
{isAllProjectsSelected && <div className="col-12 col-md-6 mb-sm-0 mb-4">
|
||||
<ProjectWiseTeamCount />
|
||||
</div>}
|
||||
|
||||
{!isAllProjectsSelected && (
|
||||
<div className="col-xxl-4 col-lg-4">
|
||||
@ -102,7 +97,7 @@ const Dashboard = () => {
|
||||
</div>
|
||||
)}
|
||||
{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 />
|
||||
</div>
|
||||
)}
|
||||
@ -123,3 +118,80 @@ const 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>
|
||||
|
||||
@ -52,18 +52,6 @@ const ExpenseAnalysis = () => {
|
||||
legend: { show: false },
|
||||
dataLabels: { enabled: true, formatter: (val) => `${val.toFixed(0)}%` },
|
||||
colors: flatColors,
|
||||
tooltip: {
|
||||
y: {
|
||||
formatter: function (value) {
|
||||
return formatCurrency(value);
|
||||
},
|
||||
},
|
||||
x: {
|
||||
formatter: function (label) {
|
||||
return label;
|
||||
},
|
||||
},
|
||||
},
|
||||
plotOptions: {
|
||||
pie: {
|
||||
donut: {
|
||||
@ -100,7 +88,7 @@ const ExpenseAnalysis = () => {
|
||||
|
||||
<div className="text-end text-sm-end">
|
||||
<FormProvider {...methods}>
|
||||
<DateRangePicker1 pastDays="30" />
|
||||
<DateRangePicker1 />
|
||||
</FormProvider>
|
||||
</div>
|
||||
</div>
|
||||
@ -152,9 +140,7 @@ const ExpenseAnalysis = () => {
|
||||
className="col-6"
|
||||
key={idx}
|
||||
style={{
|
||||
borderLeft: `3px solid ${
|
||||
flatColors[idx % flatColors.length]
|
||||
}`,
|
||||
borderLeft: `3px solid ${flatColors[idx % flatColors.length]}`,
|
||||
}}
|
||||
>
|
||||
<div className="d-flex flex-column text-start">
|
||||
@ -179,6 +165,7 @@ const ExpenseAnalysis = () => {
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -92,40 +92,45 @@ const ExpenseByProject = () => {
|
||||
<div className="card shadow-sm h-100 rounded ">
|
||||
{/* 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">
|
||||
<h5 className="mb-1 me-6 card-title">Monthly Expense </h5>
|
||||
<div className="row w-100">
|
||||
<div className="col-6"> <p className="card-subtitle m-0">{projectName}</p></div>
|
||||
<div className="col-6 d-flex justify-content-between align-items-center px-0">
|
||||
|
||||
|
||||
|
||||
<select
|
||||
className="form-select form-select-sm ms-auto mt-sm-0"
|
||||
value={selectedType}
|
||||
onChange={(e) => setSelectedType(e.target.value)}
|
||||
disabled={typeLoading}
|
||||
style={{ maxWidth: "200px" }}
|
||||
>
|
||||
<option value="">All Categories</option>
|
||||
{expenseCategories?.map((type) => (
|
||||
<option key={type.id} value={type.id}>
|
||||
{type.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<h5 className="mb-1 me-6 card-title">Monthly Expense -</h5>
|
||||
<p className="card-subtitle m-0">{projectName}</p>
|
||||
</div>
|
||||
<div className="btn-group mb-5 ms-n8">
|
||||
<button
|
||||
className="btn btn-sm dropdown-toggle fs-5"
|
||||
type="button"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
{viewMode}
|
||||
</button>
|
||||
<ul className="dropdown-menu dropdown-menu-end ">
|
||||
<li>
|
||||
<button
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
setViewMode("Category");
|
||||
setSelectedType("");
|
||||
}}
|
||||
>
|
||||
Category
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
setViewMode("Project");
|
||||
setSelectedType("");
|
||||
}}
|
||||
>
|
||||
Project
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* Range Buttons + Expense Dropdown */}
|
||||
@ -143,7 +148,22 @@ const ExpenseByProject = () => {
|
||||
{item}
|
||||
</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>
|
||||
|
||||
|
||||
@ -92,7 +92,7 @@ const ProjectProgressChart = ({
|
||||
<div className="d-flex flex-wrap justify-content-between align-items-start mb-2">
|
||||
{/* Left: Title */}
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -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;
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import React from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useJobsProgression } from "../../hooks/useDashboard_Data";
|
||||
import { SpinnerLoader } from "../common/Loader";
|
||||
@ -7,90 +7,56 @@ import { useServiceProject } from "../../hooks/useServiceProject";
|
||||
|
||||
const ServiceJobs = () => {
|
||||
const { projectId } = useParams();
|
||||
|
||||
const { data, isLoading, isError } = useJobsProgression(projectId);
|
||||
|
||||
const jobs = data || {};
|
||||
|
||||
const { data: projectData, isLoading: projectLoading } =
|
||||
useServiceProject(projectId);
|
||||
|
||||
const [activeTab, setActiveTab] = useState("tab-new");
|
||||
|
||||
// 👇 prevents re-running auto logic after first load
|
||||
const hasInitializedTab = useRef(false);
|
||||
|
||||
const { data: projectData, isLoading: projectLoading } = useServiceProject(projectId);
|
||||
const tabMapping = [
|
||||
{ id: "tab-new", label: "My Jobs", key: "myJobs" },
|
||||
{ id: "tab-shipping", label: "In Progress", key: "inProgressJobs" },
|
||||
{ id: "tab-new", label: "My Jobs", key: "allJobs" },
|
||||
{ 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 (
|
||||
<div>
|
||||
<div className="">
|
||||
<div className="card page-min-h">
|
||||
{/* Header */}
|
||||
<div className="card-header d-flex justify-content-between">
|
||||
<div className="card-title mb-0 text-start">
|
||||
<h5 className="mb-1 fw-bold">Service Jobs</h5>
|
||||
<p className="card-subtitle">
|
||||
{projectLoading
|
||||
? "Loading..."
|
||||
: projectData?.name || "All Projects"}
|
||||
{projectLoading ? "Loading..." : projectData?.name || "All Projects"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card-body p-0">
|
||||
<div className="nav-align-top">
|
||||
{/* Tabs */}
|
||||
<ul className="nav nav-tabs nav-fill rounded-0 timeline-indicator-advanced">
|
||||
{tabMapping.map((tab) => (
|
||||
<li className="nav-item" key={tab.id}>
|
||||
|
||||
{/* ---------------- Tabs ---------------- */}
|
||||
<ul className="nav nav-tabs nav-fill rounded-0 timeline-indicator-advanced" role="tablist">
|
||||
{tabMapping.map((t, index) => (
|
||||
<li className="nav-item" key={t.id}>
|
||||
<button
|
||||
type="button"
|
||||
className={`nav-link ${
|
||||
activeTab === tab.id ? "active" : ""
|
||||
}`}
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
className={`nav-link ${index === 0 ? "active" : ""}`}
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target={`#${t.id}`}
|
||||
>
|
||||
{tab.label}
|
||||
{t.label}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
{/* Content */}
|
||||
{/* ---------------- Tab Content ---------------- */}
|
||||
<div className="tab-content border-0 mx-1 text-start">
|
||||
|
||||
{isLoading && (
|
||||
<div
|
||||
className="text-center"
|
||||
style={{
|
||||
height: "250px",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<div className="text-center" style={{ height: "250px", display: "flex", justifyContent: "center", alignItems: "center" }}>
|
||||
<SpinnerLoader />
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{isError && (
|
||||
<p
|
||||
className="text-center"
|
||||
@ -104,19 +70,19 @@ const ServiceJobs = () => {
|
||||
>
|
||||
No data found
|
||||
</p>
|
||||
|
||||
)}
|
||||
|
||||
{!isLoading &&
|
||||
!isError &&
|
||||
tabMapping.map((tab) => {
|
||||
const list = jobs[tab.key] || [];
|
||||
tabMapping.map((t, index) => {
|
||||
const list = jobs[t.key] || [];
|
||||
|
||||
return (
|
||||
<div
|
||||
key={tab.id}
|
||||
className={`tab-pane fade ${
|
||||
activeTab === tab.id ? "show active" : ""
|
||||
}`}
|
||||
key={t.id}
|
||||
className={`tab-pane fade ${index === 0 ? "show active" : ""}`}
|
||||
id={t.id}
|
||||
>
|
||||
{list.length === 0 ? (
|
||||
<p
|
||||
@ -131,19 +97,24 @@ const ServiceJobs = () => {
|
||||
>
|
||||
No jobs found
|
||||
</p>
|
||||
|
||||
) : (
|
||||
<div className="job-scroll-wrapper">
|
||||
{list.map((job, index) => (
|
||||
<React.Fragment key={index}>
|
||||
{list.map((job, i) => (
|
||||
<React.Fragment key={i}>
|
||||
<ul className="timeline mb-0">
|
||||
|
||||
{/* Assigned By */}
|
||||
<li className="timeline-item ps-6 border-left-dashed">
|
||||
<span className="timeline-indicator-advanced timeline-indicator-success border-0 shadow-none">
|
||||
<i className="bx bx-check-circle"></i>
|
||||
</span>
|
||||
<div className="timeline-event ps-1">
|
||||
<small className="text-success text-uppercase">
|
||||
Assigned By
|
||||
</small>
|
||||
<div className="timeline-header">
|
||||
<small className="text-success text-uppercase">
|
||||
Assigned By
|
||||
</small>
|
||||
</div>
|
||||
<h6 className="my-50">{job.assignedBy}</h6>
|
||||
<p className="text-body mb-0">
|
||||
{formatUTCToLocalTime(job.assignedAt)}
|
||||
@ -151,23 +122,23 @@ const ServiceJobs = () => {
|
||||
</div>
|
||||
</li>
|
||||
|
||||
{/* Project */}
|
||||
<li className="timeline-item ps-6 border-transparent">
|
||||
<span className="timeline-indicator-advanced timeline-indicator-primary border-0 shadow-none">
|
||||
<i className="bx bx-map"></i>
|
||||
</span>
|
||||
<div className="timeline-event ps-1">
|
||||
<small className="text-primary text-uppercase">
|
||||
Project
|
||||
</small>
|
||||
<div className="timeline-header">
|
||||
<small className="text-primary text-uppercase">Project</small>
|
||||
</div>
|
||||
<h6 className="my-50">{job.project}</h6>
|
||||
<p className="text-body mb-0">
|
||||
{job.title}
|
||||
</p>
|
||||
<p className="text-body mb-0">{job.title}</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{index < list.length - 1 && (
|
||||
{/* Divider */}
|
||||
{i < list.length - 1 && (
|
||||
<div className="border-1 border-light border-top border-dashed my-4"></div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
|
||||
@ -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) => {
|
||||
if (!dragging || isDocumentType) return;
|
||||
|
||||
@ -127,8 +105,6 @@ const handleWheel = (e) => {
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
||||
onWheel={handleWheel}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseUp={handleMouseUp}
|
||||
|
||||
@ -13,22 +13,37 @@ const Sidebar = () => {
|
||||
id="layout-menu"
|
||||
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">
|
||||
<span className="app-brand-logo demo d-flex align-items-center">
|
||||
{/* <span className="app-brand-logo rounded-circle app-brand-logo-border">
|
||||
<img
|
||||
className="app-brand-logo-sidebar"
|
||||
src="/img/brand/marco.png"
|
||||
width="50"
|
||||
height="40"
|
||||
alt="OnFieldWork logo"
|
||||
alt="logo"
|
||||
aria-label="logo image"
|
||||
style={{ margin: "5px", paddingRight: "5px" }}
|
||||
/>
|
||||
</span>
|
||||
</span> */}
|
||||
|
||||
<span className="app-brand-text">
|
||||
<span className="text-blue ms-1">OnField</span>
|
||||
<span className="text-green">Work</span>
|
||||
<span className="text-dark">.com</span>
|
||||
</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">
|
||||
<img
|
||||
src="/img/brand/marco.png"
|
||||
width="40"
|
||||
height="40"
|
||||
alt="OnFieldWork logo"
|
||||
/>
|
||||
</span>
|
||||
|
||||
<span className="app-brand-text">
|
||||
<span className="text-primary ">OnField</span>
|
||||
<span className="mx-1">Work</span>
|
||||
<span className="text-dark">.com</span>
|
||||
</span>
|
||||
</a>
|
||||
</Link>
|
||||
|
||||
<small className="layout-menu-toggle menu-link text-large ms-auto cursor-pointer">
|
||||
|
||||
@ -33,11 +33,8 @@ const AssignOrg = ({ setStep }) => {
|
||||
});
|
||||
const isPending = isPendingProject || isPendingTenat;
|
||||
const mergedServices = useMemo(() => {
|
||||
const master = Array.isArray(masterService?.data) ? masterService.data : [];
|
||||
const project = Array.isArray(projectServices) ? projectServices : [];
|
||||
|
||||
const combined = [...master, ...project];
|
||||
|
||||
if (!masterService || !projectServices) return [];
|
||||
const combined = [...masterService?.data, ...projectServices];
|
||||
return combined.filter(
|
||||
(item, index, self) => index === self.findIndex((s) => s.id === item.id)
|
||||
);
|
||||
@ -103,8 +100,7 @@ const AssignOrg = ({ setStep }) => {
|
||||
alt="logo"
|
||||
width={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 className="text-end">
|
||||
<button
|
||||
@ -117,10 +113,7 @@ const AssignOrg = ({ setStep }) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="d-flex text-secondary mb-3">
|
||||
{" "}
|
||||
<i className="bx bx-sm bx-info-circle me-2" /> Organization Info
|
||||
</div>
|
||||
<div className="d-flex text-secondary mb-3"> <i className="bx bx-sm bx-info-circle me-2" /> Organization Info</div>
|
||||
{/* Contact Info */}
|
||||
<div className="col-md-12 mb-4">
|
||||
<div className="d-flex">
|
||||
@ -139,7 +132,7 @@ const AssignOrg = ({ setStep }) => {
|
||||
className="form-label me-2 mb-0 fw-semibold"
|
||||
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>
|
||||
<div className="text-muted">{orgData.contactNumber}</div>
|
||||
</div>
|
||||
@ -150,7 +143,7 @@ const AssignOrg = ({ setStep }) => {
|
||||
className="form-label me-2 mb-0 fw-semibold"
|
||||
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>
|
||||
<div className="text-muted text-wrap">{orgData.email}</div>
|
||||
</div>
|
||||
@ -173,7 +166,7 @@ const AssignOrg = ({ setStep }) => {
|
||||
className="form-label me-1 mb-0 fw-semibold"
|
||||
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>
|
||||
<div className="text-muted text-start">{orgData.address}</div>
|
||||
</div>
|
||||
@ -187,11 +180,7 @@ const AssignOrg = ({ setStep }) => {
|
||||
<>
|
||||
{/* Organization Type */}
|
||||
<div className="mb-3 text-start">
|
||||
<Label
|
||||
htmlFor="organizationTypeId"
|
||||
className="mb-3 fw-semibold"
|
||||
required
|
||||
>
|
||||
<Label htmlFor="organizationTypeId" className="mb-3 fw-semibold" required>
|
||||
Organization Type
|
||||
</Label>
|
||||
<div className="d-flex flex-wrap gap-3 mt-1">
|
||||
@ -225,11 +214,7 @@ const AssignOrg = ({ setStep }) => {
|
||||
|
||||
{/* Services */}
|
||||
<div className="mb-3">
|
||||
<Label
|
||||
htmlFor="serviceIds"
|
||||
className="mb-3 fw-semibold"
|
||||
required
|
||||
>
|
||||
<Label htmlFor="serviceIds" className="mb-3 fw-semibold" required>
|
||||
Select Services
|
||||
</Label>
|
||||
{mergedServices?.map((service) => (
|
||||
@ -270,8 +255,8 @@ const AssignOrg = ({ setStep }) => {
|
||||
{isPending
|
||||
? "Please wait..."
|
||||
: flowType === "default"
|
||||
? "Assign to Organization"
|
||||
: "Assign to Project"}
|
||||
? "Assign to Organization"
|
||||
: "Assign to Project"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@ -237,116 +237,13 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
|
||||
))}
|
||||
</span>
|
||||
</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)}>
|
||||
<div className="form-label text-start">
|
||||
<div className="row mb-1">
|
||||
<div className="col-12">
|
||||
<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>
|
||||
<AppFormController
|
||||
name="organizationId"
|
||||
@ -399,7 +296,109 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
|
||||
)}
|
||||
</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>
|
||||
|
||||
@ -8,7 +8,7 @@ import {
|
||||
useGroups,
|
||||
useWorkCategoriesMaster,
|
||||
} 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 Label from "../../common/Label";
|
||||
import { useSelectedProject } from "../../../slices/apiDataManager";
|
||||
@ -28,16 +28,12 @@ const taskSchema = z.object({
|
||||
comment: z.string(),
|
||||
});
|
||||
|
||||
|
||||
|
||||
const TaskModel = ({ project, onSubmit, onClose }) => {
|
||||
const currentService = useCurrentService()
|
||||
const defaultModel = {
|
||||
const defaultModel = {
|
||||
id: null,
|
||||
buildingID: "",
|
||||
floorId: "",
|
||||
workAreaId: "",
|
||||
serviceId: currentService ?? "",
|
||||
serviceId: "",
|
||||
activityGroupId: "",
|
||||
activityID: "",
|
||||
workCategoryId: "",
|
||||
@ -45,6 +41,8 @@ const TaskModel = ({ project, onSubmit, onClose }) => {
|
||||
completedWork: 0,
|
||||
comment: "",
|
||||
};
|
||||
|
||||
const TaskModel = ({ project, onSubmit, onClose }) => {
|
||||
// const { activities, loading: activityLoading } = useActivitiesMaster();
|
||||
const { categories, categoryLoading } = useWorkCategoriesMaster();
|
||||
|
||||
|
||||
@ -55,7 +55,7 @@ const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
|
||||
const { data: assignedServices, isLoading: servicesLoading } =
|
||||
useProjectAssignedServices(projectId);
|
||||
|
||||
const { control, setValue } = useForm({
|
||||
const { control } = useForm({
|
||||
defaultValues: {
|
||||
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 (
|
||||
<>
|
||||
{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="ms-4 mt-n1">
|
||||
{!servicesLoading && assignedServices?.length > 0 && (
|
||||
<AppFormController
|
||||
name="serviceId"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<SelectField
|
||||
label="Select Service"
|
||||
options={[...(assignedServices ?? [])]}
|
||||
placeholder="Choose a Service"
|
||||
labelKey="name"
|
||||
valueKey="id"
|
||||
value={field.value}
|
||||
onChange={(val) => {
|
||||
field.onChange(val);
|
||||
dispatch(setService(val));
|
||||
}}
|
||||
isLoading={servicesLoading}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
assignedServices.length > 1 ? (
|
||||
<AppFormController
|
||||
name="serviceId"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<SelectField
|
||||
label="Select Service"
|
||||
options={[{ id: "", name: "All Projects" }, ...(assignedServices ?? [])]}
|
||||
placeholder="Choose a Service"
|
||||
required
|
||||
labelKey="name"
|
||||
valueKey="id"
|
||||
value={field.value}
|
||||
onChange={(val) => {
|
||||
field.onChange(val);
|
||||
handleServiceChange(val);
|
||||
}}
|
||||
isLoading={servicesLoading}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<h5>{assignedServices[0].name}</h5>
|
||||
)
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -159,9 +159,8 @@ const ProjectListView = ({ data, currentPage, totalPages, paginate }) => {
|
||||
</td>
|
||||
))}
|
||||
<td
|
||||
className={`mx-2 ${
|
||||
canManageProject ? "d-sm-table-cell" : "d-none"
|
||||
}`}
|
||||
className={`mx-2 ${canManageProject ? "d-sm-table-cell" : "d-none"
|
||||
}`}
|
||||
>
|
||||
<div className="dropdown z-2">
|
||||
<button
|
||||
@ -239,7 +238,7 @@ const ProjectListView = ({ data, currentPage, totalPages, paginate }) => {
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
totalPages={totalPages}
|
||||
onPageChange={paginate}
|
||||
paginate={paginate}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -170,7 +170,7 @@ const ProjectStatistics = ({ project }) => {
|
||||
<h5 className="card-action-title mb-0">
|
||||
{" "}
|
||||
<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>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
import TeamEmployeeList from "./TeamEmployeeList";
|
||||
import { useOrganization } from "../../../hooks/useDirectory";
|
||||
import { useOrganizationsList } from "../../../hooks/useOrganization";
|
||||
@ -14,50 +14,40 @@ const TeamAssignToProject = ({ closeModal }) => {
|
||||
const project = useSelectedProject();
|
||||
const { data, isLoading, isError, error } =
|
||||
useProjectAssignedOrganizationsName(project);
|
||||
const { control, watch, setValue, formState: { errors } } = useForm({
|
||||
const { control, watch, formState: { errors } } = useForm({
|
||||
defaultValues: { organizationId: "" },
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.length === 1) {
|
||||
setValue("organizationId", data[0].id, {
|
||||
shouldValidate: true,
|
||||
shouldDirty: true,
|
||||
});
|
||||
}
|
||||
}, [data, setValue]);
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
{/* <p className="fs-5 fs-seminbod ">Assign Employee To Project </p> */}
|
||||
<h5 className="mb-4">Assign Employee To Project</h5>
|
||||
|
||||
<div className="row align-items-center gx-5 text-start">
|
||||
<div className="col-12 col-md-6 mb-2">
|
||||
<AppFormController
|
||||
name="organizationId"
|
||||
control={control}
|
||||
rules={{ required: "Organization is required" }}
|
||||
render={({ field }) => (
|
||||
<SelectField
|
||||
label="Select Organization"
|
||||
options={data ?? []}
|
||||
placeholder="Choose an Organization"
|
||||
required
|
||||
labelKey="name"
|
||||
valueKey="id"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
isLoading={isLoading}
|
||||
className="m-0 w-100"
|
||||
/>
|
||||
<div className="col-12 col-md-6 mb-2">
|
||||
<AppFormController
|
||||
name="organizationId"
|
||||
control={control}
|
||||
rules={{ required: "Organization is required" }}
|
||||
render={({ field }) => (
|
||||
<SelectField
|
||||
label="Select Organization"
|
||||
options={data ?? []}
|
||||
placeholder="Choose an Organization"
|
||||
required
|
||||
labelKey="name"
|
||||
valueKey="id"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
isLoading={isLoading}
|
||||
className="m-0 w-100"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{errors.organizationId && (
|
||||
<small className="danger-text">{errors.organizationId.message}</small>
|
||||
)}
|
||||
/>
|
||||
{errors.organizationId && (
|
||||
<small className="danger-text">{errors.organizationId.message}</small>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-12 col-md-6 mt-n2">
|
||||
</div>
|
||||
<div className="col-12 col-md-6 mt-n5">
|
||||
<div className="d-flex flex-column">
|
||||
<label htmlFor="search" className="form-label mb-1">
|
||||
Search Employee
|
||||
|
||||
@ -162,23 +162,15 @@ const TeamEmployeeList = ({ organizationId, searchTerm, closeModal }) => {
|
||||
<table className="table" style={{ maxHeight: "80px", overflowY: "auto" }}>
|
||||
<thead className=" position-sticky top-0">
|
||||
<tr>
|
||||
<th>Select</th>
|
||||
<th>Employee</th>
|
||||
<th>Service</th>
|
||||
<th>Job Role</th>
|
||||
<th>Select</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{employees.map((emp, index) => (
|
||||
<tr key={emp.id}>
|
||||
<td>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={emp.isChecked}
|
||||
onChange={() => handleCheckboxChange(index)}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<div className="d-flex align-items-center">
|
||||
<Avatar firstName={emp.firstName} lastName={emp.lastName} />
|
||||
@ -187,7 +179,6 @@ const TeamEmployeeList = ({ organizationId, searchTerm, closeModal }) => {
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<select
|
||||
value={emp.serviceId}
|
||||
@ -230,7 +221,14 @@ const TeamEmployeeList = ({ organizationId, searchTerm, closeModal }) => {
|
||||
<div className="danger-text">{emp.errors.jobRole}</div>
|
||||
)}
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={emp.isChecked}
|
||||
onChange={() => handleCheckboxChange(index)}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
||||
@ -34,7 +34,7 @@ const Teams = () => {
|
||||
const [selectedEmployee, setSelectedEmployee] = useState(null);
|
||||
const [deleteEmployee, setDeleteEmplyee] = useState(null);
|
||||
const [activeEmployee, setActiveEmployee] = useState(false);
|
||||
const { control, watch, setValue } = useForm({
|
||||
const { control, watch } = useForm({
|
||||
defaultValues: {
|
||||
selectedService: "",
|
||||
searchTerm: "",
|
||||
@ -136,18 +136,6 @@ const Teams = () => {
|
||||
return () => eventBus.off("employee", employeeHandler);
|
||||
}, [employeeHandler]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!servicesLoading && assignedServices?.length === 1) {
|
||||
const serviceId = assignedServices[0].id;
|
||||
|
||||
setValue("selectedService", serviceId, {
|
||||
shouldDirty: true,
|
||||
shouldValidate: true,
|
||||
});
|
||||
}
|
||||
}, [assignedServices, servicesLoading, setValue]);
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{AssigTeam && (
|
||||
@ -174,28 +162,35 @@ const Teams = () => {
|
||||
<div className="card-body">
|
||||
<div className="row align-items-center justify-content-between mb-4 g-3">
|
||||
<div className="col-md-6 col-12 d-flex flex-wrap align-items-center gap-3">
|
||||
{!servicesLoading && assignedServices?.length > 0 && (
|
||||
<div className="col-12 col-md-6 mb-2 text-start">
|
||||
<AppFormController
|
||||
name="selectedService"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<SelectField
|
||||
label="Select Service"
|
||||
options={[ ...assignedServices]}
|
||||
placeholder="Choose a Service"
|
||||
labelKey="name"
|
||||
valueKey="id"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
isLoading={servicesLoading}
|
||||
className="w-100"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!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">
|
||||
<AppFormController
|
||||
name="selectedService"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<SelectField
|
||||
label="Select Service"
|
||||
options={[{ id: "", name: "All Services" }, ...assignedServices]}
|
||||
placeholder="Choose a Service"
|
||||
labelKey="name"
|
||||
valueKey="id"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
isLoading={servicesLoading}
|
||||
className="w-100"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
<div className="form-check form-switch d-flex align-items-center text-nowrap">
|
||||
|
||||
@ -56,7 +56,7 @@ const OrganizationInfo = ({ onNext, onPrev, onSubmitTenant }) => {
|
||||
// onSubmitTenant(data);
|
||||
// onNext();
|
||||
const tenantPayload = { ...data, onBoardingDate: moment.utc(data.onBoardingDate, "DD-MM-YYYY").toISOString() }
|
||||
CreateTenant(tenantPayload);
|
||||
// CreateTenant(tenantPayload);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -3,7 +3,6 @@ import { useController } from "react-hook-form";
|
||||
|
||||
const DatePicker = ({
|
||||
name,
|
||||
defaultDate,
|
||||
control,
|
||||
size="sm",
|
||||
placeholder = "DD-MM-YYYY",
|
||||
|
||||
@ -93,7 +93,6 @@ export const DateRangePicker1 = ({
|
||||
resetSignal,
|
||||
defaultRange = true,
|
||||
maxDate = null,
|
||||
pastDays = 6,
|
||||
...rest
|
||||
}) => {
|
||||
const inputRef = useRef(null);
|
||||
@ -106,7 +105,7 @@ export const DateRangePicker1 = ({
|
||||
const applyDefaultDates = () => {
|
||||
const today = 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 formattedStart = format(past);
|
||||
|
||||
@ -2,17 +2,10 @@ import React from "react";
|
||||
import { useDeliverChallane } from "../../hooks/usePurchase";
|
||||
import { SpinnerLoader } from "../common/Loader";
|
||||
import { formatUTCToLocalTime } from "../../utils/dateUtils";
|
||||
import { FileView } from "../Expenses/Filelist"; // Assuming FileView is the component showing the file icon/name
|
||||
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";
|
||||
import { FileView } from "../Expenses/Filelist";
|
||||
|
||||
const DeliverChallanList = ({ purchaseId, viewDocuments }) => {
|
||||
const { setDocumentView } = usePurchaseContext();
|
||||
const { data, isLoading, isError, error } = useDeliverChallane(purchaseId);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="d-flex justify-content-center align-items-center text-center vh-50">
|
||||
@ -24,12 +17,10 @@ const DeliverChallanList = ({ purchaseId, viewDocuments }) => {
|
||||
if (isError) {
|
||||
return (
|
||||
<div className="py-3">
|
||||
{/* Assuming Error component is used here */}
|
||||
<Error error={error} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!isLoading && data.length === 0)
|
||||
return (
|
||||
<div className="d-flex justify-content-center align-items-center text-center vh-50">
|
||||
@ -64,29 +55,8 @@ const DeliverChallanList = ({ purchaseId, viewDocuments }) => {
|
||||
{item.description || "-"}
|
||||
</p>
|
||||
|
||||
{/* Check if attachment exists and open document view on click */}
|
||||
{item.attachment?.preSignedUrl && (
|
||||
<div
|
||||
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>
|
||||
<FileView file={item.attachment} viewFile={viewDocuments} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -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;
|
||||
@ -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>
|
||||
// );
|
||||
// };
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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 AppFormProvider = FormProvider;
|
||||
export const AppFormController = Controller;
|
||||
export const useAppFormContext = useFormContext;
|
||||
export const useAppWatch = useWatch;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from "react";
|
||||
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 }) => {
|
||||
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,
|
||||
});
|
||||
};
|
||||
|
||||
@ -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,
|
||||
});
|
||||
};
|
||||
@ -122,29 +122,20 @@ const AttendancePage = () => {
|
||||
// Conditionally include Regularization tab based on permission
|
||||
...(DoRegularized
|
||||
? [
|
||||
{
|
||||
id: "regularization",
|
||||
title: "Regularization",
|
||||
content: (
|
||||
<Regularization
|
||||
searchTerm={searchTerm}
|
||||
organizationId={appliedFilters.selectedOrganization}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]
|
||||
{
|
||||
id: "regularization",
|
||||
title: "Regularization",
|
||||
content: (
|
||||
<Regularization
|
||||
searchTerm={searchTerm}
|
||||
organizationId={appliedFilters.selectedOrganization}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
// --- END: Tab Configuration Array
|
||||
useEffect(() => {
|
||||
if (!orgLoading && organizations?.length === 1) {
|
||||
setAppliedFilters((prev) => ({
|
||||
...prev,
|
||||
selectedOrganization: organizations[0].id,
|
||||
}));
|
||||
}
|
||||
}, [orgLoading, organizations]);
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -157,11 +148,11 @@ const AttendancePage = () => {
|
||||
{(modelConfig?.action === 0 ||
|
||||
modelConfig?.action === 1 ||
|
||||
modelConfig?.action === 2) && (
|
||||
<CheckCheckOutmodel
|
||||
modeldata={modelConfig}
|
||||
closeModal={closeModal}
|
||||
/>
|
||||
)}
|
||||
<CheckCheckOutmodel
|
||||
modeldata={modelConfig}
|
||||
closeModal={closeModal}
|
||||
/>
|
||||
)}
|
||||
{/* For view logs */}
|
||||
{modelConfig?.action === 6 && (
|
||||
<AttendLogs Id={modelConfig?.id} closeModal={closeModal} />
|
||||
@ -187,16 +178,18 @@ const AttendancePage = () => {
|
||||
<ul className="nav nav-tabs" role="tablist">
|
||||
{tabsData.map((tab) => (
|
||||
<li
|
||||
className={`nav-item ${tab.id === "regularization" && !DoRegularized
|
||||
? "d-none"
|
||||
: ""
|
||||
}`} // Keep the d-none logic for regularization, although it's filtered above
|
||||
className={`nav-item ${
|
||||
tab.id === "regularization" && !DoRegularized
|
||||
? "d-none"
|
||||
: ""
|
||||
}`} // Keep the d-none logic for regularization, although it's filtered above
|
||||
key={tab.id}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className={`nav-link ${activeTab === tab.id ? "active" : ""
|
||||
} fs-6`}
|
||||
className={`nav-link ${
|
||||
activeTab === tab.id ? "active" : ""
|
||||
} fs-6`}
|
||||
onClick={() => handleTabChange(tab.id)}
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target={`#navs-top-${tab.id}`}
|
||||
@ -252,8 +245,9 @@ const AttendancePage = () => {
|
||||
{tabsData.map((tab) => (
|
||||
<div
|
||||
key={tab.id}
|
||||
className={`tab-pane fade ${activeTab === tab.id ? "show active" : ""
|
||||
} py-0 ${tab.id === "all" ? "mx-2" : "p-3"}`}
|
||||
className={`tab-pane fade ${
|
||||
activeTab === tab.id ? "show active" : ""
|
||||
} py-0 ${tab.id === "all" ? "mx-2" : "p-3"}`}
|
||||
id={`navs-top-${tab.id}`}
|
||||
role="tabpanel"
|
||||
>
|
||||
|
||||
@ -15,7 +15,7 @@ const TaskPlanning = () => {
|
||||
const selectedProject = useSelectedProject();
|
||||
const selectedService = useCurrentService();
|
||||
const dispatch = useDispatch();
|
||||
const { control,setValue } = useForm({
|
||||
const { control } = useForm({
|
||||
defaultValues: {
|
||||
serviceFilter: selectedService ?? ""
|
||||
},
|
||||
@ -39,20 +39,6 @@ const TaskPlanning = () => {
|
||||
return <div className="text-center py-5">Loading...</div>;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!servicesLoading && data?.length === 1) {
|
||||
const serviceId = data[0].id;
|
||||
|
||||
setValue("serviceFilter", serviceId, {
|
||||
shouldValidate: true,
|
||||
shouldDirty: true,
|
||||
});
|
||||
|
||||
dispatch(setService(serviceId));
|
||||
}
|
||||
}, [servicesLoading, data, setValue, dispatch]);
|
||||
|
||||
|
||||
return (
|
||||
<div className="container-fluid">
|
||||
<Breadcrumb
|
||||
@ -65,7 +51,7 @@ const TaskPlanning = () => {
|
||||
<div className="card py-2 page-min-h">
|
||||
<div className="col-sm-4 col-md-3 col-12 px-4 py-2 text-start">
|
||||
{data?.length === 0 ? (
|
||||
<p className="badge bg-label-secondary m-0"></p>
|
||||
<p className="badge bg-label-secondary m-0">Service not assigned</p>
|
||||
) : (
|
||||
<AppFormController
|
||||
name="serviceFilter"
|
||||
@ -92,7 +78,6 @@ const TaskPlanning = () => {
|
||||
|
||||
|
||||
|
||||
|
||||
{/* Planning Component */}
|
||||
{selectedProject ? (
|
||||
<InfraPlanning />
|
||||
|
||||
@ -46,14 +46,14 @@ const DailyProgrssReport = () => {
|
||||
filter,
|
||||
};
|
||||
|
||||
const { control,setValue } = useForm({
|
||||
const { control } = useForm({
|
||||
defaultValues: {
|
||||
serviceFilter: ""
|
||||
}
|
||||
});
|
||||
const clearFilter = () => {
|
||||
updatedRef.current?.onClear();
|
||||
};
|
||||
};
|
||||
const handleFilter = (filterObj) => {
|
||||
setFilter(filterObj)
|
||||
}
|
||||
@ -85,22 +85,6 @@ const DailyProgrssReport = () => {
|
||||
return updated;
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoading && data?.length === 1) {
|
||||
const serviceId = data[0].id;
|
||||
|
||||
// set form value
|
||||
setValue("serviceFilter", serviceId, {
|
||||
shouldDirty: true,
|
||||
shouldValidate: true,
|
||||
});
|
||||
|
||||
// set local state
|
||||
setService(serviceId);
|
||||
}
|
||||
}, [isLoading, data, setValue]);
|
||||
|
||||
return (
|
||||
<div className="container-fluid">
|
||||
<DailyProgrssContext.Provider value={contextDispatcher}>
|
||||
@ -145,7 +129,7 @@ const DailyProgrssReport = () => {
|
||||
render={({ field }) => (
|
||||
<SelectField
|
||||
label="Services"
|
||||
options={[{ id: "", name: "All Services" }, ...(data ?? [])]}
|
||||
options={[{ id: "", name: "All Projects" }, ...(data ?? [])]}
|
||||
placeholder="Select Service"
|
||||
labelKey="name"
|
||||
valueKey="id"
|
||||
|
||||
@ -86,7 +86,7 @@ const LandingPage = () => {
|
||||
<li className="nav-item ms-1">
|
||||
<a
|
||||
className="btn btn-sm btn-green btn-ovel-small px-3 my-1"
|
||||
href="/auth/reqest/demo"
|
||||
href="#"
|
||||
>
|
||||
Request For Demo
|
||||
</a>
|
||||
@ -162,7 +162,7 @@ const LandingPage = () => {
|
||||
Make data-driven decisions with real-time project analytics.
|
||||
</p>
|
||||
<a
|
||||
href="/auth/reqest/demo"
|
||||
href="#"
|
||||
className="btn btn-green btn-square-small btn-lg mt-3 p-3"
|
||||
>
|
||||
View Demo
|
||||
@ -185,7 +185,7 @@ const LandingPage = () => {
|
||||
Eliminate Paper Receipts. Take Control of Your Cash Flow.
|
||||
</p>
|
||||
<a
|
||||
href="/auth/reqest/demo"
|
||||
href="#"
|
||||
className="btn btn-green btn-square-small btn-lg mt-3 p-3"
|
||||
>
|
||||
View Demo
|
||||
@ -320,6 +320,65 @@ const LandingPage = () => {
|
||||
</p>
|
||||
{/* <SubscriptionPlans/> */}
|
||||
<SubscriptionPlans></SubscriptionPlans>
|
||||
{/* <div className="row g-4 justify-content-center hidden">
|
||||
<div className="col-md-4">
|
||||
<div className="card pricing-card border-0 shadow-sm">
|
||||
<div className="card-body">
|
||||
<h5 className="text-green fw-bold">Starter</h5>
|
||||
<h2>
|
||||
₹499<span className="fs-6 text-muted">/month</span>
|
||||
</h2>
|
||||
<ul className="list-unstyled mt-3 mb-4 text-muted">
|
||||
<li>Up to 10 users</li>
|
||||
<li>Basic reporting</li>
|
||||
<li>Project tracking</li>
|
||||
</ul>
|
||||
<a href="#" className="btn btn-outline-success">
|
||||
Request Demo
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-md-4">
|
||||
<div className="card pricing-card border-primary shadow-lg">
|
||||
<div className="card-body">
|
||||
<h5 className="text-green fw-bold">Professional</h5>
|
||||
<h2>
|
||||
₹999<span className="fs-6 text-muted">/month</span>
|
||||
</h2>
|
||||
<ul className="list-unstyled mt-3 mb-4 text-muted">
|
||||
<li>Unlimited projects</li>
|
||||
<li>Expense & attendance tracking</li>
|
||||
<li>Advanced analytics</li>
|
||||
</ul>
|
||||
<a
|
||||
href="https://ofw.marcoaiot.com/auth/reqest/demo"
|
||||
className="btn btn-green btn-square-small"
|
||||
>
|
||||
Request Demo
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-md-4">
|
||||
<div className="card pricing-card border-0 shadow-sm">
|
||||
<div className="card-body">
|
||||
<h5 className="text-green fw-bold">Enterprise</h5>
|
||||
<h2>Custom</h2>
|
||||
<ul className="list-unstyled mt-3 mb-4 text-muted">
|
||||
<li>Dedicated support</li>
|
||||
<li>Custom integrations</li>
|
||||
<li>Private cloud hosting</li>
|
||||
</ul>
|
||||
<a href="#" className="btn btn-outline-success">
|
||||
Request Demo
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</section>
|
||||
{/* <!-- About --> */}
|
||||
|
||||
@ -9,8 +9,7 @@ const SubscriptionPlans = () => {
|
||||
const [frequency, setFrequency] = useState(1);
|
||||
const { data, isLoading, isError, error } = useSubscription(frequency);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
console.log(data);
|
||||
|
||||
const frequencyLabel = (freq) => {
|
||||
switch (freq) {
|
||||
case 0:
|
||||
@ -36,8 +35,9 @@ const SubscriptionPlans = () => {
|
||||
<button
|
||||
key={idx}
|
||||
type="button"
|
||||
className={`btn btn-${frequency === idx ? "primary" : "outline-secondary"
|
||||
}`}
|
||||
className={`btn btn-${
|
||||
frequency === idx ? "primary" : "outline-secondary"
|
||||
}`}
|
||||
onClick={() => setFrequency(idx)}
|
||||
>
|
||||
{label}
|
||||
@ -64,7 +64,7 @@ const SubscriptionPlans = () => {
|
||||
<p>{error.name}</p>
|
||||
</div>
|
||||
) : (
|
||||
data.map((plan, index) => (
|
||||
data.map((plan) => (
|
||||
<div key={plan.id} className="col-xl-4 col-lg-6 col-md-6">
|
||||
<div className="card h-100 shadow-lg border-0 p-3 text-center p-10">
|
||||
{/* Header */}
|
||||
@ -102,69 +102,24 @@ const SubscriptionPlans = () => {
|
||||
<h6 className="fw-bold text-uppercase border-top pt-3 mb-3 text-center">
|
||||
Features
|
||||
</h6>
|
||||
|
||||
<div className="accordion" id={`planFeatures-${plan.id}`}>
|
||||
<ul className="list-unstyled text-start mb-4 ms-7 fs-6">
|
||||
{plan.features?.modules &&
|
||||
Object.entries(plan.features.modules)
|
||||
.sort(([, a], [, b]) => Number(b.enabled) - Number(a.enabled))
|
||||
.map(([key, mod]) => {
|
||||
|
||||
if (!mod || !mod.name) return null;
|
||||
const isFirst = index === 0;
|
||||
|
||||
return (
|
||||
<div className="accordion-item mb-2" key={`${plan.id}-${mod.id}`}>
|
||||
<h2 id={`heading-${plan.id}-${mod.id}`} className="accordion-header">
|
||||
<button
|
||||
className="accordion-button py-2 d-flex justify-content-between align-items-center"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target={`#collapse-${plan.id}-${mod.id}`}
|
||||
aria-expanded={`${isFirst ? "true" : "false"}`}
|
||||
aria-controls={`collapse-${plan.id}-${mod.id}`}
|
||||
>
|
||||
<span className="fw-semibold d-flex align-items-center">
|
||||
{mod.enabled ? (
|
||||
<i className="fa-regular fa-circle-check text-success me-2"></i>
|
||||
) : (
|
||||
<i className="fa-regular fa-circle-xmark text-danger me-2"></i>
|
||||
)}
|
||||
{mod.name}
|
||||
</span>
|
||||
|
||||
<i className="bx bx-chevron-down ms-2"></i>
|
||||
</button>
|
||||
</h2>
|
||||
|
||||
<div
|
||||
id={`collapse-${plan.id}-${mod.id}`}
|
||||
className={`accordion-collapse collapse ${isFirst ? "show" : ""}`}
|
||||
aria-labelledby={`heading-${plan.id}-${mod.id}`}
|
||||
>
|
||||
<div className="accordion-body py-2">
|
||||
{mod.features?.length > 0 ? (
|
||||
<ul className="list-unstyled ms-2">
|
||||
{mod.features.map((feat) => (
|
||||
<li key={feat.id} className="d-flex align-items-start mb-1">
|
||||
<i
|
||||
className={`bx bxs-circle ${mod.enabled ? "text-success" : "text-danger"
|
||||
} me-2 mt-1`}
|
||||
style={{ fontSize: "8px" }}
|
||||
></i>
|
||||
<span>{feat.name}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p className="text-muted small mb-0">No additional features</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
Object.values(plan.features.modules).map((mod) =>
|
||||
mod && mod.name ? (
|
||||
<li
|
||||
key={mod.id}
|
||||
className="d-flex align-items-center mb-2"
|
||||
>
|
||||
{mod.enabled ? (
|
||||
<i className="fa-regular fa-circle-check text-success me-2"></i>
|
||||
) : (
|
||||
<i className="fa-regular fa-circle-xmark text-danger me-2"></i>
|
||||
)}
|
||||
{mod.name}
|
||||
</li>
|
||||
) : null
|
||||
)}
|
||||
</ul>
|
||||
|
||||
{/* Button */}
|
||||
<div className="mt-auto">
|
||||
@ -183,7 +138,6 @@ const SubscriptionPlans = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -20,7 +20,7 @@ const ServiceProjectDisplay = ({ listView, selectedStatuses, searchTerm }) => {
|
||||
const { manageServiceProject, setManageServiceProject } = useProjectContext();
|
||||
const debouncedSearch = useDebounce(searchTerm, 500);
|
||||
const { data, isLoading, isError, error } = useServiceProjects(
|
||||
9,
|
||||
ITEMS_PER_PAGE,
|
||||
currentPage,
|
||||
debouncedSearch
|
||||
);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import LoginWithOtp from "./LoginWithOtp";
|
||||
import React from 'react'
|
||||
import LoginWithOtp from './LoginWithOtp'
|
||||
|
||||
const MainLoginWithOTPPage = () => {
|
||||
return (
|
||||
@ -8,7 +8,7 @@ const MainLoginWithOTPPage = () => {
|
||||
<div className="d-none d-lg-flex col-lg-7 col-xl-8 align-items-center p-5">
|
||||
<div className="w-100 d-flex justify-content-center">
|
||||
<img
|
||||
src="/img/illustrations/registration.jpg"
|
||||
src="/img/illustrations/worker_03.png"
|
||||
className="img-fluid"
|
||||
alt="Login image"
|
||||
width="70%"
|
||||
@ -20,7 +20,7 @@ const MainLoginWithOTPPage = () => {
|
||||
<LoginWithOtp />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default MainLoginWithOTPPage;
|
||||
export default MainLoginWithOTPPage
|
||||
@ -1,14 +1,14 @@
|
||||
import React from "react";
|
||||
import ResetPasswordPage from "./ResetPassword";
|
||||
import React from 'react'
|
||||
import ResetPasswordPage from './ResetPassword'
|
||||
|
||||
const MainResetPasswordPage = () => {
|
||||
return (
|
||||
<>
|
||||
<>
|
||||
<div className="authentication-inner row m-0">
|
||||
<div className="d-none d-lg-flex col-lg-7 col-xl-8 align-items-center p-5">
|
||||
<div className="w-100 d-flex justify-content-center">
|
||||
<img
|
||||
src="/img/illustrations/registration.jpg"
|
||||
src="/img/illustrations/worker_03.png"
|
||||
className="img-fluid"
|
||||
alt="Login image"
|
||||
width="70%"
|
||||
@ -20,7 +20,7 @@ const MainResetPasswordPage = () => {
|
||||
<ResetPasswordPage />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default MainResetPasswordPage;
|
||||
export default MainResetPasswordPage
|
||||
@ -41,7 +41,6 @@ import Pagination from "../../components/common/Pagination";
|
||||
import handleEmployeeExport from "../../components/Employee/handleEmployeeExport";
|
||||
import { SpinnerLoader } from "../../components/common/Loader";
|
||||
import ManageReporting from "../../components/Employee/ManageReporting";
|
||||
import { capitalizeFirstLetter } from "../../utils/appUtils";
|
||||
|
||||
const EmployeeList = () => {
|
||||
const selectedProjectId = useSelector(
|
||||
@ -74,11 +73,9 @@ const EmployeeList = () => {
|
||||
const [selectedEmployee, setSelectedEmployee] = useState(null);
|
||||
const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const [selectedEmpFordelete, setSelectedEmpFordelete] = useState(null);
|
||||
const [showManageReportingModal, setShowManageReportingModal] =
|
||||
useState(false);
|
||||
const [showManageReportingModal, setShowManageReportingModal] = useState(false);
|
||||
const [employeeLodaing, setemployeeLodaing] = useState(false);
|
||||
const ViewTeamMember = useHasUserPermission(VIEW_TEAM_MEMBERS);
|
||||
const ViewAllEmployee = useHasUserPermission(VIEW_ALL_EMPLOYEES);
|
||||
const { mutate: suspendEmployee, isPending: empLodaing } = useSuspendEmployee(
|
||||
{
|
||||
setIsDeleteModalOpen,
|
||||
@ -142,15 +139,11 @@ const EmployeeList = () => {
|
||||
|
||||
const tableRef = useRef(null);
|
||||
const handleExport = (type) => {
|
||||
handleEmployeeExport(
|
||||
type,
|
||||
employeeList,
|
||||
filteredData,
|
||||
searchText,
|
||||
tableRef
|
||||
);
|
||||
handleEmployeeExport(type, employeeList, filteredData, searchText, tableRef);
|
||||
};
|
||||
|
||||
|
||||
|
||||
const handleAllEmployeesToggle = (e) => {
|
||||
const isChecked = e.target.checked;
|
||||
setShowInactive(false);
|
||||
@ -179,12 +172,10 @@ const EmployeeList = () => {
|
||||
useEffect(() => {
|
||||
if (!loading && Array.isArray(employees)) {
|
||||
const sorted = [...employees].sort((a, b) => {
|
||||
const nameA = `${a.firstName || ""}${a.middleName || ""}${
|
||||
a.lastName || ""
|
||||
}`.toLowerCase();
|
||||
const nameB = `${b.firstName || ""}${b.middleName || ""}${
|
||||
b.lastName || ""
|
||||
}`.toLowerCase();
|
||||
const nameA = `${a.firstName || ""}${a.middleName || ""}${a.lastName || ""
|
||||
}`.toLowerCase();
|
||||
const nameB = `${b.firstName || ""}${b.middleName || ""}${b.lastName || ""
|
||||
}`.toLowerCase();
|
||||
return nameA?.localeCompare(nameB);
|
||||
});
|
||||
|
||||
@ -274,9 +265,8 @@ const EmployeeList = () => {
|
||||
? "Suspend Employee"
|
||||
: "Reactivate Employee"
|
||||
}
|
||||
message={`Are you sure you want to ${
|
||||
selectedEmpFordelete?.isActive ? "suspend" : "reactivate"
|
||||
} this employee?`}
|
||||
message={`Are you sure you want to ${selectedEmpFordelete?.isActive ? "suspend" : "reactivate"
|
||||
} this employee?`}
|
||||
onSubmit={(id) =>
|
||||
suspendEmployee({
|
||||
employeeId: id,
|
||||
@ -297,7 +287,7 @@ const EmployeeList = () => {
|
||||
]}
|
||||
></Breadcrumb>
|
||||
|
||||
{ViewTeamMember || ViewAllEmployee ? (
|
||||
{ViewTeamMember ? (
|
||||
// <div className="row">
|
||||
<div className="card p-1 page-min-h">
|
||||
<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>
|
||||
</button>
|
||||
<ul
|
||||
className="dropdown-menu dropdown-menu-end shadow-sm "
|
||||
style={{ minWidth: "220px" }}
|
||||
>
|
||||
<ul className="dropdown-menu dropdown-menu-end shadow-sm " style={{ minWidth: "220px" }}>
|
||||
|
||||
<li className="dropdown-item d-flex align-items-center justify-content-between">
|
||||
<div className="form-check form-switch mb-0">
|
||||
<input
|
||||
@ -363,53 +351,39 @@ const EmployeeList = () => {
|
||||
role="switch"
|
||||
id="inactiveEmployeesCheckboxMenu"
|
||||
checked={showInactive}
|
||||
onChange={(e) =>
|
||||
setShowInactive(e.target.checked)
|
||||
}
|
||||
onChange={(e) => setShowInactive(e.target.checked)}
|
||||
/>
|
||||
</div>
|
||||
<span className="ms-0">Show Inactive Employees</span>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<hr className="dropdown-divider" />
|
||||
</li>
|
||||
|
||||
<li><hr className="dropdown-divider" /></li>
|
||||
|
||||
<li>
|
||||
<button
|
||||
className="dropdown-item"
|
||||
onClick={() => handleExport("print")}
|
||||
>
|
||||
<button className="dropdown-item" onClick={() => handleExport("print")}>
|
||||
<i className="bx bx-printer me-2"></i> Print
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
className="dropdown-item"
|
||||
onClick={() => handleExport("csv")}
|
||||
>
|
||||
<button className="dropdown-item" onClick={() => handleExport("csv")}>
|
||||
<i className="bx bx-file me-2"></i> CSV
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
className="dropdown-item"
|
||||
onClick={() => handleExport("excel")}
|
||||
>
|
||||
<button className="dropdown-item" onClick={() => handleExport("excel")}>
|
||||
<i className="bx bxs-file-export me-2"></i> Excel
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
className="dropdown-item"
|
||||
onClick={() => handleExport("pdf")}
|
||||
>
|
||||
<button className="dropdown-item" onClick={() => handleExport("pdf")}>
|
||||
<i className="bx bxs-file-pdf me-2"></i> PDF
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<table
|
||||
@ -486,9 +460,8 @@ const EmployeeList = () => {
|
||||
Status
|
||||
</th>
|
||||
<th
|
||||
className={`sorting_disabled ${
|
||||
!Manage_Employee && "d-none"
|
||||
}`}
|
||||
className={`sorting_disabled ${!Manage_Employee && "d-none"
|
||||
}`}
|
||||
rowSpan="1"
|
||||
colSpan="1"
|
||||
style={{ width: "50px" }}
|
||||
@ -502,25 +475,29 @@ const EmployeeList = () => {
|
||||
{loading && (
|
||||
<tr>
|
||||
<td colSpan={8} className="text-center py-5">
|
||||
<div
|
||||
className="d-flex justify-content-center align-items-center py-5"
|
||||
style={{ minHeight: "50vh" }}
|
||||
>
|
||||
<div className="d-flex justify-content-center align-items-center py-5" style={{ minHeight: "50vh" }}>
|
||||
<SpinnerLoader />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
|
||||
{!loading && displayData?.length === 0 && !searchText ? (
|
||||
|
||||
{!loading &&
|
||||
displayData?.length === 0 &&
|
||||
(!searchText) ? (
|
||||
<tr>
|
||||
<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>
|
||||
</tr>
|
||||
) : null}
|
||||
|
||||
{!loading && displayData?.length === 0 && searchText ? (
|
||||
{!loading &&
|
||||
displayData?.length === 0 &&
|
||||
(searchText) ? (
|
||||
<tr>
|
||||
<td colSpan={8} className="border-0 py-3">
|
||||
<div className="py-4">
|
||||
@ -549,9 +526,8 @@ const EmployeeList = () => {
|
||||
className="text-heading text-truncate cursor-pointer"
|
||||
>
|
||||
<span className="fw-normal">
|
||||
{capitalizeFirstLetter(item.firstName)}{" "}
|
||||
{capitalizeFirstLetter(item.middleName)}{" "}
|
||||
{capitalizeFirstLetter(item.lastName)}
|
||||
{item.firstName} {item.middleName}{" "}
|
||||
{item.lastName}
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
@ -564,9 +540,7 @@ const EmployeeList = () => {
|
||||
{item.email}
|
||||
</span>
|
||||
) : (
|
||||
<small className="d-block text-start text-muted small">
|
||||
-
|
||||
</small>
|
||||
<span className="d-block text-start text-muted fst-italic">NA</span>
|
||||
)}
|
||||
</td>
|
||||
|
||||
@ -587,9 +561,7 @@ const EmployeeList = () => {
|
||||
{item.joiningDate ? (
|
||||
moment(item.joiningDate).format("DD-MMM-YYYY")
|
||||
) : (
|
||||
<span className="d-block text-center text-muted fst-italic">
|
||||
NA
|
||||
</span>
|
||||
<span className="d-block text-center text-muted fst-italic">NA</span>
|
||||
)}
|
||||
</td>
|
||||
|
||||
|
||||
@ -1,10 +1,4 @@
|
||||
import React, {
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import React, { createContext, useContext, useEffect, useRef, useState } from "react";
|
||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||
import {
|
||||
ITEMS_PER_PAGE,
|
||||
@ -74,7 +68,7 @@ const ProjectPage = () => {
|
||||
|
||||
const handleToggleProject = (value) => {
|
||||
setCoreProjects(value);
|
||||
sessionStorage.setItem("whichProjectDisplay", value ? "true" : "false");
|
||||
sessionStorage.setItem("whichProjectDisplay", String(value));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@ -106,9 +100,8 @@ const ProjectPage = () => {
|
||||
{/* Service Project Button */}
|
||||
<button
|
||||
type="button"
|
||||
className={`btn px-2 py-1 rounded-0 text-tiny ${
|
||||
!coreProjects ? "btn-primary text-white" : ""
|
||||
}`}
|
||||
className={`btn px-2 py-1 rounded-0 text-tiny ${!coreProjects ? "btn-primary text-white" : ""
|
||||
}`}
|
||||
onClick={() => handleToggleProject(false)}
|
||||
>
|
||||
Service Project
|
||||
@ -116,9 +109,8 @@ const ProjectPage = () => {
|
||||
{/* Organization Project Button */}
|
||||
<button
|
||||
type="button"
|
||||
className={`btn px-2 py-1 rounded-0 text-tiny ${
|
||||
coreProjects ? "btn-primary text-white" : ""
|
||||
}`}
|
||||
className={`btn px-2 py-1 rounded-0 text-tiny ${coreProjects ? "btn-primary text-white" : ""
|
||||
}`}
|
||||
onClick={() => handleToggleProject(true)}
|
||||
>
|
||||
Infra Project
|
||||
@ -146,9 +138,8 @@ const ProjectPage = () => {
|
||||
<div className="d-flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-sm p-1 ${
|
||||
!listView ? "btn-primary" : "btn-outline-primary"
|
||||
}`}
|
||||
className={`btn btn-sm p-1 ${!listView ? "btn-primary" : "btn-outline-primary"
|
||||
}`}
|
||||
onClick={() => setListView(false)}
|
||||
title="Card View"
|
||||
>
|
||||
@ -157,9 +148,8 @@ const ProjectPage = () => {
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-sm p-1 ${
|
||||
listView ? "btn-primary" : "btn-outline-primary"
|
||||
}`}
|
||||
className={`btn btn-sm p-1 ${listView ? "btn-primary" : "btn-outline-primary"
|
||||
}`}
|
||||
onClick={() => setListView(true)}
|
||||
title="List View"
|
||||
>
|
||||
@ -174,16 +164,12 @@ const ProjectPage = () => {
|
||||
onClick={() => setOpen(!open)}
|
||||
>
|
||||
<i
|
||||
className={`bx bx-slider-alt fs-5 ${
|
||||
selectedStatuses.length !== PROJECT_STATUS.length
|
||||
? "text-primary"
|
||||
: ""
|
||||
}`}
|
||||
className={`bx bx-slider-alt fs-5 ${selectedStatuses.length !== PROJECT_STATUS.length ? "text-primary" : ""
|
||||
}`}
|
||||
></i>
|
||||
|
||||
{selectedStatuses.length !== PROJECT_STATUS.length && (
|
||||
<span
|
||||
className="badge bg-warning text-white rounded-pill position-absolute"
|
||||
<span className="badge bg-warning text-white rounded-pill position-absolute"
|
||||
style={{
|
||||
top: "-4px",
|
||||
right: "-4px",
|
||||
@ -200,7 +186,7 @@ const ProjectPage = () => {
|
||||
<ul
|
||||
ref={dropdownRef}
|
||||
className="dropdown-menu show p-2 text-capitalize"
|
||||
onMouseDown={(e) => e.stopPropagation()} // IMPORTANT
|
||||
onMouseDown={(e) => e.stopPropagation()} // IMPORTANT
|
||||
>
|
||||
{PROJECT_STATUS.map(({ id, label }) => (
|
||||
<li key={id}>
|
||||
@ -214,7 +200,7 @@ const ProjectPage = () => {
|
||||
/>
|
||||
<label
|
||||
className="form-check-label"
|
||||
onClick={(e) => e.stopPropagation()} // OPTIONAL
|
||||
onClick={(e) => e.stopPropagation()} // OPTIONAL
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
@ -225,7 +211,10 @@ const ProjectPage = () => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{(HasManageProject || !coreProjects) && (
|
||||
|
||||
|
||||
|
||||
{HasManageProject && (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary btn-sm d-flex align-items-center my-2"
|
||||
@ -234,9 +223,9 @@ const ProjectPage = () => {
|
||||
coreProjects
|
||||
? setMangeProject({ isOpen: true, Project: null }) // Organization Project → Infra
|
||||
: setManageServiceProject({
|
||||
isOpen: true,
|
||||
Project: null,
|
||||
}) // Service Project
|
||||
isOpen: true,
|
||||
Project: null,
|
||||
}) // Service Project
|
||||
}
|
||||
>
|
||||
<i className="bx bx-plus-circle me-2"></i>
|
||||
|
||||
@ -29,11 +29,25 @@ const ProjectsDisplay = ({
|
||||
const [projectList, setProjectList] = useState([]);
|
||||
const debouncedSearch = useDebounce(searchTerm, 500);
|
||||
const { data, isLoading, isError, error } = useProjects(
|
||||
9,
|
||||
ITEMS_PER_PAGE,
|
||||
currentPage,
|
||||
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) => {
|
||||
if (page >= 1 && page <= (data?.totalPages ?? 1)) {
|
||||
setCurrentPage(page);
|
||||
@ -54,7 +68,7 @@ const ProjectsDisplay = ({
|
||||
.filter((statusId) => grouped[statusId])
|
||||
.flatMap((statusId) =>
|
||||
grouped[statusId].sort((a, b) =>
|
||||
a?.shortName?.toLowerCase()?.localeCompare(b?.shortName?.toLowerCase())
|
||||
a?.name?.toLowerCase()?.localeCompare(b?.name?.toLowerCase())
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
@ -1,69 +1,8 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React from "react";
|
||||
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 [selectedProject, setSelectedProject] = useState();
|
||||
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>
|
||||
);
|
||||
return <ComingSoonPage></ComingSoonPage>;
|
||||
};
|
||||
|
||||
export default Reports;
|
||||
|
||||
@ -1,11 +1,7 @@
|
||||
import { api } from "../utils/axiosClient";
|
||||
|
||||
const GlobalRepository = {
|
||||
getDashboardProgressionData: ({
|
||||
days = "",
|
||||
FromDate = "",
|
||||
projectId = "",
|
||||
}) => {
|
||||
getDashboardProgressionData: ({ days = '', FromDate = '', projectId = '' }) => {
|
||||
let params;
|
||||
if (projectId == null) {
|
||||
params = new URLSearchParams({
|
||||
@ -22,13 +18,12 @@ const GlobalRepository = {
|
||||
|
||||
return api.get(`/api/Dashboard/Progression?${params.toString()}`);
|
||||
},
|
||||
getProjectCompletionStatus: () =>
|
||||
api.get(`/api/Dashboard/project-completion-status`),
|
||||
getProjectCompletionStatus:()=>api.get(`/api/Dashboard/project-completion-status`),
|
||||
|
||||
|
||||
getDashboardAttendanceData: (date, projectId) => {
|
||||
return api.get(
|
||||
`/api/Dashboard/project-attendance/${projectId}?date=${date}`
|
||||
);
|
||||
|
||||
return api.get(`/api/Dashboard/project-attendance/${projectId}?date=${date}`);
|
||||
},
|
||||
getDashboardProjectsCardData: () => {
|
||||
return api.get(`/api/Dashboard/projects`);
|
||||
@ -49,7 +44,7 @@ const GlobalRepository = {
|
||||
},
|
||||
|
||||
getExpenseData: (projectId, startDate, endDate) => {
|
||||
let url = `api/Dashboard/expense/type`;
|
||||
let url = `api/Dashboard/expense/type`
|
||||
const queryParams = [];
|
||||
if (projectId) {
|
||||
queryParams.push(`projectId=${projectId}`);
|
||||
@ -67,15 +62,10 @@ const GlobalRepository = {
|
||||
return api.get(url);
|
||||
},
|
||||
|
||||
getExpenseStatus: (projectId) =>
|
||||
api.get(
|
||||
`/api/Dashboard/expense/pendings${
|
||||
projectId ? `?projectId=${projectId}` : ""
|
||||
}`
|
||||
),
|
||||
getExpenseStatus: (projectId) => api.get(`/api/Dashboard/expense/pendings${projectId ? `?projectId=${projectId}` : ""}`),
|
||||
|
||||
getExpenseDataByProject: (projectId, categoryId, months) => {
|
||||
let url = `api/Dashboard/expense/monthly`;
|
||||
let url = `api/Dashboard/expense/monthly`
|
||||
const queryParams = [];
|
||||
if (projectId) {
|
||||
queryParams.push(`projectId=${projectId}`);
|
||||
@ -92,26 +82,13 @@ const GlobalRepository = {
|
||||
return api.get(url);
|
||||
},
|
||||
|
||||
getAttendanceOverview: (projectId, days) =>
|
||||
api.get(`/api/dashboard/attendance-overview/${projectId}?days=${days}`),
|
||||
getAttendanceOverview: (projectId, days) => api.get(`/api/dashboard/attendance-overview/${projectId}?days=${days}`),
|
||||
|
||||
getCollectionOverview: (projectId) =>
|
||||
api.get(`/api/Dashboard/collection-overview`),
|
||||
getCollectionOverview:(projectId) =>api.get(`/api/Dashboard/collection-overview`),
|
||||
|
||||
getJobsProgression: (projectId) =>
|
||||
api.get(
|
||||
`/api/Dashboard/job/progression${
|
||||
projectId ? `?projectId=${projectId}` : ""
|
||||
}`
|
||||
),
|
||||
getJobsProgression: (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 const ReportsRepository = {
|
||||
getDailyProgressByProject: (projectId,date) =>
|
||||
api.get(`/api/Market/get/project/report/${projectId}?date=${date}`),
|
||||
};
|
||||
|
||||
@ -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;
|
||||
}
|
||||
};
|
||||
@ -24,11 +24,6 @@ export const AppColorconfig = {
|
||||
borderColor: "#eceef1",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
export const AppColors = [
|
||||
"primary","secodary","warning","info","danger"
|
||||
]
|
||||
export const getColorNameFromHex = (hex) => {
|
||||
const normalizedHex = hex?.replace(/'/g, "").toLowerCase();
|
||||
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);
|
||||
}
|
||||
|
||||
@ -4,7 +4,6 @@ import axiosRetry from "axios-retry";
|
||||
import showToast from "../services/toastService";
|
||||
import { startSignalR, stopSignalR } from "../services/signalRService";
|
||||
import { BASE_URL } from "./constants";
|
||||
import { decryptResponse } from "../services/encryption";
|
||||
const base_Url = BASE_URL;
|
||||
|
||||
export const axiosClient = axios.create({
|
||||
@ -135,7 +134,7 @@ const apiRequest = async (method, url, data = {}, config = {}) => {
|
||||
params: method === "get" ? data : undefined,
|
||||
...config,
|
||||
});
|
||||
return decryptResponse(response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
@ -180,10 +179,11 @@ export const api = {
|
||||
authRequired: true,
|
||||
}),
|
||||
patch: (url, data = {}, customHeaders = {}) =>
|
||||
apiRequest("patch", url, data, {
|
||||
headers: { ...customHeaders },
|
||||
authRequired: true,
|
||||
}),
|
||||
apiRequest("patch", url, data, {
|
||||
headers: { ...customHeaders },
|
||||
authRequired: true,
|
||||
}),
|
||||
|
||||
};
|
||||
|
||||
// Redirect helper
|
||||
|
||||