Merge branch 'Purchase_Invoice_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Weidget_Dashboard_Services
This commit is contained in:
commit
8020258a71
345
src/components/Dashboard/CollectionOverview.jsx
Normal file
345
src/components/Dashboard/CollectionOverview.jsx
Normal file
@ -0,0 +1,345 @@
|
|||||||
|
import React from "react";
|
||||||
|
import Chart from "react-apexcharts";
|
||||||
|
import { useGetCollectionOverview } from "../../hooks/useDashboard_Data";
|
||||||
|
import { formatFigure } from "../../utils/appUtils";
|
||||||
|
|
||||||
|
const CollectionOverview = ({ data, isLoading }) => {
|
||||||
|
const borderColor = "#ddd";
|
||||||
|
const labelColor = "#6c757d";
|
||||||
|
|
||||||
|
// Extract bucket values
|
||||||
|
const labels = ["0–30 Days", "30–60 Days", "60–90 Days", "90+ Days"];
|
||||||
|
|
||||||
|
const amounts = [
|
||||||
|
data.bucket0To30Amount,
|
||||||
|
data.bucket30To60Amount,
|
||||||
|
data.bucket60To90Amount,
|
||||||
|
data.bucket90PlusAmount,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Colors (Zoho-style distributed)
|
||||||
|
const colors = ["#7367F0", "#00cfe8", "#28c76f", "#ea5455"];
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
chart: {
|
||||||
|
type: "bar",
|
||||||
|
height: 260,
|
||||||
|
toolbar: { show: false },
|
||||||
|
},
|
||||||
|
|
||||||
|
plotOptions: {
|
||||||
|
bar: {
|
||||||
|
horizontal: true,
|
||||||
|
barHeight: "65%",
|
||||||
|
distributed: true,
|
||||||
|
borderRadius: 8,
|
||||||
|
startingShape: "rounded",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
colors: colors,
|
||||||
|
|
||||||
|
grid: {
|
||||||
|
borderColor: borderColor,
|
||||||
|
strokeDashArray: 6,
|
||||||
|
padding: { top: -10, bottom: -10 },
|
||||||
|
xaxis: { lines: { show: true } },
|
||||||
|
},
|
||||||
|
|
||||||
|
dataLabels: {
|
||||||
|
enabled: true,
|
||||||
|
formatter: (_, opts) => labels[opts.dataPointIndex],
|
||||||
|
style: {
|
||||||
|
colors: ["#fff"],
|
||||||
|
fontSize: "13px",
|
||||||
|
fontWeight: 500,
|
||||||
|
},
|
||||||
|
offsetX: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
xaxis: {
|
||||||
|
categories: amounts.map((a) => a),
|
||||||
|
labels: {
|
||||||
|
style: { colors: labelColor, fontSize: "12px" },
|
||||||
|
formatter: (val) => `₹${val.toLocaleString()}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
yaxis: {
|
||||||
|
labels: {
|
||||||
|
style: {
|
||||||
|
colors: labelColor,
|
||||||
|
fontSize: "13px",
|
||||||
|
},
|
||||||
|
formatter: () => "", // hide duplicate labels
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
tooltip: {
|
||||||
|
custom: ({ series, seriesIndex, dataPointIndex }) => {
|
||||||
|
return `
|
||||||
|
<div class="px-2 py-1">
|
||||||
|
<strong>${labels[dataPointIndex]}</strong><br>
|
||||||
|
₹${series[seriesIndex][dataPointIndex].toLocaleString()}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
legend: { show: false },
|
||||||
|
};
|
||||||
|
|
||||||
|
const series = [
|
||||||
|
{
|
||||||
|
name: "Amount",
|
||||||
|
data: amounts,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Chart options={options} series={series} type="bar" height={260} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CollectionOverview;
|
||||||
|
export const TopicBarChart = ({ data,isLoading }) => {
|
||||||
|
const data1 = {
|
||||||
|
totalDueAmount: 213590,
|
||||||
|
totalCollectedAmount: 5000,
|
||||||
|
totalValue: 218590,
|
||||||
|
pendingPercentage: 97.71,
|
||||||
|
collectedPercentage: 2.29,
|
||||||
|
|
||||||
|
bucket0To30Invoices: 10,
|
||||||
|
bucket30To60Invoices: 4,
|
||||||
|
bucket60To90Invoices: 2,
|
||||||
|
bucket90PlusInvoices: 1,
|
||||||
|
|
||||||
|
bucket0To30Amount: 2130,
|
||||||
|
bucket30To60Amount: 2003,
|
||||||
|
bucket60To90Amount: 4500,
|
||||||
|
bucket90PlusAmount: 8800,
|
||||||
|
|
||||||
|
topClientBalance: 55300,
|
||||||
|
topClient: {
|
||||||
|
id: "4e3a6d31-c640-40f7-8d67-6c109fcdb9ea",
|
||||||
|
name: "Marco Secure Solutions Ltd.",
|
||||||
|
email: "admin@marcoaiot.com",
|
||||||
|
contactPerson: "Admin",
|
||||||
|
address:
|
||||||
|
"2nd Floor, Fullora Building, Tejas CHS, behind Kothrud Stand, Tejas Society, Dahanukar Colony, Kothrud, Pune, Maharashtra 411038",
|
||||||
|
gstNumber: null,
|
||||||
|
contactNumber: "123456789",
|
||||||
|
sprid: 5400,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const borderColor = "#ddd";
|
||||||
|
const labelColor = "#6c757d";
|
||||||
|
|
||||||
|
// COLORS
|
||||||
|
const config = {
|
||||||
|
colors: {
|
||||||
|
b0: "#7367F0",
|
||||||
|
b30: "#00cfe8",
|
||||||
|
b60: "#28c76f",
|
||||||
|
b90: "#ea5455",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// NEW LABELS (BUCKETS)
|
||||||
|
const chartLabels = ["0–30 Days", "30–60 Days", "60–90 Days", "90+ Days"];
|
||||||
|
|
||||||
|
// NEW VALUES (BUCKET AMOUNT)
|
||||||
|
const chartValues = [
|
||||||
|
data.bucket0To30Amount,
|
||||||
|
data.bucket30To60Amount,
|
||||||
|
data.bucket60To90Amount,
|
||||||
|
data.bucket90PlusAmount,
|
||||||
|
];
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
chart: {
|
||||||
|
height: 300,
|
||||||
|
type: "bar",
|
||||||
|
toolbar: { show: false },
|
||||||
|
},
|
||||||
|
|
||||||
|
plotOptions: {
|
||||||
|
bar: {
|
||||||
|
horizontal: true,
|
||||||
|
barHeight: "40%",
|
||||||
|
distributed: true,
|
||||||
|
startingShape: "rounded",
|
||||||
|
borderRadius: 7,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
grid: {
|
||||||
|
strokeDashArray: 10,
|
||||||
|
borderColor,
|
||||||
|
xaxis: { lines: { show: true } },
|
||||||
|
yaxis: { lines: { show: false } },
|
||||||
|
padding: { top: -35, bottom: -12 },
|
||||||
|
},
|
||||||
|
|
||||||
|
colors: [
|
||||||
|
config.colors.b0,
|
||||||
|
config.colors.b30,
|
||||||
|
config.colors.b60,
|
||||||
|
config.colors.b90,
|
||||||
|
],
|
||||||
|
|
||||||
|
labels: chartLabels,
|
||||||
|
|
||||||
|
fill: { opacity: 1 },
|
||||||
|
|
||||||
|
dataLabels: {
|
||||||
|
enabled: true,
|
||||||
|
style: {
|
||||||
|
colors: ["#fff"],
|
||||||
|
fontWeight: 400,
|
||||||
|
fontSize: "13px",
|
||||||
|
fontFamily: "Public Sans",
|
||||||
|
},
|
||||||
|
formatter: (_, opts) => chartLabels[opts.dataPointIndex],
|
||||||
|
},
|
||||||
|
|
||||||
|
xaxis: {
|
||||||
|
categories: chartValues.map((x) => formatFigure(x, { type: "currency" })),
|
||||||
|
axisBorder: { show: false },
|
||||||
|
axisTicks: { show: false },
|
||||||
|
labels: {
|
||||||
|
style: {
|
||||||
|
colors: labelColor,
|
||||||
|
fontFamily: "Public Sans",
|
||||||
|
fontSize: "13px",
|
||||||
|
},
|
||||||
|
formatter: (val) => `₹${Number(val).toLocaleString()}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
yaxis: {
|
||||||
|
labels: {
|
||||||
|
style: {
|
||||||
|
colors: labelColor,
|
||||||
|
fontFamily: "Public Sans",
|
||||||
|
fontSize: "13px",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
tooltip: {
|
||||||
|
enabled: true,
|
||||||
|
custom: ({ series, seriesIndex, dataPointIndex }) => {
|
||||||
|
return `
|
||||||
|
<div class="px-3 py-2">
|
||||||
|
<span>₹${series[seriesIndex][
|
||||||
|
dataPointIndex
|
||||||
|
].toLocaleString()}</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
legend: { show: false },
|
||||||
|
};
|
||||||
|
|
||||||
|
const series = [
|
||||||
|
{
|
||||||
|
data: chartValues,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="row p-2">
|
||||||
|
<div className="col-md-8">
|
||||||
|
<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>
|
||||||
|
<span className="ms-2 fs-5">
|
||||||
|
{formatFigure(data.totalDueAmount, { type: "currency" })}
|
||||||
|
</span>
|
||||||
|
<p className="text-secondary fs-6 m-0 ms-1">Collected Amount</p>
|
||||||
|
<span className="ms-2 fs-5">
|
||||||
|
{formatFigure(data.totalCollectedAmount, { type: "currency" })}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Chart options={options} series={series} type="bar" height={300} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-md-4 d-flex flex-column gap-2">
|
||||||
|
<div class="card-header d-flex align-items-end justify-content-between"></div>
|
||||||
|
<div className="p-1 m-1 text-start">
|
||||||
|
{/* <p className="fs-5 fs-5">Top Client</p>
|
||||||
|
<small> {data.topClient.name}</small> */}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 0–30 Days */}
|
||||||
|
<div
|
||||||
|
className="p-1 rounded-3 text-start mx-1"
|
||||||
|
style={{
|
||||||
|
background: "var(--bs-primary-bg-subtle)",
|
||||||
|
borderLeft: "4px solid var(--bs-primary)",
|
||||||
|
minWidth: "170px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h5 className="fw-bold mb-0">
|
||||||
|
{formatFigure(data.bucket0To30Amount, { type: "currency" })}
|
||||||
|
</h5>
|
||||||
|
<p className="text-secondary mb-0 small">0–30 Days</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 30–60 Days */}
|
||||||
|
<div
|
||||||
|
className="p-1 rounded-3 text-start mx-1"
|
||||||
|
style={{
|
||||||
|
background: "var(--bs-info-bg-subtle)",
|
||||||
|
borderLeft: "4px solid var(--bs-info)",
|
||||||
|
minWidth: "170px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h5 className="fw-bold mb-0">
|
||||||
|
{formatFigure(data.bucket30To60Amount, { type: "currency" })}
|
||||||
|
</h5>
|
||||||
|
<p className="text-secondary mb-0 small">30–60 Days</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 60–90 Days */}
|
||||||
|
<div
|
||||||
|
className="p-1 rounded-3 text-start mx-1"
|
||||||
|
style={{
|
||||||
|
background: "var(--bs-warning-bg-subtle)",
|
||||||
|
borderLeft: "4px solid var(--bs-warning)",
|
||||||
|
minWidth: "170px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h5 className="fw-bold mb-0">
|
||||||
|
{formatFigure(data.bucket60To90Amount, { type: "currency" })}
|
||||||
|
</h5>
|
||||||
|
<p className="text-secondary mb-0 small">60–90 Days</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 90+ Days */}
|
||||||
|
<div
|
||||||
|
className="p-1 rounded-3 text-start mx-1"
|
||||||
|
style={{
|
||||||
|
background: "var(--bs-danger-bg-subtle)",
|
||||||
|
borderLeft: "4px solid var(--bs-danger)",
|
||||||
|
minWidth: "170px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h5 className="fw-bold mb-0">
|
||||||
|
{formatFigure(data.bucket90PlusAmount, { type: "currency" })}
|
||||||
|
</h5>
|
||||||
|
<p className="text-secondary mb-0 small">90+ Days</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
40
src/components/Dashboard/CollectionOverviewSkeleton.jsx
Normal file
40
src/components/Dashboard/CollectionOverviewSkeleton.jsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
|
||||||
|
const SkeletonLine = ({ height = 20, width = "100%", className = "" }) => (
|
||||||
|
<div
|
||||||
|
className={`skeleton mb-2 ${className}`}
|
||||||
|
style={{ height, width }}
|
||||||
|
></div>
|
||||||
|
);
|
||||||
|
export const CollectionOverviewSkeleton = () => {
|
||||||
|
return (
|
||||||
|
<div className="card row p-1">
|
||||||
|
|
||||||
|
{/* LEFT SIDE */}
|
||||||
|
<div className="col-8">
|
||||||
|
<div className="">
|
||||||
|
|
||||||
|
{/* Header */}
|
||||||
|
<div className="d-flex align-items-center justify-content-between mb-3">
|
||||||
|
<SkeletonLine height={24} width="180px" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Due & Collected summary */}
|
||||||
|
<div className="d-flex align-items-center text-start px-6 mb-3">
|
||||||
|
<SkeletonLine height={16} width="100px" className="me-2" />
|
||||||
|
<SkeletonLine height={20} width="120px" className="me-2" />
|
||||||
|
<SkeletonLine height={16} width="120px" className="ms-2 me-2" />
|
||||||
|
<SkeletonLine height={20} width="120px" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Chart Skeleton */}
|
||||||
|
<SkeletonLine height={250} width="100%" className="mt-2" />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -4,7 +4,8 @@ import {
|
|||||||
useDashboardProjectsCardData,
|
useDashboardProjectsCardData,
|
||||||
useDashboardTeamsCardData,
|
useDashboardTeamsCardData,
|
||||||
useDashboardTasksCardData,
|
useDashboardTasksCardData,
|
||||||
useAttendanceOverviewData
|
useAttendanceOverviewData,
|
||||||
|
useGetCollectionOverview,
|
||||||
} from "../../hooks/useDashboard_Data";
|
} from "../../hooks/useDashboard_Data";
|
||||||
|
|
||||||
import Projects from "./Projects";
|
import Projects from "./Projects";
|
||||||
@ -19,10 +20,15 @@ import ExpenseByProject from "./ExpenseByProject";
|
|||||||
import ProjectStatistics from "../Project/ProjectStatistics";
|
import ProjectStatistics from "../Project/ProjectStatistics";
|
||||||
import ServiceJobs from "./ServiceJobs";
|
import ServiceJobs from "./ServiceJobs";
|
||||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
import { REGULARIZE_ATTENDANCE, SELF_ATTENDANCE, TEAM_ATTENDANCE } from "../../utils/constants";
|
import {
|
||||||
|
REGULARIZE_ATTENDANCE,
|
||||||
|
SELF_ATTENDANCE,
|
||||||
|
TEAM_ATTENDANCE,
|
||||||
|
} from "../../utils/constants";
|
||||||
|
import CollectionOverview, { TopicBarChart } from "./CollectionOverview";
|
||||||
|
import { CollectionOverviewSkeleton } from "./CollectionOverviewSkeleton";
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
|
|
||||||
// Get the selected project ID from Redux store
|
// Get the selected project ID from Redux store
|
||||||
const projectId = useSelector((store) => store.localVariables.projectId);
|
const projectId = useSelector((store) => store.localVariables.projectId);
|
||||||
const isAllProjectsSelected = projectId === null;
|
const isAllProjectsSelected = projectId === null;
|
||||||
@ -30,7 +36,8 @@ const Dashboard = () => {
|
|||||||
const canTeamAttendance = useHasUserPermission(TEAM_ATTENDANCE);
|
const canTeamAttendance = useHasUserPermission(TEAM_ATTENDANCE);
|
||||||
const canSelfAttendance = useHasUserPermission(SELF_ATTENDANCE);
|
const canSelfAttendance = useHasUserPermission(SELF_ATTENDANCE);
|
||||||
|
|
||||||
|
const { data, isLoading, isError } = useGetCollectionOverview();
|
||||||
|
console.log("data-->", data);
|
||||||
return (
|
return (
|
||||||
<div className="container-fluid mt-5">
|
<div className="container-fluid mt-5">
|
||||||
<div className="row gy-4">
|
<div className="row gy-4">
|
||||||
@ -40,11 +47,17 @@ const Dashboard = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className={`${!isAllProjectsSelected ? "col-sm-6 col-lg-6" : "col-sm-6 col-lg-4"}`}>
|
<div
|
||||||
|
className={`${!isAllProjectsSelected ? "col-sm-6 col-lg-6" : "col-sm-6 col-lg-4"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<Teams />
|
<Teams />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={`${!isAllProjectsSelected ? "col-sm-6 col-lg-6" : "col-sm-6 col-lg-4"}`}>
|
<div
|
||||||
|
className={`${!isAllProjectsSelected ? "col-sm-6 col-lg-6" : "col-sm-6 col-lg-4"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<TasksCard />
|
<TasksCard />
|
||||||
</div>
|
</div>
|
||||||
<div className="col-12 col-xl-4 col-md-6">
|
<div className="col-12 col-xl-4 col-md-6">
|
||||||
@ -70,7 +83,8 @@ const Dashboard = () => {
|
|||||||
<div className="col-xxl-6 col-lg-6">
|
<div className="col-xxl-6 col-lg-6">
|
||||||
<ProjectProgressChart />
|
<ProjectProgressChart />
|
||||||
</div>
|
</div>
|
||||||
{!isAllProjectsSelected && (canRegularize || canTeamAttendance || canSelfAttendance) && (
|
{!isAllProjectsSelected &&
|
||||||
|
(canRegularize || canTeamAttendance || canSelfAttendance) && (
|
||||||
<div className="col-12 col-md-6 mb-sm-0 mb-4">
|
<div className="col-12 col-md-6 mb-sm-0 mb-4">
|
||||||
<AttendanceOverview />
|
<AttendanceOverview />
|
||||||
</div>
|
</div>
|
||||||
@ -88,9 +102,97 @@ const Dashboard = () => {
|
|||||||
<ServiceJobs />
|
<ServiceJobs />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<div className="col-md-8">
|
||||||
|
{isLoading ? (
|
||||||
|
<CollectionOverviewSkeleton />
|
||||||
|
) : (
|
||||||
|
data && (
|
||||||
|
<div className="card">
|
||||||
|
<TopicBarChart data={data} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Dashboard;
|
export default Dashboard;
|
||||||
|
|
||||||
|
// <div class="col-12 col-xl-8">
|
||||||
|
// <div class="card h-100">
|
||||||
|
// <div class="card-header d-flex align-items-center justify-content-between">
|
||||||
|
// <h5 class="card-title m-0 me-2">Topic you are interested in</h5>
|
||||||
|
// <div class="dropdown">
|
||||||
|
// <button
|
||||||
|
// class="btn p-0"
|
||||||
|
// type="button"
|
||||||
|
// id="topic"
|
||||||
|
// data-bs-toggle="dropdown"
|
||||||
|
// aria-haspopup="true"
|
||||||
|
// aria-expanded="false">
|
||||||
|
// <i class="bx bx-dots-vertical-rounded bx-lg text-muted"></i>
|
||||||
|
// </button>
|
||||||
|
// <div class="dropdown-menu dropdown-menu-end" aria-labelledby="topic">
|
||||||
|
// <a class="dropdown-item" href="javascript:void(0);">Highest Views</a>
|
||||||
|
// <a class="dropdown-item" href="javascript:void(0);">See All</a>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// <div class="card-body row g-3">
|
||||||
|
// <div class="col-md-8">
|
||||||
|
// <div id="horizontalBarChart"></div>
|
||||||
|
// </div>
|
||||||
|
// <div class="col-md-4 d-flex justify-content-around align-items-center">
|
||||||
|
// <div>
|
||||||
|
// <div class="d-flex align-items-baseline">
|
||||||
|
// <span class="text-primary me-2"><i class="bx bxs-circle bx-12px"></i></span>
|
||||||
|
// <div>
|
||||||
|
// <p class="mb-0">UI Design</p>
|
||||||
|
// <h5>35%</h5>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// <div class="d-flex align-items-baseline my-12">
|
||||||
|
// <span class="text-success me-2"><i class="bx bxs-circle bx-12px"></i></span>
|
||||||
|
// <div>
|
||||||
|
// <p class="mb-0">Music</p>
|
||||||
|
// <h5>14%</h5>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// <div class="d-flex align-items-baseline">
|
||||||
|
// <span class="text-danger me-2"><i class="bx bxs-circle bx-12px"></i></span>
|
||||||
|
// <div>
|
||||||
|
// <p class="mb-0">React</p>
|
||||||
|
// <h5>10%</h5>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
|
||||||
|
// <div>
|
||||||
|
// <div class="d-flex align-items-baseline">
|
||||||
|
// <span class="text-info me-2"><i class="bx bxs-circle bx-12px"></i></span>
|
||||||
|
// <div>
|
||||||
|
// <p class="mb-0">UX Design</p>
|
||||||
|
// <h5>20%</h5>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// <div class="d-flex align-items-baseline my-12">
|
||||||
|
// <span class="text-secondary me-2"><i class="bx bxs-circle bx-12px"></i></span>
|
||||||
|
// <div>
|
||||||
|
// <p class="mb-0">Animation</p>
|
||||||
|
// <h5>12%</h5>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// <div class="d-flex align-items-baseline">
|
||||||
|
// <span class="text-warning me-2"><i class="bx bxs-circle bx-12px"></i></span>
|
||||||
|
// <div>
|
||||||
|
// <p class="mb-0">SEO</p>
|
||||||
|
// <h5>9%</h5>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { error } from "pdf-lib";
|
||||||
import { useState, useRef, useEffect } from "react";
|
import { useState, useRef, useEffect } from "react";
|
||||||
|
|
||||||
const PreviewDocument = ({ files = [] }) => {
|
const PreviewDocument = ({ files = [] }) => {
|
||||||
@ -15,9 +16,11 @@ const PreviewDocument = ({ files = [] }) => {
|
|||||||
const MIN_ZOOM = 0.4;
|
const MIN_ZOOM = 0.4;
|
||||||
const MAX_ZOOM = 3;
|
const MAX_ZOOM = 3;
|
||||||
|
|
||||||
const currentImage = images[index];
|
const currentFile = images[index];
|
||||||
|
const fileUrl = currentFile?.preSignedUrl;
|
||||||
|
|
||||||
|
const isPDF = fileUrl?.toLowerCase().endsWith(".pdf");
|
||||||
|
|
||||||
// Reset on image change
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setRotation(0);
|
setRotation(0);
|
||||||
setScale(1);
|
setScale(1);
|
||||||
@ -25,8 +28,8 @@ const PreviewDocument = ({ files = [] }) => {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
}, [index]);
|
}, [index]);
|
||||||
|
|
||||||
const zoomIn = () => setScale((prev) => Math.min(prev + 0.2, MAX_ZOOM));
|
const zoomIn = () => !isPDF && setScale((prev) => Math.min(prev + 0.2, MAX_ZOOM));
|
||||||
const zoomOut = () => setScale((prev) => Math.max(prev - 0.2, MIN_ZOOM));
|
const zoomOut = () => !isPDF && setScale((prev) => Math.max(prev - 0.2, MIN_ZOOM));
|
||||||
|
|
||||||
const resetAll = () => {
|
const resetAll = () => {
|
||||||
setRotation(0);
|
setRotation(0);
|
||||||
@ -42,24 +45,8 @@ const PreviewDocument = ({ files = [] }) => {
|
|||||||
if (index > 0) setIndex((i) => i - 1);
|
if (index > 0) setIndex((i) => i - 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleWheel = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
if (e.ctrlKey) {
|
|
||||||
const delta = e.deltaY > 0 ? -0.1 : 0.1;
|
|
||||||
setScale((prev) => {
|
|
||||||
let next = prev + delta;
|
|
||||||
if (next < MIN_ZOOM) next = MIN_ZOOM;
|
|
||||||
if (next > MAX_ZOOM) next = MAX_ZOOM;
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (e.deltaY > 0) nextImage();
|
|
||||||
else prevImage();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseDown = (e) => {
|
const handleMouseDown = (e) => {
|
||||||
|
if (isPDF) return;
|
||||||
setDragging(true);
|
setDragging(true);
|
||||||
startPos.current = {
|
startPos.current = {
|
||||||
x: e.clientX - position.x,
|
x: e.clientX - position.x,
|
||||||
@ -68,7 +55,7 @@ const PreviewDocument = ({ files = [] }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleMouseMove = (e) => {
|
const handleMouseMove = (e) => {
|
||||||
if (!dragging) return;
|
if (!dragging || isPDF) return;
|
||||||
|
|
||||||
setPosition({
|
setPosition({
|
||||||
x: e.clientX - startPos.current.x,
|
x: e.clientX - startPos.current.x,
|
||||||
@ -78,14 +65,15 @@ const PreviewDocument = ({ files = [] }) => {
|
|||||||
|
|
||||||
const handleMouseUp = () => setDragging(false);
|
const handleMouseUp = () => setDragging(false);
|
||||||
|
|
||||||
const handleDoubleClick = () => resetAll();
|
const handleDoubleClick = () => !isPDF && resetAll();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Top Controls */}
|
{/* Controls */}
|
||||||
<div className="d-flex justify-content-start align-items-center mb-2">
|
<div className="d-flex justify-content-start align-items-center mb-2">
|
||||||
{/* Left */}
|
|
||||||
<div className="d-flex gap-3">
|
<div className="d-flex gap-3">
|
||||||
|
{!isPDF && (
|
||||||
|
<>
|
||||||
<i
|
<i
|
||||||
className="bx bx-rotate-right cursor-pointer fs-4"
|
className="bx bx-rotate-right cursor-pointer fs-4"
|
||||||
onClick={() => setRotation((prev) => prev + 90)}
|
onClick={() => setRotation((prev) => prev + 90)}
|
||||||
@ -106,11 +94,12 @@ const PreviewDocument = ({ files = [] }) => {
|
|||||||
onClick={resetAll}
|
onClick={resetAll}
|
||||||
title="Reset"
|
title="Reset"
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
// onWheel={handleWheel}
|
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
onMouseMove={handleMouseMove}
|
onMouseMove={handleMouseMove}
|
||||||
onMouseUp={handleMouseUp}
|
onMouseUp={handleMouseUp}
|
||||||
@ -119,14 +108,32 @@ const PreviewDocument = ({ files = [] }) => {
|
|||||||
className="position-relative d-flex justify-content-center align-items-center bg-light-secondary overflow-hidden"
|
className="position-relative d-flex justify-content-center align-items-center bg-light-secondary overflow-hidden"
|
||||||
style={{
|
style={{
|
||||||
minHeight: "70vh",
|
minHeight: "70vh",
|
||||||
|
|
||||||
userSelect: "none",
|
userSelect: "none",
|
||||||
borderRadius: "10px",
|
borderRadius: "10px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{loading && <div className="text-secondary">Loading...</div>}
|
{loading && <div className="text-secondary">Loading...</div>}
|
||||||
|
|
||||||
|
{/* PDF VIEW */}
|
||||||
|
{isPDF ? (
|
||||||
|
<iframe
|
||||||
|
src={"./Expenses.pdf"}
|
||||||
|
title="PDF Preview"
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: "70vh",
|
||||||
|
border: "none",
|
||||||
|
}}
|
||||||
|
onLoad={() => setLoading(false)}
|
||||||
|
onError={(error)=>{
|
||||||
|
console.log(error)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
/* IMAGE VIEW */
|
||||||
<img
|
<img
|
||||||
src={currentImage?.preSignedUrl}
|
src={fileUrl}
|
||||||
alt="Preview"
|
alt="Preview"
|
||||||
draggable="false"
|
draggable="false"
|
||||||
style={{
|
style={{
|
||||||
@ -142,11 +149,14 @@ const PreviewDocument = ({ files = [] }) => {
|
|||||||
}}
|
}}
|
||||||
onLoad={() => setLoading(false)}
|
onLoad={() => setLoading(false)}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div className="d-flex justify-content-between">
|
<div className="d-flex justify-content-between">
|
||||||
<div className="text-center text-muted mt-2 small">
|
<div className="text-center text-muted mt-2 small">
|
||||||
Scroll = change image | Double click = reset
|
Scroll = change file | Double click = reset (images only)
|
||||||
</div>
|
</div>
|
||||||
<div className="d-flex align-items-center gap-2">
|
<div className="d-flex align-items-center gap-2">
|
||||||
<i
|
<i
|
||||||
|
|||||||
@ -390,12 +390,10 @@ const tdsPercentage = Number(watch("tdsPercentage")) || 0;
|
|||||||
key={doc.documentId}
|
key={doc.documentId}
|
||||||
className="d-flex align-items-center cusor-pointer"
|
className="d-flex align-items-center cusor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (isImage) {
|
|
||||||
setDocumentView({
|
setDocumentView({
|
||||||
IsOpen: true,
|
IsOpen: true,
|
||||||
Images: data?.documents,
|
Images: data?.documents,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
|
|||||||
@ -69,7 +69,7 @@ const Sidebar = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{data &&
|
{data &&
|
||||||
data?.data.map((section) => (
|
data?.data?.map((section) => (
|
||||||
<React.Fragment
|
<React.Fragment
|
||||||
key={section.id || section.header || section.items[0]?.id}
|
key={section.id || section.header || section.items[0]?.id}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -10,11 +10,13 @@ import {
|
|||||||
import useMaster, { useServices } from "../../../hooks/masterHook/useMaster";
|
import useMaster, { useServices } from "../../../hooks/masterHook/useMaster";
|
||||||
import showToast from "../../../services/toastService";
|
import showToast from "../../../services/toastService";
|
||||||
import { useOrganizationEmployees } from "../../../hooks/useOrganization";
|
import { useOrganizationEmployees } from "../../../hooks/useOrganization";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { changeMaster } from "../../../slices/localVariablesSlice";
|
||||||
|
|
||||||
const TeamEmployeeList = ({ organizationId, searchTerm, closeModal }) => {
|
const TeamEmployeeList = ({ organizationId, searchTerm, closeModal }) => {
|
||||||
const selectedProject = useSelectedProject();
|
const selectedProject = useSelectedProject();
|
||||||
const debounceSearchTerm = useDebounce(searchTerm, 500);
|
const debounceSearchTerm = useDebounce(searchTerm, 500);
|
||||||
|
const dispatch = useDispatch();
|
||||||
const {
|
const {
|
||||||
data: employeesData = [],
|
data: employeesData = [],
|
||||||
isLoading,
|
isLoading,
|
||||||
@ -45,6 +47,7 @@ const TeamEmployeeList = ({ organizationId, searchTerm, closeModal }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
dispatch(changeMaster("Job Role"));
|
||||||
if (employeesData?.data?.length > 0) {
|
if (employeesData?.data?.length > 0) {
|
||||||
const available = employeesData.data.filter((emp) => {
|
const available = employeesData.data.filter((emp) => {
|
||||||
const projEmp = projectEmployees.find((pe) => pe.employeeId === emp.id);
|
const projEmp = projectEmployees.find((pe) => pe.employeeId === emp.id);
|
||||||
@ -183,8 +186,7 @@ if (employees.length === 0) {
|
|||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleSelectChange(index, "serviceId", e.target.value)
|
handleSelectChange(index, "serviceId", e.target.value)
|
||||||
}
|
}
|
||||||
className={`form-select form-select-sm w-auto border-none rounded-0 py-1 px-auto ${
|
className={`form-select form-select-sm w-auto border-none rounded-0 py-1 px-auto ${emp.errors.serviceId ? "is-invalid" : ""
|
||||||
emp.errors.serviceId ? "is-invalid" : ""
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<option value="">Select Service</option>
|
<option value="">Select Service</option>
|
||||||
@ -205,8 +207,7 @@ if (employees.length === 0) {
|
|||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleSelectChange(index, "jobRole", e.target.value)
|
handleSelectChange(index, "jobRole", e.target.value)
|
||||||
}
|
}
|
||||||
className={`form-select form-select-sm w-auto border-none rounded-0 py-1 px-auto ${
|
className={`form-select form-select-sm w-auto border-none rounded-0 py-1 px-auto ${emp.errors.jobRole ? "is-invalid" : ""
|
||||||
emp.errors.jobRole ? "is-invalid" : ""
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<option value="">Select Job Role</option>
|
<option value="">Select Job Role</option>
|
||||||
|
|||||||
@ -1,56 +1,38 @@
|
|||||||
import React from "react";
|
import React, { useMemo } from "react";
|
||||||
import Avatar from "../../common/Avatar";
|
|
||||||
import { formatUTCToLocalTime } from "../../../utils/dateUtils";
|
|
||||||
|
|
||||||
|
import { getColorNameFromHex } from "../../../utils/appUtils";
|
||||||
|
import Timeline from "../../common/TimeLine";
|
||||||
|
|
||||||
const JobStatusLog = ({ data }) => {
|
const JobStatusLog = ({ data }) => {
|
||||||
|
// Prepare timeline items
|
||||||
|
const timelineData = useMemo(() => {
|
||||||
|
if (!data) return [];
|
||||||
|
return data
|
||||||
|
.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt))
|
||||||
|
.map((log) => ({
|
||||||
|
id: log.id,
|
||||||
|
title: log.nextStatus?.displayName || log.status?.displayName || "Status Updated",
|
||||||
|
description: log.nextStatus?.description || "",
|
||||||
|
timeAgo: log.updatedAt,
|
||||||
|
color: getColorNameFromHex(log.nextStatus?.color) || "primary",
|
||||||
|
userComment: log.comment,
|
||||||
|
users: log.updatedBy
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
firstName: log.updatedBy.firstName || "",
|
||||||
|
lastName: log.updatedBy.lastName || "",
|
||||||
|
role: log.updatedBy.jobRoleName || "",
|
||||||
|
avatar: log.updatedBy.photo || null,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [],
|
||||||
|
}));
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card shadow-none p-0">
|
<div className="card shadow-none p-0">
|
||||||
<div className="card-body p-0">
|
<div className="card-body p-3">
|
||||||
<div className="list-group">
|
<Timeline items={timelineData} transparent={true} />
|
||||||
{data?.map((item) => (
|
|
||||||
<div
|
|
||||||
key={item.id}
|
|
||||||
className="list-group-item border-0 border-bottom p-1"
|
|
||||||
>
|
|
||||||
<div className="d-flex justify-content-between">
|
|
||||||
<div>
|
|
||||||
<span className="fw-semibold">
|
|
||||||
{item.nextStatus?.displayName ??
|
|
||||||
item.status?.displayName ??
|
|
||||||
"Status"}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span className="text-secondary">
|
|
||||||
{formatUTCToLocalTime(item?.updatedAt,true)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="d-flex align-items-start mt-2 mx-0 px-0">
|
|
||||||
<Avatar
|
|
||||||
firstName={item.updatedBy?.firstName}
|
|
||||||
lastName={item.updatedBy?.lastName}
|
|
||||||
/>
|
|
||||||
<div className="">
|
|
||||||
<div className="d-flex flex-row gap-3">
|
|
||||||
<span className="fw-semibold">
|
|
||||||
{item.updatedBy?.firstName} {item.updatedBy?.lastName}
|
|
||||||
</span>
|
|
||||||
<span className="text-secondary">
|
|
||||||
{/* <em>{formatUTCToLocalTime(item?.createdAt, true)}</em> */}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="text-muted text-secondary">
|
|
||||||
{item?.updatedBy?.jobRoleName}
|
|
||||||
</div>
|
|
||||||
<div className="text-wrap">
|
|
||||||
<p className="mb-1 mt-2 text-muted">{item.comment}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -23,20 +23,21 @@ const ManageJobTicket = ({ Job }) => {
|
|||||||
);
|
);
|
||||||
const drawerRef = useRef();
|
const drawerRef = useRef();
|
||||||
const tabsData = [
|
const tabsData = [
|
||||||
{
|
|
||||||
id: "comment",
|
|
||||||
title: "Comments",
|
|
||||||
icon: "bx bx-comment me-2",
|
|
||||||
active: true,
|
|
||||||
content: <JobComments data={data} />,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "history",
|
id: "history",
|
||||||
title: "History",
|
title: "History",
|
||||||
icon: "bx bx-time me-2",
|
icon: "bx bx-time me-2",
|
||||||
active: false,
|
active: true,
|
||||||
content: <JobStatusLog data={data?.updateLogs} />,
|
content: <JobStatusLog data={data?.updateLogs} />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "comment",
|
||||||
|
title: "Comments",
|
||||||
|
icon: "bx bx-comment me-2",
|
||||||
|
active: false,
|
||||||
|
content: <JobComments data={data} />,
|
||||||
|
},
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
if (isLoading) return <JobDetailsSkeleton />;
|
if (isLoading) return <JobDetailsSkeleton />;
|
||||||
|
|||||||
@ -66,7 +66,7 @@ const ServiceProjectTeamList = () => {
|
|||||||
<tbody>
|
<tbody>
|
||||||
{data?.length > 0 ? (
|
{data?.length > 0 ? (
|
||||||
data.map((row) => (
|
data.map((row) => (
|
||||||
<tr key={row.id}>
|
<tr key={row.id} style={{ height: "50px" }}>
|
||||||
{servceProjectColmen.map((col) => (
|
{servceProjectColmen.map((col) => (
|
||||||
<td key={col.key} className={col.align}>{col.getValue(row)}</td>
|
<td key={col.key} className={col.align}>{col.getValue(row)}</td>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -201,3 +201,13 @@ export const useExpenseDataByProject = (projectId, categoryId, months) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useGetCollectionOverview = (projectId) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["collection_overview", projectId],
|
||||||
|
queryFn: async () => {
|
||||||
|
const resp = await GlobalRepository.getCollectionOverview(projectId);
|
||||||
|
return resp.data;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@ -59,7 +59,7 @@ const AttendancePage = () => {
|
|||||||
if (selectedProject == null) {
|
if (selectedProject == null) {
|
||||||
dispatch(setProjectId(projectNames[0]?.id));
|
dispatch(setProjectId(projectNames[0]?.id));
|
||||||
}
|
}
|
||||||
dispatch(setOrganization(appliedFilters?.selectedOrganization))
|
dispatch(setOrganization(appliedFilters?.selectedOrganization));
|
||||||
}, [appliedFilters?.selectedOrganization]);
|
}, [appliedFilters?.selectedOrganization]);
|
||||||
|
|
||||||
const getRole = (roleId) => {
|
const getRole = (roleId) => {
|
||||||
@ -94,6 +94,49 @@ const AttendancePage = () => {
|
|||||||
setSearchTerm(""); // Reset search term when tab changes
|
setSearchTerm(""); // Reset search term when tab changes
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// --- START: Tab Configuration Array
|
||||||
|
const tabsData = [
|
||||||
|
{
|
||||||
|
id: "all",
|
||||||
|
title: "Today's",
|
||||||
|
content: (
|
||||||
|
<Attendance
|
||||||
|
handleModalData={handleModalData}
|
||||||
|
getRole={getRole}
|
||||||
|
searchTerm={searchTerm}
|
||||||
|
organizationId={appliedFilters.selectedOrganization}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "logs",
|
||||||
|
title: "Logs",
|
||||||
|
content: (
|
||||||
|
<AttendanceLog
|
||||||
|
handleModalData={handleModalData}
|
||||||
|
searchTerm={searchTerm}
|
||||||
|
organizationId={appliedFilters.selectedOrganization}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
// Conditionally include Regularization tab based on permission
|
||||||
|
...(DoRegularized
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
id: "regularization",
|
||||||
|
title: "Regularization",
|
||||||
|
content: (
|
||||||
|
<Regularization
|
||||||
|
searchTerm={searchTerm}
|
||||||
|
organizationId={appliedFilters.selectedOrganization}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
];
|
||||||
|
// --- END: Tab Configuration Array
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isCreateModalOpen && modelConfig && (
|
{isCreateModalOpen && modelConfig && (
|
||||||
@ -114,9 +157,7 @@ const AttendancePage = () => {
|
|||||||
{modelConfig?.action === 6 && (
|
{modelConfig?.action === 6 && (
|
||||||
<AttendLogs Id={modelConfig?.id} closeModal={closeModal} />
|
<AttendLogs Id={modelConfig?.id} closeModal={closeModal} />
|
||||||
)}
|
)}
|
||||||
{modelConfig?.action === 7 && (
|
{modelConfig?.action === 7 && <Confirmation closeModal={closeModal} />}
|
||||||
<Confirmation closeModal={closeModal} />
|
|
||||||
)}
|
|
||||||
</GlobalModel>
|
</GlobalModel>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -129,57 +170,39 @@ const AttendancePage = () => {
|
|||||||
></Breadcrumb>
|
></Breadcrumb>
|
||||||
|
|
||||||
<div className="nav-align-top nav-tabs-shadow ">
|
<div className="nav-align-top nav-tabs-shadow ">
|
||||||
{/* Tabs */}
|
{/* Tabs header with search and filter */}
|
||||||
<div className="nav-align-top nav-tabs-shadow bg-white border-bottom pt-5">
|
<div className="nav-align-top nav-tabs-shadow bg-white border-bottom pt-5">
|
||||||
<div className="row align-items-center g-0 mb-3 mb-md-0 mx-1 mx-sm-5">
|
<div className="row align-items-center g-0 mb-3 mb-md-0 mx-1 mx-sm-5">
|
||||||
{/* Tabs */}
|
{/* Tabs Buttons */}
|
||||||
<div className="col-12 col-md">
|
<div className="col-12 col-md mt-1">
|
||||||
<ul className="nav nav-tabs" role="tablist">
|
<ul className="nav nav-tabs" role="tablist">
|
||||||
<li className="nav-item">
|
{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
|
||||||
|
key={tab.id}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`nav-link ${
|
className={`nav-link ${
|
||||||
activeTab === "all" ? "active" : ""
|
activeTab === tab.id ? "active" : ""
|
||||||
} fs-6`}
|
} fs-6`}
|
||||||
onClick={() => handleTabChange("all")}
|
onClick={() => handleTabChange(tab.id)}
|
||||||
data-bs-toggle="tab"
|
data-bs-toggle="tab"
|
||||||
data-bs-target="#navs-top-home"
|
data-bs-target={`#navs-top-${tab.id}`}
|
||||||
>
|
>
|
||||||
Today's
|
{tab.title}
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li className="nav-item">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={`nav-link ${
|
|
||||||
activeTab === "logs" ? "active" : ""
|
|
||||||
} fs-6`}
|
|
||||||
onClick={() => handleTabChange("logs")}
|
|
||||||
data-bs-toggle="tab"
|
|
||||||
data-bs-target="#navs-top-profile"
|
|
||||||
>
|
|
||||||
Logs
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li className={`nav-item ${!DoRegularized ? "d-none" : ""}`}>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={`nav-link ${
|
|
||||||
activeTab === "regularization" ? "active" : ""
|
|
||||||
} fs-6`}
|
|
||||||
onClick={() => handleTabChange("regularization")}
|
|
||||||
data-bs-toggle="tab"
|
|
||||||
data-bs-target="#navs-top-messages"
|
|
||||||
>
|
|
||||||
Regularization
|
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Search + Organization filter */}
|
{/* Search + Organization filter */}
|
||||||
<div className="col-12 col-md-auto pb-2 m-0 mt-md-0 ms-md-auto nav-tabs">
|
<div className="col-12 col-md-auto mb-2 mt-md-0 ms-md-auto nav">
|
||||||
<div className="row g-2">
|
<div className="row g-2">
|
||||||
<div className="col-12 col-sm-6">
|
<div className="col-12 col-sm-6">
|
||||||
<select
|
<select
|
||||||
@ -212,40 +235,25 @@ const AttendancePage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Tab Content */}
|
||||||
<div className="tab-content attedanceTabs py-0 px-1 px-sm-3 pb-10">
|
<div className="tab-content attedanceTabs py-0 px-1 px-sm-3 pb-10">
|
||||||
{selectedProject ? (
|
{selectedProject ? (
|
||||||
<>
|
<>
|
||||||
{activeTab === "all" && (
|
{tabsData.map((tab) => (
|
||||||
<div className="tab-pane fade show active py-0 mx-2">
|
<div
|
||||||
<Attendance
|
key={tab.id}
|
||||||
handleModalData={handleModalData}
|
className={`tab-pane fade ${
|
||||||
getRole={getRole}
|
activeTab === tab.id ? "show active" : ""
|
||||||
searchTerm={searchTerm}
|
} py-0 ${tab.id === "all" ? "mx-2" : "p-3"}`}
|
||||||
organizationId={appliedFilters.selectedOrganization}
|
id={`navs-top-${tab.id}`}
|
||||||
/>
|
role="tabpanel"
|
||||||
|
>
|
||||||
|
{activeTab === tab.id && tab.content}
|
||||||
</div>
|
</div>
|
||||||
)}
|
))}
|
||||||
{activeTab === "logs" && (
|
|
||||||
<div className="tab-pane fade p-3 show active py-0">
|
|
||||||
<AttendanceLog
|
|
||||||
handleModalData={handleModalData}
|
|
||||||
searchTerm={searchTerm}
|
|
||||||
organizationId={appliedFilters.selectedOrganization}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{activeTab === "regularization" && DoRegularized && (
|
|
||||||
<div className="tab-pane fade p-3 show active py-0">
|
|
||||||
<Regularization
|
|
||||||
searchTerm={searchTerm}
|
|
||||||
organizationId={appliedFilters.selectedOrganization}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="py-2">
|
<div className="py-2">
|
||||||
|
|||||||
@ -84,9 +84,9 @@ const GlobalRepository = {
|
|||||||
return api.get(url);
|
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`)
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -43,7 +43,7 @@ export const getJobStatusBadge = (statusId) => {
|
|||||||
"32d76a02-8f44-4aa0-9b66-c3716c45a918": "bg-label-primary", // New
|
"32d76a02-8f44-4aa0-9b66-c3716c45a918": "bg-label-primary", // New
|
||||||
"cfa1886d-055f-4ded-84c6-42a2a8a14a66": "bg-label-info", // Assigned
|
"cfa1886d-055f-4ded-84c6-42a2a8a14a66": "bg-label-info", // Assigned
|
||||||
"5a6873a5-fed7-4745-a52f-8f61bf3bd72d": "bg-label-warning", // In Progress
|
"5a6873a5-fed7-4745-a52f-8f61bf3bd72d": "bg-label-warning", // In Progress
|
||||||
"aab71020-2fb8-44d9-9430-c9a7e9bf33b0": "bg-label-label-dark", // Review
|
"aab71020-2fb8-44d9-9430-c9a7e9bf33b0": "bg-label-dark", // Review
|
||||||
"ed10ab57-dbaa-4ca5-8ecd-56745dcbdbd7": "bg-label-success", // Done
|
"ed10ab57-dbaa-4ca5-8ecd-56745dcbdbd7": "bg-label-success", // Done
|
||||||
"3ddeefb5-ae3c-4e10-a922-35e0a452bb69": "bg-label-secondary", // Closed
|
"3ddeefb5-ae3c-4e10-a922-35e0a452bb69": "bg-label-secondary", // Closed
|
||||||
"75a0c8b8-9c6a-41af-80bf-b35bab722eb2": "bg-label-danger", // On Hold
|
"75a0c8b8-9c6a-41af-80bf-b35bab722eb2": "bg-label-danger", // On Hold
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user