diff --git a/src/components/dashboard/CategoryChart.tsx b/src/components/dashboard/CategoryChart.tsx index fad4aa4..32b1c4f 100644 --- a/src/components/dashboard/CategoryChart.tsx +++ b/src/components/dashboard/CategoryChart.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import { CardSpotlight } from '@/components/ui/card-spotlight'; import { PieChart, Pie, Cell, ResponsiveContainer, Legend, Tooltip } from 'recharts'; import { CategorySummary } from '@/types/financialTypes'; @@ -52,57 +51,50 @@ const CategoryChart: React.FC = ({ categories, isLoading = f }; return ( - -
-
- 🧾 Gastos por Categoria +
+ {isLoading ? ( +
+
-
-
- {isLoading ? ( -
-
-
- ) : validCategories.length === 0 ? ( -
- Sem dados disponíveis - Verifique se existem transações do tipo 'despesa' cadastradas -
- ) : ( -
- - - - {validCategories.map((entry, index) => ( - - ))} - - - - - -
- )} -
- + ) : validCategories.length === 0 ? ( +
+ Sem dados disponíveis + Verifique se existem transações do tipo 'despesa' cadastradas +
+ ) : ( +
+ + + + {validCategories.map((entry, index) => ( + + ))} + + + + + +
+ )} +
); }; diff --git a/src/components/transacoes/TransactionSummaryCards.tsx b/src/components/transacoes/TransactionSummaryCards.tsx index 86e66b9..2506fea 100644 --- a/src/components/transacoes/TransactionSummaryCards.tsx +++ b/src/components/transacoes/TransactionSummaryCards.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import { CardSpotlight } from '@/components/ui/card-spotlight'; import { ArrowUp, ArrowDown, CreditCard } from 'lucide-react'; +import { SimpleCard } from '@/components/ui/simple-card'; interface TransactionSummaryCardsProps { totalReceitas: number; @@ -21,42 +21,42 @@ export const TransactionSummaryCards = ({ return (
- +
-
+
Ganhos do mês
-
+

{formatCurrency(totalReceitas)}

- + - +
-
+
Gastos do mês
-
+

{formatCurrency(totalDespesas)}

- + - +
-
+
Gastos em cartões
-
+

{formatCurrency(totalCartoes)}

@@ -65,7 +65,7 @@ export const TransactionSummaryCards = ({ {formatCurrency(totalGeral)}
- +
); }; diff --git a/src/components/ui/3d-card.tsx b/src/components/ui/3d-card.tsx new file mode 100644 index 0000000..f1b9d89 --- /dev/null +++ b/src/components/ui/3d-card.tsx @@ -0,0 +1,156 @@ + +"use client"; + +import { cn } from "@/lib/utils"; + +import React, { + createContext, + useState, + useContext, + useRef, + useEffect, +} from "react"; + +const MouseEnterContext = createContext< + [boolean, React.Dispatch>] | undefined +>(undefined); + +export const CardContainer = ({ + children, + className, + containerClassName, +}: { + children?: React.ReactNode; + className?: string; + containerClassName?: string; +}) => { + const containerRef = useRef(null); + const [isMouseEntered, setIsMouseEntered] = useState(false); + + const handleMouseMove = (e: React.MouseEvent) => { + if (!containerRef.current) return; + const { left, top, width, height } = + containerRef.current.getBoundingClientRect(); + const x = (e.clientX - left - width / 2) / 25; + const y = (e.clientY - top - height / 2) / 25; + containerRef.current.style.transform = `rotateY(${x}deg) rotateX(${y}deg)`; + }; + + const handleMouseEnter = (e: React.MouseEvent) => { + setIsMouseEntered(true); + if (!containerRef.current) return; + }; + + const handleMouseLeave = (e: React.MouseEvent) => { + if (!containerRef.current) return; + setIsMouseEntered(false); + containerRef.current.style.transform = `rotateY(0deg) rotateX(0deg)`; + }; + return ( + +
+
+ {children} +
+
+
+ ); +}; + +export const CardBody = ({ + children, + className, +}: { + children: React.ReactNode; + className?: string; +}) => { + return ( +
*]:[transform-style:preserve-3d]", + className + )} + > + {children} +
+ ); +}; + +export const CardItem = ({ + as: Tag = "div", + children, + className, + translateX = 0, + translateY = 0, + translateZ = 0, + rotateX = 0, + rotateY = 0, + rotateZ = 0, + ...rest +}: { + as?: React.ElementType; + children: React.ReactNode; + className?: string; + translateX?: number | string; + translateY?: number | string; + translateZ?: number | string; + rotateX?: number | string; + rotateY?: number | string; + rotateZ?: number | string; + [key: string]: any; +}) => { + const ref = useRef(null); + const [isMouseEntered] = useMouseEnter(); + + useEffect(() => { + handleAnimations(); + }, [isMouseEntered]); + + const handleAnimations = () => { + if (!ref.current) return; + if (isMouseEntered) { + ref.current.style.transform = `translateX(${translateX}px) translateY(${translateY}px) translateZ(${translateZ}px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) rotateZ(${rotateZ}deg)`; + } else { + ref.current.style.transform = `translateX(0px) translateY(0px) translateZ(0px) rotateX(0deg) rotateY(0deg) rotateZ(0deg)`; + } + }; + + return ( + + {children} + + ); +}; + +// Create a hook to use the context +export const useMouseEnter = () => { + const context = useContext(MouseEnterContext); + if (context === undefined) { + throw new Error("useMouseEnter must be used within a MouseEnterProvider"); + } + return context; +}; diff --git a/src/components/ui/simple-card.tsx b/src/components/ui/simple-card.tsx new file mode 100644 index 0000000..f51fda9 --- /dev/null +++ b/src/components/ui/simple-card.tsx @@ -0,0 +1,31 @@ + +import React from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { cn } from '@/lib/utils'; + +interface SimpleCardProps { + title?: string; + children: React.ReactNode; + className?: string; + headerClassName?: string; +} + +export const SimpleCard: React.FC = ({ + title, + children, + className, + headerClassName +}) => { + return ( + + {title && ( + + {title} + + )} + + {children} + + + ); +}; diff --git a/src/pages/Categorias.tsx b/src/pages/Categorias.tsx index dd575d9..6edf987 100644 --- a/src/pages/Categorias.tsx +++ b/src/pages/Categorias.tsx @@ -1,71 +1,50 @@ -import { useState, useEffect } from 'react'; -import { Card, CardContent } from '@/components/ui/card'; -import { getCategorySummary } from '@/services/transacao'; -import { CategorySummary } from '@/types/financialTypes'; -import { useToast } from "@/components/ui/use-toast"; -import { Progress } from "@/components/ui/progress"; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow -} from "@/components/ui/table"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; +import React, { useState, useEffect } from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Badge } from "@/components/ui/badge"; +import { Plus, Search, Edit, Trash2 } from 'lucide-react'; +import { useToast } from "@/hooks/use-toast"; +import { SimpleCard } from "@/components/ui/simple-card"; -const CategoriasPage = () => { - const [categories, setCategories] = useState([]); +interface Categoria { + id: string; + nome: string; + tipo: 'receita' | 'despesa'; + cor: string; + total?: number; +} + +const Categorias = () => { + const [categorias, setCategorias] = useState([]); + const [searchTerm, setSearchTerm] = useState(''); const [isLoading, setIsLoading] = useState(true); - const [tipoFiltro, setTipoFiltro] = useState('despesa'); const { toast } = useToast(); - // Atualizar o userId no localStorage para garantir consistência + // Mock data - replace with actual API calls useEffect(() => { - const storedUserId = localStorage.getItem('userId'); - const finDashUser = localStorage.getItem('finDashUser'); + const mockCategorias: Categoria[] = [ + { id: '1', nome: 'Alimentação', tipo: 'despesa', cor: '#ef4444', total: 850.50 }, + { id: '2', nome: 'Transporte', tipo: 'despesa', cor: '#f97316', total: 420.30 }, + { id: '3', nome: 'Lazer', tipo: 'despesa', cor: '#eab308', total: 300.00 }, + { id: '4', nome: 'Salário', tipo: 'receita', cor: '#22c55e', total: 5000.00 }, + { id: '5', nome: 'Freelance', tipo: 'receita', cor: '#10b981', total: 1200.00 }, + { id: '6', nome: 'Educação', tipo: 'despesa', cor: '#3b82f6', total: 450.00 }, + ]; - if (!storedUserId && finDashUser) { - localStorage.setItem('userId', finDashUser); - } else if (!storedUserId && !finDashUser) { - const defaultId = '9f267008-9128-4a2f-b730-de0a0b5602a9'; - localStorage.setItem('userId', defaultId); - } + setTimeout(() => { + setCategorias(mockCategorias); + setIsLoading(false); + }, 1000); }, []); - const loadCategories = async (tipo: string) => { - try { - setIsLoading(true); - console.log(`Carregando dados de categorias para ${tipo}...`); - const data = await getCategorySummary(tipo); - 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); - } - }; + const filteredCategorias = categorias.filter(categoria => + categoria.nome.toLowerCase().includes(searchTerm.toLowerCase()) + ); - useEffect(() => { - loadCategories(tipoFiltro); - }, [tipoFiltro, toast]); - - const handleTipoChange = (value: string) => { - setTipoFiltro(value); - }; + const receitas = filteredCategorias.filter(cat => cat.tipo === 'receita'); + const despesas = filteredCategorias.filter(cat => cat.tipo === 'despesa'); const formatCurrency = (value: number) => { return new Intl.NumberFormat('pt-BR', { @@ -73,139 +52,173 @@ const CategoriasPage = () => { currency: 'BRL', }).format(value); }; - - const totalGastos = categories.reduce((total, category) => total + category.valor, 0); - return ( -
-
-

Categorias

-
- Filtrar por: - + const handleEdit = (categoria: Categoria) => { + toast({ + title: "Editar categoria", + description: `Função de edição para ${categoria.nome} será implementada`, + }); + }; + + const handleDelete = (categoria: Categoria) => { + toast({ + title: "Excluir categoria", + description: `Função de exclusão para ${categoria.nome} será implementada`, + }); + }; + + if (isLoading) { + return ( +
+
+

Categorias

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

Nenhuma categoria encontrada

-

- {tipoFiltro === 'despesa' - ? 'Verifique se existem transações do tipo "despesa" cadastradas' - : tipoFiltro === 'receita' - ? 'Verifique se existem transações do tipo "receita" cadastradas' - : 'Verifique se existem transações cadastradas'} +

+ ); + } + + return ( +
+
+

Categorias

+ +
+ + +
+ + setSearchTerm(e.target.value)} + className="pl-10" + /> +
+
+ +
+ +
+ {receitas.length === 0 ? ( +

+ Nenhuma categoria de receita encontrada

-
- - - ) : ( - <> -
- {categories.map((category) => ( - - -
-
- - {category.categoria} + ) : ( + receitas.map((categoria) => ( +
+
+
+
+

{categoria.nome}

+

+ {categoria.total ? formatCurrency(categoria.total) : 'R$ 0,00'} +

-
-
- {formatCurrency(category.valor)} -
-
- -
- {(category.percentage * 100).toFixed(1)}% do total -
-
+
+ + {categoria.tipo} + + +
- - - ))} +
+ )) + )}
- - - -
-

Resumo de Categorias {tipoFiltro !== 'all' ? (tipoFiltro === 'despesa' ? '(Despesas)' : '(Receitas)') : ''}

-
-
- - - - Categoria - Valor - Percentual - - - - {categories.map((category) => ( - - - - {category.categoria} - - {formatCurrency(category.valor)} - {(category.percentage * 100).toFixed(1)}% - - ))} - - Total - {formatCurrency(totalGastos)} - 100% - - -
-
-
-
- - )} + + + +
+ {despesas.length === 0 ? ( +

+ Nenhuma categoria de despesa encontrada +

+ ) : ( + despesas.map((categoria) => ( +
+
+
+
+

{categoria.nome}

+

+ {categoria.total ? formatCurrency(categoria.total) : 'R$ 0,00'} +

+
+
+
+ + {categoria.tipo} + + + +
+
+ )) + )} +
+ +
); }; -export default CategoriasPage; +export default Categorias; diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index 38c1e20..0b12081 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -2,12 +2,13 @@ import { useState, useEffect } from 'react'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { useToast } from "@/hooks/use-toast"; -import { ArrowUpIcon, ArrowDownIcon, CreditCardIcon, Target } from "lucide-react"; +import { ArrowUpIcon, ArrowDownIcon, CreditCardIcon, Target, TrendingUpIcon, TrendingDownIcon } from "lucide-react"; import { getResumoFinanceiro, getCategorySummary } from "@/services/transacao"; import { ResumoFinanceiro, CategorySummary } from "@/types/financialTypes"; import TransactionsTable from "@/components/dashboard/TransactionsTable"; import { useTransactions } from "@/hooks/useTransactions"; import CategoryChart from "@/components/dashboard/CategoryChart"; +import { SimpleCard } from "@/components/ui/simple-card"; const Dashboard = () => { const [resumo, setResumo] = useState(null); @@ -86,6 +87,8 @@ const Dashboard = () => { ); } + const saldo = resumo ? resumo.totalReceitas - resumo.totalDespesas - (resumo.totalCartoes || 0) : 0; + return (
@@ -139,43 +142,67 @@ const Dashboard = () => {
= 0 - ? 'text-green-600' - : 'text-red-600' + saldo >= 0 ? 'text-green-600' : 'text-red-600' }`}> - {resumo ? formatCurrency(resumo.totalReceitas - resumo.totalDespesas - (resumo.totalCartoes || 0)) : 'R$ 0,00'} + {resumo ? formatCurrency(saldo) : 'R$ 0,00'}

Resultado do mês

-
- - - Despesas por Categoria - Distribuição dos seus gastos - - - - - +
+ {/* 1. Receitas vs Despesas */} + +
+
+
+ +
+
+

Receitas

+

+ {resumo ? formatCurrency(resumo.totalReceitas) : 'R$ 0,00'} +

+
+
+
+
+ +
+
+

Despesas

+

+ {resumo ? formatCurrency(resumo.totalDespesas + (resumo.totalCartoes || 0)) : 'R$ 0,00'} +

+
+
+
+
+
+ Resultado: + = 0 ? 'text-green-600' : 'text-red-600'}`}> + {resumo ? formatCurrency(saldo) : 'R$ 0,00'} + +
+
+
- - - Últimas Transações - Suas transações mais recentes - - - {}} - onDelete={() => {}} - /> - - + {/* 2. Gastos por Categoria */} + + + + + {/* 3. Últimas Transações */} + + {}} + onDelete={() => {}} + /> +
); diff --git a/src/pages/Landing.tsx b/src/pages/Landing.tsx index c73b52a..84ad93b 100644 --- a/src/pages/Landing.tsx +++ b/src/pages/Landing.tsx @@ -17,6 +17,7 @@ import { BarChart3, Calendar } from 'lucide-react'; +import { CardContainer, CardBody, CardItem } from '@/components/ui/3d-card'; const Landing = () => { const features = [ @@ -79,7 +80,7 @@ const Landing = () => { }, { step: "2", - title: "Visualize no Dashboard", + title: "Visualize no Dashboard", description: "Acompanhe seus gastos em tempo real com gráficos e relatórios automáticos", icon: , image: "/dashboard-screenshot.png" @@ -178,7 +179,7 @@ const Landing = () => {
- {/* Step 1 - WhatsApp Methods */} + {/* Step 1 - WhatsApp Methods with 3D Cards */}
{howItWorksSteps[0].step} @@ -188,29 +189,47 @@ const Landing = () => {
{howItWorksSteps[0].methods.map((method, index) => ( -
-
- {method.icon} -
-

{method.title}

-

{method.description}

-
- {`Exemplo { - console.error(`Failed to load image: ${method.image}`); - e.currentTarget.style.display = 'none'; - }} - /> -
-
+ + + + {method.icon} + + + {method.title} + + + {method.description} + + +
+ {`Exemplo { + console.error(`Failed to load image: ${method.image}`); + e.currentTarget.style.display = 'none'; + }} + /> +
+
+
+
))}
- {/* Step 2 - Dashboard */} + {/* Step 2 - Dashboard with 3D Card */}
{howItWorksSteps[1].step} @@ -219,23 +238,27 @@ const Landing = () => {

{howItWorksSteps[1].description}

-
-
- Dashboard do sistema { - console.error(`Failed to load image: ${howItWorksSteps[1].image}`); - e.currentTarget.style.display = 'none'; - }} - /> -
-
+ + + +
+ Dashboard do sistema { + console.error(`Failed to load image: ${howItWorksSteps[1].image}`); + e.currentTarget.style.display = 'none'; + }} + /> +
+
+
+
- {/* Step 3 - Calendar */} + {/* Step 3 - Calendar with 3D Card */}
{howItWorksSteps[2].step} @@ -244,19 +267,23 @@ const Landing = () => {

{howItWorksSteps[2].description}

-
-
- Calendário do sistema { - console.error(`Failed to load image: ${howItWorksSteps[2].image}`); - e.currentTarget.style.display = 'none'; - }} - /> -
-
+ + + +
+ Calendário do sistema { + console.error(`Failed to load image: ${howItWorksSteps[2].image}`); + e.currentTarget.style.display = 'none'; + }} + /> +
+
+
+
diff --git a/src/pages/Transacoes.tsx b/src/pages/Transacoes.tsx index efcad2d..0167a39 100644 --- a/src/pages/Transacoes.tsx +++ b/src/pages/Transacoes.tsx @@ -1,24 +1,12 @@ -import React, { useState } from 'react'; -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'; -import { useAccessControl } from '@/hooks/useAccessControl'; - -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(getCurrentMonth()); - - const access = useAccessControl(); +import { TransactionHeader } from '@/components/transacoes/TransactionHeader'; +import TransactionsTable from '@/components/dashboard/TransactionsTable'; +import { SimpleCard } from "@/components/ui/simple-card"; +const Transacoes = () => { const { transactions, isLoading, @@ -40,60 +28,15 @@ const TransacoesPage = () => { handleOpenDialog, handleOpenCartaoCreditoDialog, loadTransactions - } = 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}`; - }; + } = useTransactions(); return (
-
-
-

Transações

-

- Dados de: {formatMonthDisplay(selectedMonth)} -

- {/* Exibe mensagem para trial OU para admin-liberou */} - {!access.loading && access.podeAdicionarTransacao && ( - access.adminLiberou ? ( -
- {access.motivo} -
- ) : (access.diasRestantesTrial > 0 && ( -
- {`Você está em período gratuito. ${access.diasRestantesTrial} dia(s) restante(s) de teste.`} -
- )) - )} -
- -
- - {/* Mensagem de bloqueio */} - {!access.loading && !access.podeAdicionarTransacao && ( -
- Atenção: {access.motivo} -
- )} - - {/* Só passa as funções de adicionar se permitido */} {}} - onOpenCartaoCreditoDialog={access.podeAdicionarTransacao ? handleOpenCartaoCreditoDialog : () => {}} - disableAdicionar={!access.podeAdicionarTransacao} + onOpenDialog={handleOpenDialog} + onOpenCartaoCreditoDialog={handleOpenCartaoCreditoDialog} /> - {/* Resumo em Cards */} { formatCurrency={formatCurrency} /> - {/* Tabela completa de transações */} -
-

Todas as Transações

+ -
+ - {/* Diálogos */} - { ); }; -export default TransacoesPage; +export default Transacoes;