diff --git a/src/App.tsx b/src/App.tsx index 7357562..dd8de00 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,6 +5,9 @@ import { TooltipProvider } from "@/components/ui/tooltip"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { BrowserRouter, Routes, Route } from "react-router-dom"; import Index from "./pages/Index"; +import Transacoes from "./pages/Transacoes"; +import Categorias from "./pages/Categorias"; +import Calendario from "./pages/Calendario"; import NotFound from "./pages/NotFound"; const queryClient = new QueryClient({ @@ -24,6 +27,9 @@ const App = () => ( } /> + } /> + } /> + } /> {/* Adicione novas rotas acima desta linha */} } /> diff --git a/src/components/dashboard/TransactionsTable.tsx b/src/components/dashboard/TransactionsTable.tsx index 35bdeda..c8cc67d 100644 --- a/src/components/dashboard/TransactionsTable.tsx +++ b/src/components/dashboard/TransactionsTable.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { format } from 'date-fns'; -import { ChevronDown, Search } from 'lucide-react'; +import { ChevronDown, Search, ChevronLeft, ChevronRight } from 'lucide-react'; import { Transaction } from '@/types/financialTypes'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; @@ -24,12 +24,19 @@ import { cn } from '@/lib/utils'; interface TransactionsTableProps { transactions: Transaction[]; isLoading?: boolean; + showPagination?: boolean; } -const TransactionsTable = ({ transactions, isLoading = false }: TransactionsTableProps) => { +const TransactionsTable = ({ + transactions, + isLoading = false, + showPagination = false +}: TransactionsTableProps) => { const [searchQuery, setSearchQuery] = useState(''); const [sortColumn, setSortColumn] = useState('quando'); const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc'); + const [currentPage, setCurrentPage] = useState(1); + const itemsPerPage = showPagination ? 10 : 5; const handleSort = (column: string) => { if (sortColumn === column) { @@ -43,9 +50,9 @@ const TransactionsTable = ({ transactions, isLoading = false }: TransactionsTabl const filteredTransactions = transactions.filter((transaction) => { const query = searchQuery.toLowerCase(); return ( - transaction.estabelecimento.toLowerCase().includes(query) || - transaction.detalhes.toLowerCase().includes(query) || - transaction.categoria.toLowerCase().includes(query) + transaction.estabelecimento?.toLowerCase().includes(query) || + transaction.detalhes?.toLowerCase().includes(query) || + transaction.categoria?.toLowerCase().includes(query) ); }); @@ -68,6 +75,13 @@ const TransactionsTable = ({ transactions, isLoading = false }: TransactionsTabl : bValue.localeCompare(aValue); }); + // Paginação + const totalPages = Math.ceil(sortedTransactions.length / itemsPerPage); + const paginatedTransactions = sortedTransactions.slice( + (currentPage - 1) * itemsPerPage, + currentPage * itemsPerPage + ); + const formatCurrency = (value: number) => { return new Intl.NumberFormat('pt-BR', { style: 'currency', @@ -154,14 +168,14 @@ const TransactionsTable = ({ transactions, isLoading = false }: TransactionsTabl ))} )) - ) : sortedTransactions.length === 0 ? ( + ) : paginatedTransactions.length === 0 ? ( {searchQuery ? 'Nenhuma transação encontrada' : 'Não há transações disponíveis'} ) : ( - sortedTransactions.slice(0, 5).map((transaction) => ( + paginatedTransactions.map((transaction) => ( {format(new Date(transaction.quando), 'dd/MM/yyyy')} @@ -186,11 +200,50 @@ const TransactionsTable = ({ transactions, isLoading = false }: TransactionsTabl -
- -
+ {showPagination && totalPages > 0 && ( +
+
+ Mostrando {Math.min(paginatedTransactions.length, itemsPerPage)} de{" "} + {filteredTransactions.length} transações +
+
+ +
+ Página {currentPage} de{" "} + {totalPages} +
+ +
+
+ )} + + {!showPagination && filteredTransactions.length > itemsPerPage && ( +
+ +
+ )} ); }; diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx index f000e3e..ef57637 100644 --- a/src/components/ui/badge.tsx +++ b/src/components/ui/badge.tsx @@ -1,20 +1,23 @@ + import * as React from "react" import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" const badgeVariants = cva( - "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", { variants: { variant: { default: - "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", + "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", destructive: - "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", + "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", outline: "text-foreground", + success: + "border-transparent bg-finance-green text-white shadow hover:bg-finance-green/80", }, }, defaultVariants: { diff --git a/src/components/ui/progress.tsx b/src/components/ui/progress.tsx index 105fb65..f381697 100644 --- a/src/components/ui/progress.tsx +++ b/src/components/ui/progress.tsx @@ -1,3 +1,4 @@ + import * as React from "react" import * as ProgressPrimitive from "@radix-ui/react-progress" @@ -5,19 +6,24 @@ import { cn } from "@/lib/utils" const Progress = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, value, ...props }, ref) => ( + React.ComponentPropsWithoutRef & { + indicatorColor?: string; + } +>(({ className, value, indicatorColor, ...props }, ref) => ( )) diff --git a/src/pages/Calendario.tsx b/src/pages/Calendario.tsx new file mode 100644 index 0000000..d8a68ec --- /dev/null +++ b/src/pages/Calendario.tsx @@ -0,0 +1,149 @@ + +import { useState, useEffect } from 'react'; +import Layout from '@/components/layout/Layout'; +import { Card, CardContent } from '@/components/ui/card'; +import { Calendar } from "@/components/ui/calendar"; +import { getTransacoes } from '@/services/transacaoService'; +import { Transaction } from '@/types/financialTypes'; +import { useToast } from "@/components/ui/use-toast"; +import { Badge } from "@/components/ui/badge"; +import { isSameDay, format } from 'date-fns'; +import { ptBR } from 'date-fns/locale'; + +const CalendarioPage = () => { + const [transactions, setTransactions] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [selectedDate, setSelectedDate] = useState(new Date()); + const [filteredTransactions, setFilteredTransactions] = useState([]); + const { toast } = useToast(); + + useEffect(() => { + async function loadTransactions() { + try { + setIsLoading(true); + console.log("Carregando transações para o calendário..."); + const data = await getTransacoes(); + console.log(`${data.length} transações carregadas para o calendário`); + setTransactions(data); + } catch (error) { + console.error("Erro ao carregar transações:", error); + toast({ + title: "Erro ao carregar transações", + description: "Não foi possível obter os dados do Supabase", + variant: "destructive" + }); + } finally { + setIsLoading(false); + } + } + + loadTransactions(); + }, [toast]); + + useEffect(() => { + if (selectedDate && transactions.length > 0) { + const filtered = transactions.filter(transaction => + isSameDay(new Date(transaction.quando), selectedDate) + ); + setFilteredTransactions(filtered); + } else { + setFilteredTransactions([]); + } + }, [selectedDate, transactions]); + + const formatCurrency = (value: number) => { + return new Intl.NumberFormat('pt-BR', { + style: 'currency', + currency: 'BRL', + }).format(value); + }; + + // Função para verificar se um dia tem transações + const hasDayTransaction = (date: Date) => { + return transactions.some(transaction => + isSameDay(new Date(transaction.quando), date) + ); + }; + + return ( + +
+
+

Calendário de Transações

+
+ +
+ + + hasDayTransaction(date), + }} + modifiersStyles={{ + hasTransaction: { + fontWeight: "bold", + textDecoration: "underline", + backgroundColor: "rgba(16, 185, 129, 0.1)" + } + }} + /> +
+ Dias com transações estão destacados +
+
+
+ + + +

+ {selectedDate ? format(selectedDate, "dd 'de' MMMM 'de' yyyy", { locale: ptBR }) : "Selecione uma data"} +

+ + {isLoading ? ( +
+ {[1, 2, 3].map((i) => ( +
+
+
+
+ ))} +
+ ) : filteredTransactions.length === 0 ? ( +
+ Nenhuma transação nesta data +
+ ) : ( +
+ {filteredTransactions.map((transaction) => ( +
+
+
{transaction.estabelecimento}
+ + {transaction.tipo === 'entrada' ? 'Receita' : 'Despesa'} + +
+
{transaction.detalhes}
+
+ {transaction.categoria} + + {transaction.tipo === 'entrada' ? '+' : '-'}{formatCurrency(Math.abs(transaction.valor))} + +
+
+ ))} +
+ )} +
+
+
+
+
+ ); +}; + +export default CalendarioPage; diff --git a/src/pages/Categorias.tsx b/src/pages/Categorias.tsx new file mode 100644 index 0000000..100c068 --- /dev/null +++ b/src/pages/Categorias.tsx @@ -0,0 +1,109 @@ + +import { useState, useEffect } from 'react'; +import Layout from '@/components/layout/Layout'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { getCategorySummary } from '@/services/transacaoService'; +import { CategorySummary } from '@/types/financialTypes'; +import { useToast } from "@/components/ui/use-toast"; +import { Progress } from "@/components/ui/progress"; + +const CategoriasPage = () => { + const [categories, setCategories] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const { toast } = useToast(); + + useEffect(() => { + async function loadCategories() { + try { + setIsLoading(true); + console.log("Carregando dados de categorias..."); + const data = await getCategorySummary(); + console.log(`${data.length} categorias carregadas com sucesso`); + setCategories(data); + } catch (error) { + console.error("Erro ao carregar categorias:", error); + toast({ + title: "Erro ao carregar categorias", + description: "Não foi possível obter os dados do Supabase", + variant: "destructive" + }); + } finally { + setIsLoading(false); + } + } + + loadCategories(); + }, [toast]); + + const formatCurrency = (value: number) => { + return new Intl.NumberFormat('pt-BR', { + style: 'currency', + currency: 'BRL', + }).format(value); + }; + + return ( + +
+
+

Categorias de Despesas

+
+ + {isLoading ? ( +
+ {[1, 2, 3, 4, 5, 6].map((i) => ( + + +
+
+ +
+
+
+
+ ))} +
+ ) : categories.length === 0 ? ( + + +

Nenhuma categoria encontrada

+
+
+ ) : ( +
+ {categories.map((category) => ( + + + + + {category.categoria} + + + +
+ {formatCurrency(category.valor)} +
+
+ +
+ {(category.percentage * 100).toFixed(1)}% do total de despesas +
+
+
+
+ ))} +
+ )} +
+
+ ); +}; + +export default CategoriasPage; diff --git a/src/pages/Transacoes.tsx b/src/pages/Transacoes.tsx new file mode 100644 index 0000000..487f3f3 --- /dev/null +++ b/src/pages/Transacoes.tsx @@ -0,0 +1,60 @@ + +import { useState, useEffect } from 'react'; +import Layout from '@/components/layout/Layout'; +import TransactionsTable from '@/components/dashboard/TransactionsTable'; +import { Transaction } from '@/types/financialTypes'; +import { useToast } from "@/components/ui/use-toast"; +import { getTransacoes } from '@/services/transacaoService'; +import { Button } from '@/components/ui/button'; +import { PlusCircle } from 'lucide-react'; + +const TransacoesPage = () => { + const [transactions, setTransactions] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const { toast } = useToast(); + + useEffect(() => { + async function loadTransactions() { + try { + setIsLoading(true); + console.log("Carregando todas as transações..."); + const data = await getTransacoes(); + console.log(`${data.length} transações carregadas com sucesso`); + setTransactions(data); + } catch (error) { + console.error("Erro ao carregar transações:", error); + toast({ + title: "Erro ao carregar transações", + description: "Não foi possível obter os dados do Supabase", + variant: "destructive" + }); + } finally { + setIsLoading(false); + } + } + + loadTransactions(); + }, [toast]); + + return ( + +
+
+

Todas as Transações

+ +
+ + +
+
+ ); +}; + +export default TransacoesPage;