Changes in Monthly Expense dashboard.
This commit is contained in:
parent
2397be3b8c
commit
80ac18ff26
@ -1,18 +1,18 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import Chart from "react-apexcharts";
|
import Chart from "react-apexcharts";
|
||||||
import { useExpenseType } from "../../hooks/masterHook/useMaster";
|
import { useExpenseType } from "../../hooks/masterHook/useMaster";
|
||||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
import { useSelector } from "react-redux";
|
||||||
import { useExpenseDataByProject } from "../../hooks/useDashboard_Data";
|
import { useExpenseDataByProject } from "../../hooks/useDashboard_Data";
|
||||||
|
|
||||||
const ExpenseByProject = () => {
|
const ExpenseByProject = () => {
|
||||||
const projectId = useSelectedProject();
|
const projectId = useSelector((store) => store.localVariables.projectId);
|
||||||
const [range, setRange] = useState("12M");
|
const [range, setRange] = useState("12M");
|
||||||
const [selectedType, setSelectedType] = useState("");
|
const [selectedType, setSelectedType] = useState("");
|
||||||
|
const [viewMode, setViewMode] = useState("Category");
|
||||||
const [chartData, setChartData] = useState({ categories: [], data: [] });
|
const [chartData, setChartData] = useState({ categories: [], data: [] });
|
||||||
|
|
||||||
const { ExpenseTypes, loading: typeLoading } = useExpenseType();
|
const { ExpenseTypes, loading: typeLoading } = useExpenseType();
|
||||||
|
|
||||||
// Fetch API data
|
|
||||||
const { data: expenseApiData, isLoading } = useExpenseDataByProject(
|
const { data: expenseApiData, isLoading } = useExpenseDataByProject(
|
||||||
projectId,
|
projectId,
|
||||||
selectedType,
|
selectedType,
|
||||||
@ -25,46 +25,95 @@ const ExpenseByProject = () => {
|
|||||||
(item) => `${item.monthName} ${item.year}`
|
(item) => `${item.monthName} ${item.year}`
|
||||||
);
|
);
|
||||||
const data = expenseApiData.map((item) => item.total);
|
const data = expenseApiData.map((item) => item.total);
|
||||||
|
|
||||||
setChartData({ categories, data });
|
setChartData({ categories, data });
|
||||||
} else {
|
} else {
|
||||||
setChartData({ categories: [], data: [] });
|
setChartData({ categories: [], data: [] });
|
||||||
}
|
}
|
||||||
}, [expenseApiData]);
|
}, [expenseApiData]);
|
||||||
|
|
||||||
|
const getSelectedTypeName = () => {
|
||||||
|
if (!selectedType) return "All Types";
|
||||||
|
const found = ExpenseTypes.find((t) => t.id === selectedType);
|
||||||
|
return found ? found.name : "All Types";
|
||||||
|
};
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
chart: { type: "bar", toolbar: { show: false } },
|
chart: { type: "bar", toolbar: { show: false } },
|
||||||
plotOptions: { bar: { horizontal: false, columnWidth: "55%", borderRadius: 4 } },
|
plotOptions: { bar: { horizontal: false, columnWidth: "55%", borderRadius: 4 } },
|
||||||
dataLabels: { enabled: true, formatter: (val) => val },
|
dataLabels: { enabled: true, formatter: (val) => val },
|
||||||
xaxis: { categories: chartData.categories, labels: { style: { fontSize: "12px" }, rotate: -45 } },
|
xaxis: {
|
||||||
|
categories: chartData.categories,
|
||||||
|
labels: { style: { fontSize: "12px" }, rotate: -45 },
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
y: {
|
||||||
|
formatter: (val) => `${val.toLocaleString()} (${getSelectedTypeName()})`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
annotations: { xaxis: [{ x: 0, strokeDashArray: 0, }] },
|
||||||
fill: { opacity: 1 },
|
fill: { opacity: 1 },
|
||||||
colors: ["#2196f3"]
|
colors: ["#2196f3"],
|
||||||
};
|
};
|
||||||
|
|
||||||
const series = [
|
const series = [
|
||||||
{
|
{
|
||||||
name: selectedType || "Expense",
|
name: getSelectedTypeName(),
|
||||||
data: chartData.data
|
data: chartData.data,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card shadow-sm ">
|
<div className="card shadow-sm ">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="card-header">
|
<div className="card-header">
|
||||||
<div className="d-flex justify-content-between align-items-center mb-3 mt-3">
|
<div className="d-flex justify-content-start align-items-center mb-3 mt-3">
|
||||||
<div>
|
<div>
|
||||||
<h5 className="mb-1 fw-bold">Expense Breakdown</h5>
|
<h5 className="mb-1 me-6 fw-bold">Monthly Expense -</h5>
|
||||||
<p className="card-subtitle me-3 mb-0">Detailed project expenses</p>
|
<p className="card-subtitle me-5 mb-0">Detailed project expenses</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="d-flex align-items-center gap-2">
|
{/* View Mode Dropdown → same style as Header */}
|
||||||
{/* Expense Type Dropdown */}
|
<div className="btn-group mb-4 ms-n4">
|
||||||
|
<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
|
<select
|
||||||
className="form-select form-select-sm mb-2"
|
className="form-select form-select-sm ms-auto mb-3"
|
||||||
value={selectedType}
|
value={selectedType}
|
||||||
onChange={(e) => setSelectedType(e.target.value)}
|
onChange={(e) => setSelectedType(e.target.value)}
|
||||||
disabled={typeLoading}
|
disabled={typeLoading}
|
||||||
|
style={{ maxWidth: "200px" }}
|
||||||
>
|
>
|
||||||
<option value="">All Types</option>
|
<option value="">All Types</option>
|
||||||
{ExpenseTypes.map((type) => (
|
{ExpenseTypes.map((type) => (
|
||||||
@ -73,15 +122,19 @@ const ExpenseByProject = () => {
|
|||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Range Buttons */}
|
{/* Range Buttons + Expense Dropdown */}
|
||||||
<div className="d-flex gap-2">
|
<div className="d-flex gap-2 align-items-center flex-wrap">
|
||||||
{["6M", "12M", "All"].map((item) => (
|
{["1M", "3M", "6M", "12M", "All"].map((item) => (
|
||||||
<button
|
<button
|
||||||
key={item}
|
key={item}
|
||||||
className={`btn btn-xs ${range === item ? "btn-primary" : "btn-outline-light"}`}
|
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)}
|
onClick={() => setRange(item)}
|
||||||
>
|
>
|
||||||
{item}
|
{item}
|
||||||
@ -91,7 +144,10 @@ const ExpenseByProject = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Chart */}
|
{/* Chart */}
|
||||||
<div className="card-body bg-white text-dark" style={{ minHeight: "440px" }}>
|
<div
|
||||||
|
className="card-body bg-white text-dark p-3"
|
||||||
|
style={{ minHeight: "440px" }}
|
||||||
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<p>Loading chart...</p>
|
<p>Loading chart...</p>
|
||||||
) : (
|
) : (
|
||||||
@ -103,5 +159,3 @@ const ExpenseByProject = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default ExpenseByProject;
|
export default ExpenseByProject;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useEffect, useState,useMemo } from "react";
|
import { useCallback, useEffect, useState, useMemo } from "react";
|
||||||
import getGreetingMessage from "../../utils/greetingHandler";
|
import getGreetingMessage from "../../utils/greetingHandler";
|
||||||
import {
|
import {
|
||||||
cacheData,
|
cacheData,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user