feat: Add month filter to dashboard and transactions
Implement month filtering for the dashboard and transactions pages, allowing users to select a month and view data accordingly.
This commit is contained in:
parent
e7b414724b
commit
cb2d335aab
63
src/components/filters/MonthFilter.tsx
Normal file
63
src/components/filters/MonthFilter.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
|
||||
import React from 'react';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Calendar } from 'lucide-react';
|
||||
|
||||
interface MonthFilterProps {
|
||||
selectedMonth: string;
|
||||
onMonthChange: (month: string) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const MonthFilter = ({ selectedMonth, onMonthChange, className }: MonthFilterProps) => {
|
||||
const months = [
|
||||
{ value: '2024-01', label: 'Janeiro 2024' },
|
||||
{ value: '2024-02', label: 'Fevereiro 2024' },
|
||||
{ value: '2024-03', label: 'Março 2024' },
|
||||
{ value: '2024-04', label: 'Abril 2024' },
|
||||
{ value: '2024-05', label: 'Maio 2024' },
|
||||
{ value: '2024-06', label: 'Junho 2024' },
|
||||
{ value: '2024-07', label: 'Julho 2024' },
|
||||
{ value: '2024-08', label: 'Agosto 2024' },
|
||||
{ value: '2024-09', label: 'Setembro 2024' },
|
||||
{ value: '2024-10', label: 'Outubro 2024' },
|
||||
{ value: '2024-11', label: 'Novembro 2024' },
|
||||
{ value: '2024-12', label: 'Dezembro 2024' },
|
||||
{ value: '2025-01', label: 'Janeiro 2025' },
|
||||
{ value: '2025-02', label: 'Fevereiro 2025' },
|
||||
{ value: '2025-03', label: 'Março 2025' },
|
||||
{ value: '2025-04', label: 'Abril 2025' },
|
||||
{ value: '2025-05', label: 'Maio 2025' },
|
||||
{ value: '2025-06', label: 'Junho 2025' },
|
||||
{ value: '2025-07', label: 'Julho 2025' },
|
||||
{ value: '2025-08', label: 'Agosto 2025' },
|
||||
{ value: '2025-09', label: 'Setembro 2025' },
|
||||
{ value: '2025-10', label: 'Outubro 2025' },
|
||||
{ value: '2025-11', label: 'Novembro 2025' },
|
||||
{ value: '2025-12', label: 'Dezembro 2025' },
|
||||
];
|
||||
|
||||
// Função para obter o mês atual no formato YYYY-MM
|
||||
const getCurrentMonth = () => {
|
||||
const now = new Date();
|
||||
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`flex items-center space-x-2 ${className}`}>
|
||||
<Calendar className="h-4 w-4 text-muted-foreground" />
|
||||
<Select value={selectedMonth} onValueChange={onMonthChange}>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue placeholder="Selecione o mês" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{months.map((month) => (
|
||||
<SelectItem key={month.value} value={month.value}>
|
||||
{month.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -1,4 +1,3 @@
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Transaction } from '@/types/financialTypes';
|
||||
import { CartaoCredito } from '@/types/cartaoTypes';
|
||||
@ -6,7 +5,11 @@ import { useToast } from '@/hooks/use-toast';
|
||||
import { getTransacoes } from '@/services/transacao';
|
||||
import { getCartoes } from '@/services/cartaoCreditoService';
|
||||
|
||||
export const useTransactions = () => {
|
||||
interface UseTransactionsProps {
|
||||
monthFilter?: string;
|
||||
}
|
||||
|
||||
export const useTransactions = ({ monthFilter }: UseTransactionsProps = {}) => {
|
||||
const [transactions, setTransactions] = useState<Transaction[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||
@ -20,8 +23,8 @@ export const useTransactions = () => {
|
||||
const loadTransactions = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
console.log("Carregando todas as transações...");
|
||||
const data = await getTransacoes();
|
||||
console.log("Carregando transações com filtro:", monthFilter);
|
||||
const data = await getTransacoes(monthFilter);
|
||||
console.log(`${data.length} transações carregadas com sucesso`);
|
||||
setTransactions(data);
|
||||
} catch (error) {
|
||||
@ -53,7 +56,7 @@ export const useTransactions = () => {
|
||||
useEffect(() => {
|
||||
loadTransactions();
|
||||
loadCartoes();
|
||||
}, []);
|
||||
}, [monthFilter]);
|
||||
|
||||
const handleTransactionSuccess = () => {
|
||||
setIsDialogOpen(false);
|
||||
|
||||
@ -144,6 +144,39 @@ export type Database = {
|
||||
}
|
||||
Relationships: []
|
||||
}
|
||||
conversas_zap: {
|
||||
Row: {
|
||||
created_at: string
|
||||
date_time: string | null
|
||||
id: number
|
||||
id_transacao_gerada: number | null
|
||||
login: string | null
|
||||
msg_recebida: string | null
|
||||
remote_jid: string | null
|
||||
status_processamento: string | null
|
||||
}
|
||||
Insert: {
|
||||
created_at?: string
|
||||
date_time?: string | null
|
||||
id?: number
|
||||
id_transacao_gerada?: number | null
|
||||
login?: string | null
|
||||
msg_recebida?: string | null
|
||||
remote_jid?: string | null
|
||||
status_processamento?: string | null
|
||||
}
|
||||
Update: {
|
||||
created_at?: string
|
||||
date_time?: string | null
|
||||
id?: number
|
||||
id_transacao_gerada?: number | null
|
||||
login?: string | null
|
||||
msg_recebida?: string | null
|
||||
remote_jid?: string | null
|
||||
status_processamento?: string | null
|
||||
}
|
||||
Relationships: []
|
||||
}
|
||||
despesas_cartao: {
|
||||
Row: {
|
||||
cartao_id: string | null
|
||||
@ -294,10 +327,11 @@ export type Database = {
|
||||
empresa: string | null
|
||||
id: string
|
||||
instancia_zap: string | null
|
||||
nome: string
|
||||
nome: string | null
|
||||
remote_jid: string | null
|
||||
senha: string
|
||||
senha: string | null
|
||||
status_instancia: string | null
|
||||
webhook: string | null
|
||||
whatsapp: string
|
||||
}
|
||||
Insert: {
|
||||
@ -306,10 +340,11 @@ export type Database = {
|
||||
empresa?: string | null
|
||||
id?: string
|
||||
instancia_zap?: string | null
|
||||
nome: string
|
||||
nome?: string | null
|
||||
remote_jid?: string | null
|
||||
senha: string
|
||||
senha?: string | null
|
||||
status_instancia?: string | null
|
||||
webhook?: string | null
|
||||
whatsapp: string
|
||||
}
|
||||
Update: {
|
||||
@ -318,10 +353,11 @@ export type Database = {
|
||||
empresa?: string | null
|
||||
id?: string
|
||||
instancia_zap?: string | null
|
||||
nome?: string
|
||||
nome?: string | null
|
||||
remote_jid?: string | null
|
||||
senha?: string
|
||||
senha?: string | null
|
||||
status_instancia?: string | null
|
||||
webhook?: string | null
|
||||
whatsapp?: string
|
||||
}
|
||||
Relationships: []
|
||||
|
||||
@ -5,6 +5,7 @@ import SummaryCard from '@/components/dashboard/SummaryCard';
|
||||
import TransactionsTable from '@/components/dashboard/TransactionsTable';
|
||||
import CategoryChart from '@/components/dashboard/CategoryChart';
|
||||
import MonthlyChart from '@/components/dashboard/MonthlyChart';
|
||||
import { MonthFilter } from '@/components/filters/MonthFilter';
|
||||
import { Wallet, ArrowUp, ArrowDown, PiggyBank, CreditCard } from 'lucide-react';
|
||||
import { Transaction, CategorySummary, MonthlyData } from '@/types/financialTypes';
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
@ -24,6 +25,14 @@ const Dashboard = () => {
|
||||
const [totals, setTotals] = useState({ receitas: 0, despesas: 0, saldo: 0, cartoes: 0 });
|
||||
const { toast } = useToast();
|
||||
|
||||
// Função para obter o mês atual no formato YYYY-MM
|
||||
const getCurrentMonth = () => {
|
||||
const now = new Date();
|
||||
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
const [selectedMonth, setSelectedMonth] = useState<string>(getCurrentMonth());
|
||||
|
||||
// Use useCallback to ensure this function doesn't change on every render
|
||||
const setupUserId = useCallback(() => {
|
||||
const storedUserId = localStorage.getItem('userId');
|
||||
@ -42,19 +51,19 @@ const Dashboard = () => {
|
||||
setupUserId();
|
||||
}, [setupUserId]);
|
||||
|
||||
// Load data only once when component mounts
|
||||
// Load data when component mounts or month changes
|
||||
useEffect(() => {
|
||||
async function loadData() {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
console.log("Iniciando carregamento de dados...");
|
||||
console.log("Iniciando carregamento de dados para o mês:", selectedMonth);
|
||||
|
||||
// Buscar todos os dados necessários
|
||||
// Buscar todos os dados necessários com filtro de mês
|
||||
const [transacoesData, totalsData, categoriesData, monthlyDataResult, cartoesData] = await Promise.all([
|
||||
getTransacoes(),
|
||||
getTransactionSummary(),
|
||||
getCategorySummary(),
|
||||
getMonthlyData(),
|
||||
getTransacoes(selectedMonth),
|
||||
getTransactionSummary(selectedMonth),
|
||||
getCategorySummary('despesa', selectedMonth),
|
||||
getMonthlyData(), // Monthly data não precisa de filtro pois já mostra todos os meses
|
||||
getCartoes()
|
||||
]);
|
||||
|
||||
@ -67,7 +76,8 @@ const Dashboard = () => {
|
||||
categories: categoriesData.length,
|
||||
monthlyData: monthlyDataResult.length,
|
||||
cartoes: cartoesData.length,
|
||||
totalCartoes: totalCartoes
|
||||
totalCartoes: totalCartoes,
|
||||
mesEscolhido: selectedMonth
|
||||
});
|
||||
|
||||
setTransactions(transacoesData);
|
||||
@ -82,7 +92,7 @@ const Dashboard = () => {
|
||||
|
||||
toast({
|
||||
title: "Dados carregados com sucesso",
|
||||
description: "Seus dados financeiros foram atualizados"
|
||||
description: `Dados financeiros de ${selectedMonth} foram atualizados`
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Erro ao carregar dados:", error);
|
||||
@ -97,9 +107,8 @@ const Dashboard = () => {
|
||||
}
|
||||
|
||||
loadData();
|
||||
// Ensure toast doesn't cause re-renders by removing it from dependencies
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
}, [selectedMonth]);
|
||||
|
||||
const formatCurrency = (value: number) => {
|
||||
return new Intl.NumberFormat('pt-BR', {
|
||||
@ -111,14 +120,30 @@ const Dashboard = () => {
|
||||
// Calculate total of all expenses (regular + credit cards)
|
||||
const totalDespesasGeral = totals.despesas + totals.cartoes;
|
||||
|
||||
// Função para formatar o mês para exibição
|
||||
const formatMonthDisplay = (month: string) => {
|
||||
const [year, monthNum] = month.split('-');
|
||||
const months = [
|
||||
'Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho',
|
||||
'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'
|
||||
];
|
||||
return `${months[parseInt(monthNum) - 1]} ${year}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className="space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<h1 className="text-2xl font-bold tracking-tight">Dashboard Financeiro</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Última atualização: {new Date().toLocaleDateString('pt-BR')}
|
||||
</p>
|
||||
<div className="space-y-1">
|
||||
<h1 className="text-2xl font-bold tracking-tight">Dashboard Financeiro</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Dados de: {formatMonthDisplay(selectedMonth)}
|
||||
</p>
|
||||
</div>
|
||||
<MonthFilter
|
||||
selectedMonth={selectedMonth}
|
||||
onMonthChange={setSelectedMonth}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
|
||||
@ -1,13 +1,22 @@
|
||||
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import Layout from '@/components/layout/Layout';
|
||||
import TransactionsTable from '@/components/dashboard/TransactionsTable';
|
||||
import { useTransactions } from '@/hooks/useTransactions';
|
||||
import { TransactionHeader } from '@/components/transacoes/TransactionHeader';
|
||||
import { TransactionSummaryCards } from '@/components/transacoes/TransactionSummaryCards';
|
||||
import { TransactionDialogs } from '@/components/transacoes/TransactionDialogs';
|
||||
import { MonthFilter } from '@/components/filters/MonthFilter';
|
||||
|
||||
const TransacoesPage = () => {
|
||||
// Função para obter o mês atual no formato YYYY-MM
|
||||
const getCurrentMonth = () => {
|
||||
const now = new Date();
|
||||
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
const [selectedMonth, setSelectedMonth] = useState<string>(getCurrentMonth());
|
||||
|
||||
const {
|
||||
transactions,
|
||||
isLoading,
|
||||
@ -29,11 +38,34 @@ const TransacoesPage = () => {
|
||||
handleOpenDialog,
|
||||
handleOpenCartaoCreditoDialog,
|
||||
loadTransactions
|
||||
} = useTransactions();
|
||||
} = useTransactions({ monthFilter: selectedMonth });
|
||||
|
||||
// Função para formatar o mês para exibição
|
||||
const formatMonthDisplay = (month: string) => {
|
||||
const [year, monthNum] = month.split('-');
|
||||
const months = [
|
||||
'Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho',
|
||||
'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'
|
||||
];
|
||||
return `${months[parseInt(monthNum) - 1]} ${year}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className="space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="space-y-1">
|
||||
<h1 className="text-2xl font-bold tracking-tight">Transações</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Dados de: {formatMonthDisplay(selectedMonth)}
|
||||
</p>
|
||||
</div>
|
||||
<MonthFilter
|
||||
selectedMonth={selectedMonth}
|
||||
onMonthChange={setSelectedMonth}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<TransactionHeader
|
||||
onOpenDialog={handleOpenDialog}
|
||||
onOpenCartaoCreditoDialog={handleOpenCartaoCreditoDialog}
|
||||
|
||||
@ -4,11 +4,12 @@ import { CategorySummary, MonthlyData } from "@/types/financialTypes";
|
||||
import { getUserEmail, getUserGroups } from "./baseService";
|
||||
|
||||
/**
|
||||
* Get transaction summary (totals for incomes and expenses)
|
||||
* Get transaction summary (totals for incomes and expenses) with optional month filter
|
||||
* @param monthFilter - Optional month filter in format 'YYYY-MM'
|
||||
* @returns Promise with summary data
|
||||
*/
|
||||
export async function getTransactionSummary() {
|
||||
console.log("Buscando resumo das transações...");
|
||||
export async function getTransactionSummary(monthFilter?: string) {
|
||||
console.log("Buscando resumo das transações...", monthFilter ? `Filtro do mês: ${monthFilter}` : "Sem filtro de mês");
|
||||
|
||||
const normalizedEmail = getUserEmail();
|
||||
|
||||
@ -20,12 +21,26 @@ export async function getTransactionSummary() {
|
||||
// Get user groups by email
|
||||
const groupIds = await getUserGroups(normalizedEmail);
|
||||
|
||||
// Fetch transaction summary with enhanced filter based on email (login) or group_id
|
||||
const { data, error } = await supabase
|
||||
// Build the query with month filter if provided
|
||||
let query = supabase
|
||||
.from('transacoes')
|
||||
.select('tipo, valor')
|
||||
.or(`login.eq.${normalizedEmail},${groupIds.length > 0 ? `grupo_id.in.(${groupIds.map(id => `"${id}"`).join(',')})` : ''}`);
|
||||
|
||||
// Apply month filter if provided
|
||||
if (monthFilter) {
|
||||
const startDate = `${monthFilter}-01`;
|
||||
const year = parseInt(monthFilter.split('-')[0]);
|
||||
const month = parseInt(monthFilter.split('-')[1]);
|
||||
const endDate = new Date(year, month, 0).toISOString().split('T')[0]; // Last day of the month
|
||||
|
||||
query = query
|
||||
.gte('quando', startDate)
|
||||
.lte('quando', `${endDate}T23:59:59.999Z`);
|
||||
}
|
||||
|
||||
const { data, error } = await query;
|
||||
|
||||
if (error) {
|
||||
console.error('Erro ao buscar resumo das transações:', error);
|
||||
throw new Error('Não foi possível carregar o resumo das transações');
|
||||
@ -56,12 +71,13 @@ export async function getTransactionSummary() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get category summary for transactions
|
||||
* Get category summary for transactions with optional month filter
|
||||
* @param tipoFiltro Filter by transaction type ('receita', 'despesa', or 'all')
|
||||
* @param monthFilter - Optional month filter in format 'YYYY-MM'
|
||||
* @returns Promise with category summary data
|
||||
*/
|
||||
export async function getCategorySummary(tipoFiltro: string = 'despesa'): Promise<CategorySummary[]> {
|
||||
console.log(`Buscando resumo de categorias para tipo: ${tipoFiltro}`);
|
||||
export async function getCategorySummary(tipoFiltro: string = 'despesa', monthFilter?: string): Promise<CategorySummary[]> {
|
||||
console.log(`Buscando resumo de categorias para tipo: ${tipoFiltro}`, monthFilter ? `Filtro do mês: ${monthFilter}` : "Sem filtro de mês");
|
||||
|
||||
const normalizedEmail = getUserEmail();
|
||||
|
||||
@ -73,12 +89,26 @@ export async function getCategorySummary(tipoFiltro: string = 'despesa'): Promis
|
||||
// Get user groups by email
|
||||
const groupIds = await getUserGroups(normalizedEmail);
|
||||
|
||||
// Fetch category summary with enhanced filter based on email (login) or group_id
|
||||
const { data, error } = await supabase
|
||||
// Build the query with month filter if provided
|
||||
let query = supabase
|
||||
.from('transacoes')
|
||||
.select('categoria, valor, tipo')
|
||||
.or(`login.eq.${normalizedEmail},${groupIds.length > 0 ? `grupo_id.in.(${groupIds.map(id => `"${id}"`).join(',')})` : ''}`);
|
||||
|
||||
// Apply month filter if provided
|
||||
if (monthFilter) {
|
||||
const startDate = `${monthFilter}-01`;
|
||||
const year = parseInt(monthFilter.split('-')[0]);
|
||||
const month = parseInt(monthFilter.split('-')[1]);
|
||||
const endDate = new Date(year, month, 0).toISOString().split('T')[0]; // Last day of the month
|
||||
|
||||
query = query
|
||||
.gte('quando', startDate)
|
||||
.lte('quando', `${endDate}T23:59:59.999Z`);
|
||||
}
|
||||
|
||||
const { data, error } = await query;
|
||||
|
||||
if (error) {
|
||||
console.error('Erro ao buscar resumo de categorias:', error);
|
||||
throw new Error('Não foi possível carregar o resumo por categoria');
|
||||
|
||||
@ -4,11 +4,12 @@ import { Transaction } from "@/types/financialTypes";
|
||||
import { getUserEmail, getUserGroups } from "./baseService";
|
||||
|
||||
/**
|
||||
* Fetch all transactions for the current user
|
||||
* Fetch all transactions for the current user with optional month filter
|
||||
* @param monthFilter - Optional month filter in format 'YYYY-MM'
|
||||
* @returns Promise with array of transactions
|
||||
*/
|
||||
export async function getTransacoes(): Promise<Transaction[]> {
|
||||
console.log("Buscando transações do Supabase...");
|
||||
export async function getTransacoes(monthFilter?: string): Promise<Transaction[]> {
|
||||
console.log("Buscando transações do Supabase...", monthFilter ? `Filtro do mês: ${monthFilter}` : "Sem filtro de mês");
|
||||
|
||||
// Get the user's email from localStorage
|
||||
const normalizedEmail = getUserEmail();
|
||||
@ -24,13 +25,29 @@ export async function getTransacoes(): Promise<Transaction[]> {
|
||||
const groupIds = await getUserGroups(normalizedEmail);
|
||||
console.log(`Encontrados ${groupIds.length} grupos vinculados ao usuário:`, groupIds);
|
||||
|
||||
// Fetch transactions with filter based on email (login) or group_id
|
||||
const { data, error } = await supabase
|
||||
// Build the query with month filter if provided
|
||||
let query = supabase
|
||||
.from('transacoes')
|
||||
.select('*')
|
||||
.or(`login.eq.${normalizedEmail},${groupIds.length > 0 ? `grupo_id.in.(${groupIds.map(id => `"${id}"`).join(',')})` : ''}`)
|
||||
.order('quando', { ascending: false });
|
||||
|
||||
// Apply month filter if provided
|
||||
if (monthFilter) {
|
||||
const startDate = `${monthFilter}-01`;
|
||||
const year = parseInt(monthFilter.split('-')[0]);
|
||||
const month = parseInt(monthFilter.split('-')[1]);
|
||||
const endDate = new Date(year, month, 0).toISOString().split('T')[0]; // Last day of the month
|
||||
|
||||
query = query
|
||||
.gte('quando', startDate)
|
||||
.lte('quando', `${endDate}T23:59:59.999Z`);
|
||||
|
||||
console.log(`Aplicando filtro de mês: ${startDate} até ${endDate}`);
|
||||
}
|
||||
|
||||
const { data, error } = await query;
|
||||
|
||||
if (error) {
|
||||
console.error('Erro ao buscar transações:', error);
|
||||
throw new Error('Não foi possível carregar as transações');
|
||||
|
||||
Loading…
Reference in New Issue
Block a user