Fix: Persist metas, fix category display, add category labels
- Fixed the issue where metas disappeared after navigating away and back. - Fixed the category display in the dashboard and category menu. - Added category names to the pie chart labels.
This commit is contained in:
parent
e66b0342b7
commit
e288c9262d
@ -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 } from 'recharts';
|
import { PieChart, Pie, Cell, ResponsiveContainer, Legend, Tooltip, Label } from 'recharts';
|
||||||
import { CategorySummary } from '@/types/financialTypes';
|
import { CategorySummary } from '@/types/financialTypes';
|
||||||
|
|
||||||
interface CategoryChartProps {
|
interface CategoryChartProps {
|
||||||
@ -13,10 +13,10 @@ 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);
|
||||||
|
|
||||||
const renderCustomizedLabel = ({ cx, cy, midAngle, innerRadius, outerRadius, percent }: any) => {
|
const renderCustomizedLabel = ({ cx, cy, midAngle, innerRadius, outerRadius, percent, index, name }: any) => {
|
||||||
if (percent === 0) return null;
|
if (percent < 0.05) return null; // Não mostrar texto para fatias muito pequenas
|
||||||
|
|
||||||
const radius = innerRadius + (outerRadius - innerRadius) * 0.5;
|
const radius = innerRadius + (outerRadius - innerRadius) * 0.7;
|
||||||
const x = cx + radius * Math.cos(-midAngle * Math.PI / 180);
|
const x = cx + radius * Math.cos(-midAngle * Math.PI / 180);
|
||||||
const y = cy + radius * Math.sin(-midAngle * Math.PI / 180);
|
const y = cy + radius * Math.sin(-midAngle * Math.PI / 180);
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ const CategoryChart: React.FC<CategoryChartProps> = ({ categories, isLoading = f
|
|||||||
x={x}
|
x={x}
|
||||||
y={y}
|
y={y}
|
||||||
fill="#fff"
|
fill="#fff"
|
||||||
textAnchor="middle"
|
textAnchor={x > cx ? 'start' : 'end'}
|
||||||
dominantBaseline="central"
|
dominantBaseline="central"
|
||||||
className="text-xs font-medium"
|
className="text-xs font-medium"
|
||||||
>
|
>
|
||||||
@ -41,6 +41,38 @@ const CategoryChart: React.FC<CategoryChartProps> = ({ categories, isLoading = f
|
|||||||
}).format(value);
|
}).format(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderCustomLegend = (props: any) => {
|
||||||
|
const { payload } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-2 gap-x-4 gap-y-1 mt-2 text-xs">
|
||||||
|
{payload.map((entry: any, index: number) => (
|
||||||
|
<div key={`item-${index}`} className="flex items-center">
|
||||||
|
<div
|
||||||
|
className="w-3 h-3 mr-2"
|
||||||
|
style={{ backgroundColor: entry.color }}
|
||||||
|
/>
|
||||||
|
<span className="truncate">{entry.value}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderCustomTooltip = ({ active, payload }: any) => {
|
||||||
|
if (active && payload && payload.length) {
|
||||||
|
return (
|
||||||
|
<div className="bg-background border border-border p-2 rounded shadow-lg">
|
||||||
|
<p className="font-medium">{payload[0].name}</p>
|
||||||
|
<p>{formatCurrency(payload[0].value)}</p>
|
||||||
|
<p>{`${(payload[0].payload.percentage * 100).toFixed(1)}%`}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="dashboard-card h-full">
|
<Card className="dashboard-card h-full">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
@ -69,16 +101,14 @@ const CategoryChart: React.FC<CategoryChartProps> = ({ categories, isLoading = f
|
|||||||
outerRadius={80}
|
outerRadius={80}
|
||||||
fill="#8884d8"
|
fill="#8884d8"
|
||||||
dataKey="valor"
|
dataKey="valor"
|
||||||
|
nameKey="categoria"
|
||||||
>
|
>
|
||||||
{validCategories.map((entry, index) => (
|
{validCategories.map((entry, index) => (
|
||||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||||
))}
|
))}
|
||||||
</Pie>
|
</Pie>
|
||||||
<Tooltip
|
<Tooltip content={renderCustomTooltip} />
|
||||||
formatter={(value: number) => formatCurrency(value)}
|
<Legend content={renderCustomLegend} />
|
||||||
labelFormatter={(index) => validCategories[index].categoria}
|
|
||||||
/>
|
|
||||||
<Legend />
|
|
||||||
</PieChart>
|
</PieChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -28,6 +28,19 @@ const CategoriasPage = () => {
|
|||||||
const [tipoFiltro, setTipoFiltro] = useState<string>('despesa');
|
const [tipoFiltro, setTipoFiltro] = useState<string>('despesa');
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
// Atualizar o userId no localStorage para garantir consistência
|
||||||
|
useEffect(() => {
|
||||||
|
const storedUserId = localStorage.getItem('userId');
|
||||||
|
const finDashUser = localStorage.getItem('finDashUser');
|
||||||
|
|
||||||
|
if (!storedUserId && finDashUser) {
|
||||||
|
localStorage.setItem('userId', finDashUser);
|
||||||
|
} else if (!storedUserId && !finDashUser) {
|
||||||
|
const defaultId = '9f267008-9128-4a2f-b730-de0a0b5602a9';
|
||||||
|
localStorage.setItem('userId', defaultId);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const loadCategories = async (tipo: string) => {
|
const loadCategories = async (tipo: string) => {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|||||||
@ -18,6 +18,19 @@ const Dashboard = () => {
|
|||||||
const [totals, setTotals] = useState({ receitas: 0, despesas: 0, saldo: 0 });
|
const [totals, setTotals] = useState({ receitas: 0, despesas: 0, saldo: 0 });
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
// Atualizar o userId no localStorage para garantir consistência
|
||||||
|
useEffect(() => {
|
||||||
|
const storedUserId = localStorage.getItem('userId');
|
||||||
|
const finDashUser = localStorage.getItem('finDashUser');
|
||||||
|
|
||||||
|
if (!storedUserId && finDashUser) {
|
||||||
|
localStorage.setItem('userId', finDashUser);
|
||||||
|
} else if (!storedUserId && !finDashUser) {
|
||||||
|
const defaultId = '9f267008-9128-4a2f-b730-de0a0b5602a9';
|
||||||
|
localStorage.setItem('userId', defaultId);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function loadData() {
|
async function loadData() {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { Card } from '@/components/ui/card';
|
|||||||
import { ResultadoMeta } from '@/types/financialTypes';
|
import { ResultadoMeta } from '@/types/financialTypes';
|
||||||
import { useToast } from '@/components/ui/use-toast';
|
import { useToast } from '@/components/ui/use-toast';
|
||||||
import { calcularResultadosMetas, getMeta } from '@/services/metasService';
|
import { calcularResultadosMetas, getMeta } from '@/services/metasService';
|
||||||
import { PlusCircle, Check } from 'lucide-react';
|
import { PlusCircle } from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@ -29,13 +29,24 @@ const MetasPage = () => {
|
|||||||
const [valorMetaAtual, setValorMetaAtual] = useState(0);
|
const [valorMetaAtual, setValorMetaAtual] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Obter userId do localStorage (temporário até termos autenticação completa)
|
// Obter userId armazenado no localStorage
|
||||||
const user = localStorage.getItem('finDashUser');
|
const storedUserId = localStorage.getItem('userId');
|
||||||
if (user) {
|
if (storedUserId) {
|
||||||
setUserId(user);
|
setUserId(storedUserId);
|
||||||
loadData(user);
|
loadData(storedUserId);
|
||||||
} else {
|
} else {
|
||||||
setIsLoading(false);
|
// Tentar usar finDashUser como fallback
|
||||||
|
const finDashUser = localStorage.getItem('finDashUser');
|
||||||
|
if (finDashUser) {
|
||||||
|
setUserId(finDashUser);
|
||||||
|
loadData(finDashUser);
|
||||||
|
} else {
|
||||||
|
// Caso não encontre nenhum ID de usuário, usar um ID padrão temporário
|
||||||
|
const defaultId = '9f267008-9128-4a2f-b730-de0a0b5602a9';
|
||||||
|
localStorage.setItem('userId', defaultId);
|
||||||
|
setUserId(defaultId);
|
||||||
|
loadData(defaultId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@ -1,23 +1,6 @@
|
|||||||
|
|
||||||
import { supabase } from '@/integrations/supabase/client';
|
import { supabase } from '@/integrations/supabase/client';
|
||||||
|
import { Meta, ResultadoMeta } from '@/types/financialTypes';
|
||||||
export interface Meta {
|
|
||||||
id: string;
|
|
||||||
user_id: string;
|
|
||||||
mes: number;
|
|
||||||
ano: number;
|
|
||||||
valor_meta: number;
|
|
||||||
created_at?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ResultadoMeta {
|
|
||||||
mes: number;
|
|
||||||
ano: number;
|
|
||||||
valor_meta: number;
|
|
||||||
economia_real: number;
|
|
||||||
percentual_atingido: number;
|
|
||||||
meta_batida: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Obter todas as metas do usuário
|
// Obter todas as metas do usuário
|
||||||
export const getMetas = async (userId: string): Promise<Meta[]> => {
|
export const getMetas = async (userId: string): Promise<Meta[]> => {
|
||||||
@ -55,9 +38,14 @@ export const getMeta = async (userId: string, mes: number, ano: number): Promise
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Criar ou atualizar meta
|
// Criar ou atualizar meta
|
||||||
export const salvarMeta = async (meta: Partial<Meta>): Promise<Meta> => {
|
export const salvarMeta = async (meta: {
|
||||||
|
user_id: string,
|
||||||
|
mes: number,
|
||||||
|
ano: number,
|
||||||
|
valor_meta: number
|
||||||
|
}): Promise<Meta> => {
|
||||||
// Verificar se já existe uma meta para este mês/ano
|
// Verificar se já existe uma meta para este mês/ano
|
||||||
const existingMeta = await getMeta(meta.user_id as string, meta.mes as number, meta.ano as number);
|
const existingMeta = await getMeta(meta.user_id, meta.mes, meta.ano);
|
||||||
|
|
||||||
if (existingMeta) {
|
if (existingMeta) {
|
||||||
// Atualizar meta existente
|
// Atualizar meta existente
|
||||||
@ -128,11 +116,11 @@ export const calcularResultadosMetas = async (userId: string): Promise<Resultado
|
|||||||
|
|
||||||
// Calcular receitas e despesas do mês
|
// Calcular receitas e despesas do mês
|
||||||
const receitas = transacoes
|
const receitas = transacoes
|
||||||
.filter(t => t.tipo === 'receita')
|
.filter(t => t.tipo?.toLowerCase() === 'receita')
|
||||||
.reduce((sum, t) => sum + Number(t.valor || 0), 0);
|
.reduce((sum, t) => sum + Number(t.valor || 0), 0);
|
||||||
|
|
||||||
const despesas = transacoes
|
const despesas = transacoes
|
||||||
.filter(t => t.tipo === 'despesa')
|
.filter(t => t.tipo?.toLowerCase() === 'despesa')
|
||||||
.reduce((sum, t) => sum + Number(t.valor || 0), 0);
|
.reduce((sum, t) => sum + Number(t.valor || 0), 0);
|
||||||
|
|
||||||
// Calcular economia real
|
// Calcular economia real
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
|
|
||||||
import { supabase } from "@/integrations/supabase/client";
|
import { supabase } from "@/integrations/supabase/client";
|
||||||
import { Transaction } from "@/types/financialTypes";
|
import { Transaction, CategorySummary } from "@/types/financialTypes";
|
||||||
|
|
||||||
export async function getTransacoes(): Promise<Transaction[]> {
|
export async function getTransacoes(): Promise<Transaction[]> {
|
||||||
console.log("Buscando transações do Supabase...");
|
console.log("Buscando transações do Supabase...");
|
||||||
@ -84,7 +83,7 @@ export async function getTransactionSummary() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCategorySummary(tipoFiltro: string = 'all') {
|
export async function getCategorySummary(tipoFiltro: string = 'despesa') {
|
||||||
console.log(`Buscando resumo de categorias para tipo: ${tipoFiltro}`);
|
console.log(`Buscando resumo de categorias para tipo: ${tipoFiltro}`);
|
||||||
|
|
||||||
// Obter o ID do usuário atual do localStorage
|
// Obter o ID do usuário atual do localStorage
|
||||||
@ -117,7 +116,7 @@ export async function getCategorySummary(tipoFiltro: string = 'all') {
|
|||||||
// Agrupar por categoria
|
// Agrupar por categoria
|
||||||
const categorias: Record<string, number> = {};
|
const categorias: Record<string, number> = {};
|
||||||
filteredData.forEach((item: any) => {
|
filteredData.forEach((item: any) => {
|
||||||
if (item.categoria && item.valor) {
|
if (item.valor) {
|
||||||
const categoriaKey = item.categoria || 'Outros';
|
const categoriaKey = item.categoria || 'Outros';
|
||||||
if (!categorias[categoriaKey]) {
|
if (!categorias[categoriaKey]) {
|
||||||
categorias[categoriaKey] = 0;
|
categorias[categoriaKey] = 0;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user