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:
gpt-engineer-app[bot] 2025-05-20 14:35:43 +00:00
parent 67c4d2e318
commit 002103e676
2 changed files with 124 additions and 29 deletions

View File

@ -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>

View File

@ -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';
@ -28,9 +30,34 @@ const MonthlyChart: React.FC<MonthlyChartProps> = ({ data, isLoading = false })
}).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 ( return (
<Card className="dashboard-card h-full"> <div className="bg-white p-3 border border-gray-200 rounded-md shadow-lg">
<CardHeader> <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 (
<Card className="dashboard-card h-full shadow-md">
<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>