Refactor: Improve chart visuals
Improve the visual appearance of the pie chart and the "Receitas vs Despesas" chart, aiming for a more modern look, without affecting other functionalities.
This commit is contained in:
parent
67c4d2e318
commit
002103e676
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { PieChart, Pie, Cell, ResponsiveContainer, Legend, Tooltip, Label } from 'recharts';
|
import { PieChart, Pie, Cell, ResponsiveContainer, Legend, Tooltip } from 'recharts';
|
||||||
import { CategorySummary } from '@/types/financialTypes';
|
import { CategorySummary } from '@/types/financialTypes';
|
||||||
|
|
||||||
interface CategoryChartProps {
|
interface CategoryChartProps {
|
||||||
@ -13,6 +13,9 @@ const CategoryChart: React.FC<CategoryChartProps> = ({ categories, isLoading = f
|
|||||||
// Filter out any categories with zero value to prevent rendering issues
|
// Filter out any categories with zero value to prevent rendering issues
|
||||||
const validCategories = categories.filter(cat => cat.valor > 0);
|
const validCategories = categories.filter(cat => cat.valor > 0);
|
||||||
|
|
||||||
|
// Calcular o valor total para exibir no centro do gráfico
|
||||||
|
const totalValue = validCategories.reduce((sum, cat) => sum + cat.valor, 0);
|
||||||
|
|
||||||
const renderCustomizedLabel = ({ cx, cy, midAngle, innerRadius, outerRadius, percent, index, name }: any) => {
|
const renderCustomizedLabel = ({ cx, cy, midAngle, innerRadius, outerRadius, percent, index, name }: any) => {
|
||||||
if (percent < 0.05) return null; // Não mostrar texto para fatias muito pequenas
|
if (percent < 0.05) return null; // Não mostrar texto para fatias muito pequenas
|
||||||
|
|
||||||
@ -27,7 +30,7 @@ const CategoryChart: React.FC<CategoryChartProps> = ({ categories, isLoading = f
|
|||||||
fill="#fff"
|
fill="#fff"
|
||||||
textAnchor={x > cx ? 'start' : 'end'}
|
textAnchor={x > cx ? 'start' : 'end'}
|
||||||
dominantBaseline="central"
|
dominantBaseline="central"
|
||||||
className="text-xs font-medium"
|
className="text-xs font-bold"
|
||||||
>
|
>
|
||||||
{`${(percent * 100).toFixed(0)}%`}
|
{`${(percent * 100).toFixed(0)}%`}
|
||||||
</text>
|
</text>
|
||||||
@ -45,14 +48,14 @@ const CategoryChart: React.FC<CategoryChartProps> = ({ categories, isLoading = f
|
|||||||
const { payload } = props;
|
const { payload } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-2 gap-x-4 gap-y-1 mt-2 text-xs">
|
<div className="grid grid-cols-2 gap-x-4 gap-y-2 mt-3 text-xs">
|
||||||
{payload.map((entry: any, index: number) => (
|
{payload.map((entry: any, index: number) => (
|
||||||
<div key={`item-${index}`} className="flex items-center">
|
<div key={`item-${index}`} className="flex items-center">
|
||||||
<div
|
<div
|
||||||
className="w-3 h-3 mr-2"
|
className="w-3 h-3 mr-2 rounded-sm"
|
||||||
style={{ backgroundColor: entry.color }}
|
style={{ backgroundColor: entry.color }}
|
||||||
/>
|
/>
|
||||||
<span className="truncate">{entry.value}</span>
|
<span className="truncate font-medium">{entry.value}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -62,10 +65,10 @@ const CategoryChart: React.FC<CategoryChartProps> = ({ categories, isLoading = f
|
|||||||
const renderCustomTooltip = ({ active, payload }: any) => {
|
const renderCustomTooltip = ({ active, payload }: any) => {
|
||||||
if (active && payload && payload.length) {
|
if (active && payload && payload.length) {
|
||||||
return (
|
return (
|
||||||
<div className="bg-background border border-border p-2 rounded shadow-lg">
|
<div className="bg-background border border-border p-2 rounded-lg shadow-lg">
|
||||||
<p className="font-medium">{payload[0].name}</p>
|
<p className="font-medium">{payload[0].name}</p>
|
||||||
<p>{formatCurrency(payload[0].value)}</p>
|
<p className="font-bold">{formatCurrency(payload[0].value)}</p>
|
||||||
<p>{`${(payload[0].payload.percentage * 100).toFixed(1)}%`}</p>
|
<p className="text-sm">{`${(payload[0].payload.percentage * 100).toFixed(1)}%`}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -74,9 +77,11 @@ const CategoryChart: React.FC<CategoryChartProps> = ({ categories, isLoading = f
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="dashboard-card h-full">
|
<Card className="dashboard-card h-full shadow-md">
|
||||||
<CardHeader>
|
<CardHeader className="pb-2 bg-gradient-to-r from-gray-50 to-gray-100 dark:from-gray-800 dark:to-gray-900">
|
||||||
<CardTitle className="text-lg">Gastos por Categoria</CardTitle>
|
<CardTitle className="text-lg flex items-center">
|
||||||
|
Gastos por Categoria
|
||||||
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
@ -89,28 +94,44 @@ const CategoryChart: React.FC<CategoryChartProps> = ({ categories, isLoading = f
|
|||||||
<p className="text-sm mt-2">Verifique se existem transações do tipo 'despesa' cadastradas</p>
|
<p className="text-sm mt-2">Verifique se existem transações do tipo 'despesa' cadastradas</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="h-[250px]">
|
<div className="h-[300px] relative">
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<PieChart>
|
<PieChart>
|
||||||
<Pie
|
<Pie
|
||||||
data={validCategories}
|
data={validCategories}
|
||||||
cx="50%"
|
cx="50%"
|
||||||
cy="50%"
|
cy="45%"
|
||||||
labelLine={false}
|
labelLine={false}
|
||||||
label={renderCustomizedLabel}
|
label={renderCustomizedLabel}
|
||||||
outerRadius={80}
|
outerRadius={100}
|
||||||
|
innerRadius={60}
|
||||||
fill="#8884d8"
|
fill="#8884d8"
|
||||||
dataKey="valor"
|
dataKey="valor"
|
||||||
nameKey="categoria"
|
nameKey="categoria"
|
||||||
|
paddingAngle={1}
|
||||||
|
strokeWidth={1}
|
||||||
|
stroke="#fff"
|
||||||
>
|
>
|
||||||
{validCategories.map((entry, index) => (
|
{validCategories.map((entry, index) => (
|
||||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
<Cell
|
||||||
|
key={`cell-${index}`}
|
||||||
|
fill={entry.color}
|
||||||
|
style={{ filter: 'drop-shadow(0px 2px 2px rgba(0, 0, 0, 0.1))' }}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</Pie>
|
</Pie>
|
||||||
<Tooltip content={renderCustomTooltip} />
|
<Tooltip content={renderCustomTooltip} />
|
||||||
<Legend content={renderCustomLegend} />
|
<Legend content={renderCustomLegend} layout="horizontal" verticalAlign="bottom" />
|
||||||
</PieChart>
|
</PieChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
|
|
||||||
|
{/* Valor total no centro do gráfico */}
|
||||||
|
<div
|
||||||
|
className="absolute top-[45%] left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-center pointer-events-none"
|
||||||
|
style={{ width: '120px' }}
|
||||||
|
>
|
||||||
|
<div className="font-bold text-lg">{formatCurrency(totalValue)}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@ -10,6 +10,8 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
Legend,
|
Legend,
|
||||||
ResponsiveContainer,
|
ResponsiveContainer,
|
||||||
|
Cell,
|
||||||
|
LabelList,
|
||||||
} from 'recharts';
|
} from 'recharts';
|
||||||
import { MonthlyData } from '@/types/financialTypes';
|
import { MonthlyData } from '@/types/financialTypes';
|
||||||
|
|
||||||
@ -27,10 +29,35 @@ const MonthlyChart: React.FC<MonthlyChartProps> = ({ data, isLoading = false })
|
|||||||
maximumFractionDigits: 0,
|
maximumFractionDigits: 0,
|
||||||
}).format(value);
|
}).format(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Adicionar formatação condicional para os barras
|
||||||
|
const getBarColor = (dataPoint: MonthlyData) => {
|
||||||
|
return dataPoint.receitas > dataPoint.despesas ? '#10B981' : '#EF4444';
|
||||||
|
};
|
||||||
|
|
||||||
|
const customTooltip = ({ active, payload, label }: any) => {
|
||||||
|
if (active && payload && payload.length) {
|
||||||
|
return (
|
||||||
|
<div className="bg-white p-3 border border-gray-200 rounded-md shadow-lg">
|
||||||
|
<p className="font-bold mb-1">{`Mês: ${label}`}</p>
|
||||||
|
<p className="text-green-600">
|
||||||
|
{`Receitas: ${formatCurrency(payload[0].value)}`}
|
||||||
|
</p>
|
||||||
|
<p className="text-red-600">
|
||||||
|
{`Despesas: ${formatCurrency(payload[1].value)}`}
|
||||||
|
</p>
|
||||||
|
<p className="text-gray-700 font-medium pt-1 border-t border-gray-200 mt-1">
|
||||||
|
{`Saldo: ${formatCurrency(payload[0].value - payload[1].value)}`}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="dashboard-card h-full">
|
<Card className="dashboard-card h-full shadow-md">
|
||||||
<CardHeader>
|
<CardHeader className="pb-2 bg-gradient-to-r from-gray-50 to-gray-100 dark:from-gray-800 dark:to-gray-900">
|
||||||
<CardTitle className="text-lg">Receitas vs Despesas</CardTitle>
|
<CardTitle className="text-lg">Receitas vs Despesas</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
@ -43,35 +70,82 @@ const MonthlyChart: React.FC<MonthlyChartProps> = ({ data, isLoading = false })
|
|||||||
Sem dados disponíveis
|
Sem dados disponíveis
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="h-[250px]">
|
<div className="h-[300px]">
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<BarChart
|
<BarChart
|
||||||
data={data}
|
data={data}
|
||||||
margin={{ top: 20, right: 30, left: 20, bottom: 5 }}
|
margin={{ top: 20, right: 30, left: 20, bottom: 15 }}
|
||||||
|
barGap={0}
|
||||||
>
|
>
|
||||||
<CartesianGrid strokeDasharray="3 3" vertical={false} />
|
<CartesianGrid strokeDasharray="3 3" vertical={false} opacity={0.4} />
|
||||||
<XAxis dataKey="month" />
|
<XAxis
|
||||||
|
dataKey="month"
|
||||||
|
axisLine={false}
|
||||||
|
tickLine={false}
|
||||||
|
tick={{ fontSize: 12, fontWeight: 500 }}
|
||||||
|
dy={10}
|
||||||
|
/>
|
||||||
<YAxis
|
<YAxis
|
||||||
tickFormatter={formatCurrency}
|
tickFormatter={formatCurrency}
|
||||||
|
axisLine={false}
|
||||||
|
tickLine={false}
|
||||||
|
tick={{ fontSize: 12 }}
|
||||||
width={80}
|
width={80}
|
||||||
/>
|
/>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
formatter={(value: number) => formatCurrency(value)}
|
cursor={{ fill: 'rgba(0, 0, 0, 0.05)' }}
|
||||||
labelFormatter={(label) => `Mês: ${label}`}
|
content={customTooltip}
|
||||||
|
/>
|
||||||
|
<Legend
|
||||||
|
verticalAlign="top"
|
||||||
|
height={36}
|
||||||
|
iconType="circle"
|
||||||
|
iconSize={10}
|
||||||
/>
|
/>
|
||||||
<Legend />
|
|
||||||
<Bar
|
<Bar
|
||||||
name="Receitas"
|
name="Receitas"
|
||||||
dataKey="receitas"
|
dataKey="receitas"
|
||||||
fill="#10B981"
|
fill="#10B981"
|
||||||
radius={[4, 4, 0, 0]}
|
radius={[4, 4, 0, 0]}
|
||||||
/>
|
barSize={30}
|
||||||
|
animationDuration={1000}
|
||||||
|
>
|
||||||
|
{data.map((entry, index) => (
|
||||||
|
<Cell
|
||||||
|
key={`cell-receitas-${index}`}
|
||||||
|
fill="#10B981"
|
||||||
|
style={{ filter: 'drop-shadow(0px 2px 2px rgba(0, 0, 0, 0.1))' }}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<LabelList
|
||||||
|
dataKey="receitas"
|
||||||
|
position="top"
|
||||||
|
formatter={formatCurrency}
|
||||||
|
style={{ fontSize: 10, fontWeight: 600, fill: '#10B981' }}
|
||||||
|
/>
|
||||||
|
</Bar>
|
||||||
<Bar
|
<Bar
|
||||||
name="Despesas"
|
name="Despesas"
|
||||||
dataKey="despesas"
|
dataKey="despesas"
|
||||||
fill="#EF4444"
|
fill="#EF4444"
|
||||||
radius={[4, 4, 0, 0]}
|
radius={[4, 4, 0, 0]}
|
||||||
/>
|
barSize={30}
|
||||||
|
animationDuration={1000}
|
||||||
|
>
|
||||||
|
{data.map((entry, index) => (
|
||||||
|
<Cell
|
||||||
|
key={`cell-despesas-${index}`}
|
||||||
|
fill="#EF4444"
|
||||||
|
style={{ filter: 'drop-shadow(0px 2px 2px rgba(0, 0, 0, 0.1))' }}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<LabelList
|
||||||
|
dataKey="despesas"
|
||||||
|
position="top"
|
||||||
|
formatter={formatCurrency}
|
||||||
|
style={{ fontSize: 10, fontWeight: 600, fill: '#EF4444' }}
|
||||||
|
/>
|
||||||
|
</Bar>
|
||||||
</BarChart>
|
</BarChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user