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:
parent
22ce38db82
commit
f315af5ed7
@ -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>
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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: {
|
||||
|
||||
@ -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
149
src/pages/Calendario.tsx
Normal 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
109
src/pages/Categorias.tsx
Normal 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
60
src/pages/Transacoes.tsx
Normal 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;
|
||||
Loading…
Reference in New Issue
Block a user