Fix: Activate and connect menu items

Connects the "Transações", "Categorias", and "Calendário" menu items to their respective functionalities. Ensures data from Supabase is displayed in these sections.
This commit is contained in:
gpt-engineer-app[bot] 2025-05-17 23:44:35 +00:00
parent 22ce38db82
commit f315af5ed7
7 changed files with 406 additions and 20 deletions

View File

@ -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 = () => (
<BrowserRouter>
<Routes>
<Route path="/" element={<Index />} />
<Route path="/transacoes" element={<Transacoes />} />
<Route path="/categorias" element={<Categorias />} />
<Route path="/calendario" element={<Calendario />} />
{/* Adicione novas rotas acima desta linha */}
<Route path="*" element={<NotFound />} />
</Routes>

View File

@ -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<string>('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
))}
</TableRow>
))
) : sortedTransactions.length === 0 ? (
) : paginatedTransactions.length === 0 ? (
<TableRow>
<TableCell colSpan={5} className="text-center py-8 text-muted-foreground">
{searchQuery ? 'Nenhuma transação encontrada' : 'Não há transações disponíveis'}
</TableCell>
</TableRow>
) : (
sortedTransactions.slice(0, 5).map((transaction) => (
paginatedTransactions.map((transaction) => (
<TableRow key={transaction.id}>
<TableCell className="font-medium">
{format(new Date(transaction.quando), 'dd/MM/yyyy')}
@ -186,11 +200,50 @@ const TransactionsTable = ({ transactions, isLoading = false }: TransactionsTabl
</Table>
</div>
<div className="flex justify-center pt-2">
<Button variant="outline" size="sm">
Ver todas as transações
</Button>
</div>
{showPagination && totalPages > 0 && (
<div className="flex items-center justify-between pt-2">
<div className="text-sm text-muted-foreground">
Mostrando <span className="font-medium">{Math.min(paginatedTransactions.length, itemsPerPage)}</span> de{" "}
<span className="font-medium">{filteredTransactions.length}</span> transações
</div>
<div className="flex items-center space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => setCurrentPage(currentPage - 1)}
disabled={currentPage === 1}
>
<ChevronLeft className="h-4 w-4" />
Anterior
</Button>
<div className="text-sm">
Página <span className="font-medium">{currentPage}</span> de{" "}
<span className="font-medium">{totalPages}</span>
</div>
<Button
variant="outline"
size="sm"
onClick={() => setCurrentPage(currentPage + 1)}
disabled={currentPage === totalPages}
>
Próxima
<ChevronRight className="h-4 w-4" />
</Button>
</div>
</div>
)}
{!showPagination && filteredTransactions.length > itemsPerPage && (
<div className="flex justify-center pt-2">
<Button
variant="outline"
size="sm"
onClick={() => window.location.href = '/transacoes'}
>
Ver todas as transações
</Button>
</div>
)}
</div>
);
};

View File

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

View File

@ -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<typeof ProgressPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
>(({ className, value, ...props }, ref) => (
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root> & {
indicatorColor?: string;
}
>(({ className, value, indicatorColor, ...props }, ref) => (
<ProgressPrimitive.Root
ref={ref}
className={cn(
"relative h-4 w-full overflow-hidden rounded-full bg-secondary",
"relative h-2 w-full overflow-hidden rounded-full bg-muted",
className
)}
{...props}
>
<ProgressPrimitive.Indicator
className="h-full w-full flex-1 bg-primary transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
className="h-full w-full flex-1 transition-all"
style={{
transform: `translateX(-${100 - (value || 0)}%)`,
backgroundColor: indicatorColor || 'hsl(var(--primary))'
}}
/>
</ProgressPrimitive.Root>
))

149
src/pages/Calendario.tsx Normal file
View File

@ -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<Transaction[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [selectedDate, setSelectedDate] = useState<Date | undefined>(new Date());
const [filteredTransactions, setFilteredTransactions] = useState<Transaction[]>([]);
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 (
<Layout>
<div className="space-y-6">
<div className="flex justify-between items-center">
<h1 className="text-2xl font-bold tracking-tight">Calendário de Transações</h1>
</div>
<div className="grid md:grid-cols-5 gap-6">
<Card className="md:col-span-2">
<CardContent className="pt-6">
<Calendar
mode="single"
selected={selectedDate}
onSelect={setSelectedDate}
locale={ptBR}
className="max-w-full pointer-events-auto"
modifiers={{
hasTransaction: (date) => hasDayTransaction(date),
}}
modifiersStyles={{
hasTransaction: {
fontWeight: "bold",
textDecoration: "underline",
backgroundColor: "rgba(16, 185, 129, 0.1)"
}
}}
/>
<div className="mt-4 text-sm text-muted-foreground text-center">
Dias com transações estão destacados
</div>
</CardContent>
</Card>
<Card className="md:col-span-3">
<CardContent className="p-6">
<h2 className="text-lg font-semibold mb-4">
{selectedDate ? format(selectedDate, "dd 'de' MMMM 'de' yyyy", { locale: ptBR }) : "Selecione uma data"}
</h2>
{isLoading ? (
<div className="space-y-3">
{[1, 2, 3].map((i) => (
<div key={i} className="p-4 border rounded-md animate-pulse">
<div className="h-4 bg-muted rounded w-1/4 mb-2"></div>
<div className="h-6 bg-muted rounded w-1/2"></div>
</div>
))}
</div>
) : filteredTransactions.length === 0 ? (
<div className="text-center py-10 text-muted-foreground">
Nenhuma transação nesta data
</div>
) : (
<div className="space-y-3">
{filteredTransactions.map((transaction) => (
<div key={transaction.id} className="p-4 border rounded-md hover:bg-muted/50 transition-colors">
<div className="flex justify-between items-center mb-1">
<div className="font-medium">{transaction.estabelecimento}</div>
<Badge variant={transaction.tipo === 'entrada' ? "success" : "destructive"}>
{transaction.tipo === 'entrada' ? 'Receita' : 'Despesa'}
</Badge>
</div>
<div className="text-sm text-muted-foreground mb-2">{transaction.detalhes}</div>
<div className="flex justify-between">
<Badge variant="outline">{transaction.categoria}</Badge>
<span className={transaction.tipo === 'entrada' ? "text-finance-green font-medium" : "text-finance-red font-medium"}>
{transaction.tipo === 'entrada' ? '+' : '-'}{formatCurrency(Math.abs(transaction.valor))}
</span>
</div>
</div>
))}
</div>
)}
</CardContent>
</Card>
</div>
</div>
</Layout>
);
};
export default CalendarioPage;

109
src/pages/Categorias.tsx Normal file
View File

@ -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<CategorySummary[]>([]);
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 (
<Layout>
<div className="space-y-6">
<div className="flex justify-between items-center">
<h1 className="text-2xl font-bold tracking-tight">Categorias de Despesas</h1>
</div>
{isLoading ? (
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{[1, 2, 3, 4, 5, 6].map((i) => (
<Card key={i} className="animate-pulse">
<CardHeader className="pb-2">
<div className="h-5 bg-muted rounded w-1/2"></div>
</CardHeader>
<CardContent>
<div className="h-4 bg-muted rounded w-1/4 mb-3"></div>
<div className="h-2 bg-muted rounded"></div>
</CardContent>
</Card>
))}
</div>
) : categories.length === 0 ? (
<Card>
<CardContent className="py-10 text-center">
<p className="text-muted-foreground">Nenhuma categoria encontrada</p>
</CardContent>
</Card>
) : (
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{categories.map((category) => (
<Card key={category.categoria}>
<CardHeader className="pb-2">
<CardTitle className="text-base flex items-center">
<span
className="w-3 h-3 rounded-full mr-2"
style={{ backgroundColor: category.color }}
/>
{category.categoria}
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold mb-2">
{formatCurrency(category.valor)}
</div>
<div className="space-y-2">
<Progress
value={category.percentage * 100}
className="h-2"
indicatorColor={category.color}
/>
<div className="text-xs text-muted-foreground">
{(category.percentage * 100).toFixed(1)}% do total de despesas
</div>
</div>
</CardContent>
</Card>
))}
</div>
)}
</div>
</Layout>
);
};
export default CategoriasPage;

60
src/pages/Transacoes.tsx Normal file
View File

@ -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<Transaction[]>([]);
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 (
<Layout>
<div className="space-y-6">
<div className="flex justify-between items-center">
<h1 className="text-2xl font-bold tracking-tight">Todas as Transações</h1>
<Button className="flex items-center gap-2">
<PlusCircle className="h-4 w-4" />
Nova Transação
</Button>
</div>
<TransactionsTable
transactions={transactions}
isLoading={isLoading}
showPagination={true}
/>
</div>
</Layout>
);
};
export default TransacoesPage;