addd Expense By project

This commit is contained in:
pramod.mahajan 2025-10-04 11:08:19 +05:30
parent 18ac8e11bd
commit 997971629f
6 changed files with 234 additions and 26 deletions

View File

@ -18,6 +18,7 @@ import { useSelectedProject } from "../../slices/apiDataManager";
import { useProjectName } from "../../hooks/useProjects";
import ExpenseAnalysis from "./ExpenseAnalysis";
import ExpenseStatus from "./ExpenseStatus";
import ExpenseByProject from "./ExpenseByProject";
const Dashboard = () => {
// const { projectsCardData } = useDashboardProjectsCardData();
@ -29,24 +30,33 @@ const Dashboard = () => {
const isAllProjectsSelected = projectId === null;
return (
<div className="container-xxl flex-grow-1 container-p-y">
<div class="row mb-6 g-6">
<div class="col-12 col-xl-8">
<div class="card h-100">
<ExpenseAnalysis />
</div>
</div>
<div class="col-12 col-xl-4 col-md-6">
<div class="card h-100">
<ExpenseStatus />
</div>
</div>
<div className="container-xxl flex-grow-1 container-p-y">
<div className="row mb-6 g-6">
<div className="col-12 col-xl-8">
<div className="card h-100">
<ExpenseAnalysis />
</div>
{isAllProjectsSelected && (<div className="co-12">
<AttendanceOverview />
</div>)}
</div>
<div className="col-12 col-xl-4 col-md-6">
<div className="card h-100">
<ExpenseStatus />
</div>
</div>
</div>
<div className="row g-6">
{!isAllProjectsSelected && (
<div className="col-6">
<AttendanceOverview />
</div>
)}
<div className="col-6">
<ExpenseByProject />
</div>
</div>
</div>
);
};

View File

@ -0,0 +1,162 @@
import React, { useState, useEffect } from "react";
import Chart from "react-apexcharts";
import { useExpenseType } from "../../hooks/masterHook/useMaster";
import { useSelector } from "react-redux";
import { useExpenseDataByProject } from "../../hooks/useDashboard_Data";
import { formatCurrency } from "../../utils/appUtils";
import { formatDate_DayMonth } from "../../utils/dateUtils";
const ExpenseByProject = () => {
const projectId = useSelector((store) => store.localVariables.projectId);
const [range, setRange] = useState("12M");
const [selectedType, setSelectedType] = useState("");
const [viewMode, setViewMode] = useState("Category");
const [chartData, setChartData] = useState({ categories: [], data: [] });
const { ExpenseTypes, loading: typeLoading } = useExpenseType();
const { data: expenseApiData, isLoading } = useExpenseDataByProject(
projectId,
selectedType,
range === "All" ? null : parseInt(range)
);
useEffect(() => {
if (expenseApiData) {
const categories = expenseApiData.map(
(item) => formatDate_DayMonth(item.monthName, item.year)
);
const data = expenseApiData.map((item) => item.total);
setChartData({ categories, data });
} else {
setChartData({ categories: [], data: [] });
}
}, [expenseApiData]);
const getSelectedTypeName = () => {
if (!selectedType) return "All Types";
const found = ExpenseTypes.find((t) => t.id === selectedType);
return found ? found.name : "All Types";
};
const options = {
chart: { type: "bar", toolbar: { show: false } },
plotOptions: { bar: { horizontal: false, columnWidth: "55%", borderRadius: 4 } },
dataLabels: { enabled: true, formatter: (val) => formatCurrency(val) },
xaxis: {
categories: chartData.categories,
labels: { style: { fontSize: "12px" }, rotate: -45 },
},
tooltip: {
y: {
formatter: (val) => `${formatCurrency(val)} (${getSelectedTypeName()})`,
},
},
annotations: { xaxis: [{ x: 0, strokeDashArray: 0, }] },
fill: { opacity: 1 },
colors: ["#2196f3"],
};
const series = [
{
name: getSelectedTypeName(),
data: chartData.data,
},
];
return (
<div className="card shadow-sm ">
{/* Header */}
<div className="card-header">
<div className="d-flex justify-content-start align-items-center mb-3 mt-3">
<div>
<h5 className="mb-1 me-6 fw-bold">Monthly Expense -</h5>
<p className="card-subtitle me-5 mb-0">Detailed project expenses</p>
</div>
<div className="btn-group mb-4 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>
{viewMode === "Category" && (
<select
className="form-select form-select-sm ms-auto mb-3"
value={selectedType}
onChange={(e) => setSelectedType(e.target.value)}
disabled={typeLoading}
style={{ maxWidth: "200px" }}
>
<option value="">All Types</option>
{ExpenseTypes.map((type) => (
<option key={type.id} value={type.id}>
{type.name}
</option>
))}
</select>
)}
</div>
{/* Range Buttons + Expense Dropdown */}
<div className="d-flex gap-2 align-items-center flex-wrap">
{["1M", "3M", "6M", "12M", "All"].map((item) => (
<button
key={item}
className={`border-0 px-2 py-1 text-sm rounded ${range === item
? "text-white bg-primary"
: "text-body bg-transparent"
}`}
style={{ cursor: "pointer", transition: "all 0.2s ease" }}
onClick={() => setRange(item)}
>
{item}
</button>
))}
</div>
</div>
{/* Chart */}
<div
className="card-body bg-white text-dark p-3"
style={{ minHeight: "400px" }}
>
{isLoading ? (
<p>Loading chart...</p>
) : (
<Chart options={options} series={series} type="bar" height={400} />
)}
</div>
</div>
);
};
export default ExpenseByProject;

View File

@ -282,4 +282,15 @@ export const useExpenseStatus = (projectId)=>{
return resp.data;
}
})
}
}
export const useExpenseDataByProject = (projectId, categoryId, months) => {
return useQuery({
queryKey: ["expenseByProject", projectId, categoryId, months],
queryFn: async () => {
const resp = await GlobalRepository.getExpenseDataByProject(projectId, categoryId, months);
return resp.data;
},
});
};

View File

@ -129,9 +129,7 @@ const ExpensePage = () => {
</div>
<div className="col-6 text-end mt-2 mt-sm-0">
<button className="btn btn-sm" onClick={clearFilter}>
CLear
</button>
{IsCreatedAble && (
<button
className="btn btn-sm btn-primary"

View File

@ -67,7 +67,30 @@ 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`
const queryParams = [];
if (projectId) {
queryParams.push(`projectId=${projectId}`);
}
if (categoryId) {
queryParams.push(`categoryId=${categoryId}`);
}
if (months) {
queryParams.push(`months=${months}`);
}
if (queryParams.length > 0) {
url += `?${queryParams.join("&")}`;
}
return api.get(url);
},
};

View File

@ -94,11 +94,15 @@ export const getCompletionPercentage = (completedWork, plannedWork)=> {
return clamped.toFixed(2);
}
export const formatDate_DayMonth = (dateInput)=>{
const date = new Date(dateInput);
return format(date, "d MMM");
}
export const formatDate_DayMonth = (monthName, year) => {
if (!monthName || !year) return "";
try {
const shortMonth = monthName.substring(0, 3);
return `${shortMonth} ${year}`;
} catch {
return "";
}
};
export const getTenantStatus =(statusId)=>{
return ActiveTenant === statusId ? " bg-label-success":"bg-label-secondary"
}