Refactor dashboard layout and card effects
- Reordered dashboard components. - Removed card spotlight effect from expense category card. - Applied hover effect from account alerts page to transaction and category cards. - Added 3D card effect to landing page images. - Added 3d card component.
This commit is contained in:
parent
2990ab9d30
commit
5c3280f64e
@ -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<CategoryChartProps> = ({ categories, isLoading = f
|
||||
};
|
||||
|
||||
return (
|
||||
<CardSpotlight className="dashboard-card h-full animate-fade-in bg-gradient-to-br from-orange-50 via-white to-indigo-50 dark:from-zinc-900 dark:via-zinc-800 dark:to-zinc-900">
|
||||
<div className="pb-2 bg-gradient-to-r from-indigo-50/60 to-orange-50/0 dark:from-zinc-900 dark:to-zinc-900 rounded-t-2xl -m-6 mb-4 p-6">
|
||||
<div className="text-lg font-bold tracking-tight text-zinc-700 dark:text-white flex items-center relative z-20">
|
||||
<span className="mr-2">🧾</span> Gastos por Categoria
|
||||
<div className="h-full animate-fade-in">
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center h-[260px]">
|
||||
<div className="h-32 w-32 rounded-full border-4 border-t-primary border-opacity-20 animate-spin" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative z-20">
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center h-[260px]">
|
||||
<div className="h-32 w-32 rounded-full border-4 border-t-primary border-opacity-20 animate-spin" />
|
||||
</div>
|
||||
) : validCategories.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center h-[260px] text-muted-foreground">
|
||||
<span>Sem dados disponíveis</span>
|
||||
<span className="text-xs mt-2">Verifique se existem transações do tipo 'despesa' cadastradas</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-[300px] relative">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<PieChart>
|
||||
<Pie
|
||||
data={validCategories}
|
||||
cx="50%"
|
||||
cy="45%"
|
||||
outerRadius={90}
|
||||
innerRadius={40}
|
||||
fill="#8884d8"
|
||||
dataKey="valor"
|
||||
nameKey="categoria"
|
||||
paddingAngle={2}
|
||||
strokeWidth={2}
|
||||
stroke="#fff"
|
||||
isAnimationActive={true}
|
||||
animationDuration={1300}
|
||||
>
|
||||
{validCategories.map((entry, index) => (
|
||||
<Cell
|
||||
key={`cell-${index}`}
|
||||
fill={entry.color}
|
||||
style={{ filter: 'drop-shadow(0px 2px 3px rgba(0,0,0,0.08))' }}
|
||||
/>
|
||||
))}
|
||||
</Pie>
|
||||
<Tooltip content={renderCustomTooltip} />
|
||||
<Legend content={renderCustomLegend} layout="horizontal" verticalAlign="bottom" />
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardSpotlight>
|
||||
) : validCategories.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center h-[260px] text-muted-foreground">
|
||||
<span>Sem dados disponíveis</span>
|
||||
<span className="text-xs mt-2">Verifique se existem transações do tipo 'despesa' cadastradas</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-[300px] relative">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<PieChart>
|
||||
<Pie
|
||||
data={validCategories}
|
||||
cx="50%"
|
||||
cy="45%"
|
||||
outerRadius={90}
|
||||
innerRadius={40}
|
||||
fill="#8884d8"
|
||||
dataKey="valor"
|
||||
nameKey="categoria"
|
||||
paddingAngle={2}
|
||||
strokeWidth={2}
|
||||
stroke="#fff"
|
||||
isAnimationActive={true}
|
||||
animationDuration={1300}
|
||||
>
|
||||
{validCategories.map((entry, index) => (
|
||||
<Cell
|
||||
key={`cell-${index}`}
|
||||
fill={entry.color}
|
||||
style={{ filter: 'drop-shadow(0px 2px 3px rgba(0,0,0,0.08))' }}
|
||||
/>
|
||||
))}
|
||||
</Pie>
|
||||
<Tooltip content={renderCustomTooltip} />
|
||||
<Legend content={renderCustomLegend} layout="horizontal" verticalAlign="bottom" />
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -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 (
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
<CardSpotlight className="border-2 border-green-200">
|
||||
<SimpleCard className="border-green-200">
|
||||
<div className="pb-2 bg-green-50 rounded-t-lg -m-6 mb-4 p-6">
|
||||
<div className="text-green-700 flex items-center font-bold relative z-20">
|
||||
<div className="text-green-700 flex items-center font-bold">
|
||||
<ArrowUp className="h-5 w-5 mr-2 text-green-600" />
|
||||
Ganhos do mês
|
||||
</div>
|
||||
</div>
|
||||
<div className="pt-0 relative z-20">
|
||||
<div className="pt-0">
|
||||
<p className="text-2xl font-bold text-green-600">
|
||||
{formatCurrency(totalReceitas)}
|
||||
</p>
|
||||
</div>
|
||||
</CardSpotlight>
|
||||
</SimpleCard>
|
||||
|
||||
<CardSpotlight className="border-2 border-red-200">
|
||||
<SimpleCard className="border-red-200">
|
||||
<div className="pb-2 bg-red-50 rounded-t-lg -m-6 mb-4 p-6">
|
||||
<div className="text-red-700 flex items-center font-bold relative z-20">
|
||||
<div className="text-red-700 flex items-center font-bold">
|
||||
<ArrowDown className="h-5 w-5 mr-2 text-red-600" />
|
||||
Gastos do mês
|
||||
</div>
|
||||
</div>
|
||||
<div className="pt-0 relative z-20">
|
||||
<div className="pt-0">
|
||||
<p className="text-2xl font-bold text-red-600">
|
||||
{formatCurrency(totalDespesas)}
|
||||
</p>
|
||||
</div>
|
||||
</CardSpotlight>
|
||||
</SimpleCard>
|
||||
|
||||
<CardSpotlight className="border-2 border-blue-200">
|
||||
<SimpleCard className="border-blue-200">
|
||||
<div className="pb-2 bg-blue-50 rounded-t-lg -m-6 mb-4 p-6">
|
||||
<div className="text-blue-700 flex items-center font-bold relative z-20">
|
||||
<div className="text-blue-700 flex items-center font-bold">
|
||||
<CreditCard className="h-5 w-5 mr-2 text-blue-600" />
|
||||
Gastos em cartões
|
||||
</div>
|
||||
</div>
|
||||
<div className="pt-0 space-y-1 relative z-20">
|
||||
<div className="pt-0 space-y-1">
|
||||
<p className="text-2xl font-bold text-blue-600">
|
||||
{formatCurrency(totalCartoes)}
|
||||
</p>
|
||||
@ -65,7 +65,7 @@ export const TransactionSummaryCards = ({
|
||||
<span className="font-bold text-red-600">{formatCurrency(totalGeral)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardSpotlight>
|
||||
</SimpleCard>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
156
src/components/ui/3d-card.tsx
Normal file
156
src/components/ui/3d-card.tsx
Normal file
@ -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<React.SetStateAction<boolean>>] | undefined
|
||||
>(undefined);
|
||||
|
||||
export const CardContainer = ({
|
||||
children,
|
||||
className,
|
||||
containerClassName,
|
||||
}: {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
}) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [isMouseEntered, setIsMouseEntered] = useState(false);
|
||||
|
||||
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
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<HTMLDivElement>) => {
|
||||
setIsMouseEntered(true);
|
||||
if (!containerRef.current) return;
|
||||
};
|
||||
|
||||
const handleMouseLeave = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (!containerRef.current) return;
|
||||
setIsMouseEntered(false);
|
||||
containerRef.current.style.transform = `rotateY(0deg) rotateX(0deg)`;
|
||||
};
|
||||
return (
|
||||
<MouseEnterContext.Provider value={[isMouseEntered, setIsMouseEntered]}>
|
||||
<div
|
||||
className={cn(
|
||||
"py-20 flex items-center justify-center",
|
||||
containerClassName
|
||||
)}
|
||||
style={{
|
||||
perspective: "1000px",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
ref={containerRef}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
className={cn(
|
||||
"flex items-center justify-center relative transition-all duration-200 ease-linear",
|
||||
className
|
||||
)}
|
||||
style={{
|
||||
transformStyle: "preserve-3d",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</MouseEnterContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const CardBody = ({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"h-96 w-96 [transform-style:preserve-3d] [&>*]:[transform-style:preserve-3d]",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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<HTMLDivElement>(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 (
|
||||
<Tag
|
||||
ref={ref}
|
||||
className={cn("w-fit transition duration-200 ease-linear", className)}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</Tag>
|
||||
);
|
||||
};
|
||||
|
||||
// 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;
|
||||
};
|
||||
31
src/components/ui/simple-card.tsx
Normal file
31
src/components/ui/simple-card.tsx
Normal file
@ -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<SimpleCardProps> = ({
|
||||
title,
|
||||
children,
|
||||
className,
|
||||
headerClassName
|
||||
}) => {
|
||||
return (
|
||||
<Card className={cn("border-2 shadow-sm hover:shadow-md transition-shadow", className)}>
|
||||
{title && (
|
||||
<CardHeader className={cn("pb-3", headerClassName)}>
|
||||
<CardTitle className="text-lg font-semibold">{title}</CardTitle>
|
||||
</CardHeader>
|
||||
)}
|
||||
<CardContent className="pt-0">
|
||||
{children}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@ -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<CategorySummary[]>([]);
|
||||
interface Categoria {
|
||||
id: string;
|
||||
nome: string;
|
||||
tipo: 'receita' | 'despesa';
|
||||
cor: string;
|
||||
total?: number;
|
||||
}
|
||||
|
||||
const Categorias = () => {
|
||||
const [categorias, setCategorias] = useState<Categoria[]>([]);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [tipoFiltro, setTipoFiltro] = useState<string>('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 (
|
||||
<div className="space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<h1 className="text-2xl font-bold tracking-tight">Categorias</h1>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-muted-foreground">Filtrar por:</span>
|
||||
<Select value={tipoFiltro} onValueChange={handleTipoChange}>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue placeholder="Tipo de transação" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="despesa">Despesas</SelectItem>
|
||||
<SelectItem value="receita">Receitas</SelectItem>
|
||||
<SelectItem value="all">Todos</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
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 (
|
||||
<div className="space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<h1 className="text-2xl font-bold">Categorias</h1>
|
||||
<Button disabled>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Nova Categoria
|
||||
</Button>
|
||||
</div>
|
||||
</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">
|
||||
<CardContent className="p-6">
|
||||
<div className="pb-2">
|
||||
<div className="h-5 bg-muted rounded w-1/2"></div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="h-4 bg-muted rounded w-1/4 mb-3"></div>
|
||||
<div className="h-2 bg-muted rounded"></div>
|
||||
</div>
|
||||
<CardHeader>
|
||||
<div className="h-4 bg-muted rounded w-3/4"></div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="h-6 bg-muted rounded w-1/2 mb-2"></div>
|
||||
<div className="h-3 bg-muted rounded w-full"></div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
) : categories.length === 0 ? (
|
||||
<Card>
|
||||
<CardContent className="p-6">
|
||||
<div className="py-10 text-center">
|
||||
<p className="text-muted-foreground">Nenhuma categoria encontrada</p>
|
||||
<p className="mt-2 text-sm">
|
||||
{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'}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<h1 className="text-2xl font-bold">Categorias</h1>
|
||||
<Button>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Nova Categoria
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<SimpleCard className="border-gray-200">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Buscar categorias..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="pl-10"
|
||||
/>
|
||||
</div>
|
||||
</SimpleCard>
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
<SimpleCard title="Receitas" className="border-green-200">
|
||||
<div className="space-y-3">
|
||||
{receitas.length === 0 ? (
|
||||
<p className="text-muted-foreground text-center py-4">
|
||||
Nenhuma categoria de receita encontrada
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
<>
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{categories.map((category) => (
|
||||
<Card key={category.categoria}>
|
||||
<CardContent className="p-6">
|
||||
<div className="pb-2">
|
||||
<div className="text-base flex items-center font-semibold">
|
||||
<span
|
||||
className="w-3 h-3 rounded-full mr-2"
|
||||
style={{ backgroundColor: category.color }}
|
||||
/>
|
||||
{category.categoria}
|
||||
) : (
|
||||
receitas.map((categoria) => (
|
||||
<div
|
||||
key={categoria.id}
|
||||
className="flex items-center justify-between p-3 border rounded-lg hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div
|
||||
className="w-4 h-4 rounded-full"
|
||||
style={{ backgroundColor: categoria.cor }}
|
||||
/>
|
||||
<div>
|
||||
<p className="font-medium">{categoria.nome}</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{categoria.total ? formatCurrency(categoria.total) : 'R$ 0,00'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<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"
|
||||
style={{
|
||||
backgroundColor: 'rgba(0,0,0,0.1)',
|
||||
'--progress-background': category.color
|
||||
} as React.CSSProperties}
|
||||
/>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{(category.percentage * 100).toFixed(1)}% do total
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Badge variant="secondary" className="bg-green-100 text-green-800">
|
||||
{categoria.tipo}
|
||||
</Badge>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleEdit(categoria)}
|
||||
>
|
||||
<Edit className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleDelete(categoria)}
|
||||
className="text-red-600 hover:text-red-700"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Card className="mt-6">
|
||||
<CardContent className="p-6">
|
||||
<div>
|
||||
<h3 className="text-xl font-bold mb-4">Resumo de Categorias {tipoFiltro !== 'all' ? (tipoFiltro === 'despesa' ? '(Despesas)' : '(Receitas)') : ''}</h3>
|
||||
</div>
|
||||
<div>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Categoria</TableHead>
|
||||
<TableHead>Valor</TableHead>
|
||||
<TableHead>Percentual</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{categories.map((category) => (
|
||||
<TableRow key={category.categoria}>
|
||||
<TableCell className="font-medium flex items-center">
|
||||
<span
|
||||
className="w-3 h-3 rounded-full mr-2"
|
||||
style={{ backgroundColor: category.color }}
|
||||
/>
|
||||
{category.categoria}
|
||||
</TableCell>
|
||||
<TableCell>{formatCurrency(category.valor)}</TableCell>
|
||||
<TableCell>{(category.percentage * 100).toFixed(1)}%</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
<TableRow className="bg-muted/50">
|
||||
<TableCell className="font-bold">Total</TableCell>
|
||||
<TableCell className="font-bold">{formatCurrency(totalGastos)}</TableCell>
|
||||
<TableCell>100%</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
)}
|
||||
</SimpleCard>
|
||||
|
||||
<SimpleCard title="Despesas" className="border-red-200">
|
||||
<div className="space-y-3">
|
||||
{despesas.length === 0 ? (
|
||||
<p className="text-muted-foreground text-center py-4">
|
||||
Nenhuma categoria de despesa encontrada
|
||||
</p>
|
||||
) : (
|
||||
despesas.map((categoria) => (
|
||||
<div
|
||||
key={categoria.id}
|
||||
className="flex items-center justify-between p-3 border rounded-lg hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div
|
||||
className="w-4 h-4 rounded-full"
|
||||
style={{ backgroundColor: categoria.cor }}
|
||||
/>
|
||||
<div>
|
||||
<p className="font-medium">{categoria.nome}</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{categoria.total ? formatCurrency(categoria.total) : 'R$ 0,00'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Badge variant="secondary" className="bg-red-100 text-red-800">
|
||||
{categoria.tipo}
|
||||
</Badge>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleEdit(categoria)}
|
||||
>
|
||||
<Edit className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleDelete(categoria)}
|
||||
className="text-red-600 hover:text-red-700"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</SimpleCard>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CategoriasPage;
|
||||
export default Categorias;
|
||||
|
||||
@ -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<ResumoFinanceiro | null>(null);
|
||||
@ -86,6 +87,8 @@ const Dashboard = () => {
|
||||
);
|
||||
}
|
||||
|
||||
const saldo = resumo ? resumo.totalReceitas - resumo.totalDespesas - (resumo.totalCartoes || 0) : 0;
|
||||
|
||||
return (
|
||||
<div className="space-y-6" data-tour="dashboard-content">
|
||||
<div className="flex items-center justify-between">
|
||||
@ -139,43 +142,67 @@ const Dashboard = () => {
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className={`text-2xl font-bold ${
|
||||
resumo && (resumo.totalReceitas - resumo.totalDespesas - (resumo.totalCartoes || 0)) >= 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'}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">Resultado do mês</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
<Card className="md:col-span-1">
|
||||
<CardHeader>
|
||||
<CardTitle>Despesas por Categoria</CardTitle>
|
||||
<CardDescription>Distribuição dos seus gastos</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CategoryChart categories={categories} isLoading={isLoading} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="space-y-6">
|
||||
{/* 1. Receitas vs Despesas */}
|
||||
<SimpleCard title="Receitas vs Despesas" className="border-green-200">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="p-2 bg-green-100 rounded-lg">
|
||||
<TrendingUpIcon className="h-6 w-6 text-green-600" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">Receitas</p>
|
||||
<p className="text-xl font-bold text-green-600">
|
||||
{resumo ? formatCurrency(resumo.totalReceitas) : 'R$ 0,00'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="p-2 bg-red-100 rounded-lg">
|
||||
<TrendingDownIcon className="h-6 w-6 text-red-600" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">Despesas</p>
|
||||
<p className="text-xl font-bold text-red-600">
|
||||
{resumo ? formatCurrency(resumo.totalDespesas + (resumo.totalCartoes || 0)) : 'R$ 0,00'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 pt-4 border-t">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm font-medium">Resultado:</span>
|
||||
<span className={`text-lg font-bold ${saldo >= 0 ? 'text-green-600' : 'text-red-600'}`}>
|
||||
{resumo ? formatCurrency(saldo) : 'R$ 0,00'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</SimpleCard>
|
||||
|
||||
<Card className="md:col-span-1">
|
||||
<CardHeader>
|
||||
<CardTitle>Últimas Transações</CardTitle>
|
||||
<CardDescription>Suas transações mais recentes</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<TransactionsTable
|
||||
transactions={transactions.slice(0, 5)}
|
||||
isLoading={transactionsLoading}
|
||||
showPagination={false}
|
||||
onEdit={() => {}}
|
||||
onDelete={() => {}}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/* 2. Gastos por Categoria */}
|
||||
<SimpleCard title="Despesas por Categoria" className="border-orange-200">
|
||||
<CategoryChart categories={categories} isLoading={isLoading} />
|
||||
</SimpleCard>
|
||||
|
||||
{/* 3. Últimas Transações */}
|
||||
<SimpleCard title="Últimas Transações" className="border-blue-200">
|
||||
<TransactionsTable
|
||||
transactions={transactions.slice(0, 5)}
|
||||
isLoading={transactionsLoading}
|
||||
showPagination={false}
|
||||
onEdit={() => {}}
|
||||
onDelete={() => {}}
|
||||
/>
|
||||
</SimpleCard>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -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: <BarChart3 className="h-8 w-8" />,
|
||||
image: "/dashboard-screenshot.png"
|
||||
@ -178,7 +179,7 @@ const Landing = () => {
|
||||
</div>
|
||||
|
||||
<div className="max-w-6xl mx-auto space-y-20">
|
||||
{/* Step 1 - WhatsApp Methods */}
|
||||
{/* Step 1 - WhatsApp Methods with 3D Cards */}
|
||||
<div className="text-center">
|
||||
<div className="inline-flex items-center justify-center w-12 h-12 bg-green-500 text-white rounded-full text-xl font-bold mb-6">
|
||||
{howItWorksSteps[0].step}
|
||||
@ -188,29 +189,47 @@ const Landing = () => {
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{howItWorksSteps[0].methods.map((method, index) => (
|
||||
<div key={index} className="bg-slate-700/50 p-6 rounded-xl border border-slate-600 hover:border-green-500 transition-all duration-300">
|
||||
<div className="bg-green-500/20 p-3 rounded-lg w-fit mx-auto mb-4 text-green-400">
|
||||
{method.icon}
|
||||
</div>
|
||||
<h4 className="text-xl font-semibold text-white mb-2">{method.title}</h4>
|
||||
<p className="text-slate-300 mb-4">{method.description}</p>
|
||||
<div className="aspect-video bg-slate-800 rounded-lg flex items-center justify-center overflow-hidden">
|
||||
<img
|
||||
src={method.image}
|
||||
alt={`Exemplo ${method.title}`}
|
||||
className="max-w-full max-h-full object-contain rounded-lg"
|
||||
onError={(e) => {
|
||||
console.error(`Failed to load image: ${method.image}`);
|
||||
e.currentTarget.style.display = 'none';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<CardContainer key={index} className="inter-var" containerClassName="py-10">
|
||||
<CardBody className="bg-slate-700/50 relative group/card hover:shadow-2xl hover:shadow-emerald-500/[0.1] border-slate-600 w-auto h-auto rounded-xl p-6 border">
|
||||
<CardItem
|
||||
translateZ="50"
|
||||
className="bg-green-500/20 p-3 rounded-lg w-fit mx-auto mb-4 text-green-400"
|
||||
>
|
||||
{method.icon}
|
||||
</CardItem>
|
||||
<CardItem
|
||||
translateZ="60"
|
||||
className="text-xl font-semibold text-white mb-2 text-center"
|
||||
>
|
||||
{method.title}
|
||||
</CardItem>
|
||||
<CardItem
|
||||
as="p"
|
||||
translateZ="60"
|
||||
className="text-slate-300 mb-4 text-center"
|
||||
>
|
||||
{method.description}
|
||||
</CardItem>
|
||||
<CardItem translateZ="100" className="w-full">
|
||||
<div className="aspect-video bg-slate-800 rounded-lg flex items-center justify-center overflow-hidden">
|
||||
<img
|
||||
src={method.image}
|
||||
alt={`Exemplo ${method.title}`}
|
||||
className="max-w-full max-h-full object-contain rounded-lg group-hover/card:shadow-xl"
|
||||
onError={(e) => {
|
||||
console.error(`Failed to load image: ${method.image}`);
|
||||
e.currentTarget.style.display = 'none';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</CardItem>
|
||||
</CardBody>
|
||||
</CardContainer>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Step 2 - Dashboard */}
|
||||
{/* Step 2 - Dashboard with 3D Card */}
|
||||
<div className="text-center">
|
||||
<div className="inline-flex items-center justify-center w-12 h-12 bg-blue-500 text-white rounded-full text-xl font-bold mb-6">
|
||||
{howItWorksSteps[1].step}
|
||||
@ -219,23 +238,27 @@ const Landing = () => {
|
||||
<p className="text-lg text-slate-300 mb-8">{howItWorksSteps[1].description}</p>
|
||||
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<div className="bg-slate-700/50 p-6 rounded-xl border border-slate-600">
|
||||
<div className="aspect-video bg-slate-800 rounded-lg flex items-center justify-center overflow-hidden">
|
||||
<img
|
||||
src={howItWorksSteps[1].image}
|
||||
alt="Dashboard do sistema"
|
||||
className="max-w-full max-h-full object-contain rounded-lg"
|
||||
onError={(e) => {
|
||||
console.error(`Failed to load image: ${howItWorksSteps[1].image}`);
|
||||
e.currentTarget.style.display = 'none';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<CardContainer className="inter-var" containerClassName="py-10">
|
||||
<CardBody className="bg-slate-700/50 relative group/card hover:shadow-2xl hover:shadow-blue-500/[0.1] border-slate-600 w-full h-auto rounded-xl p-6 border">
|
||||
<CardItem translateZ="100" className="w-full">
|
||||
<div className="aspect-video bg-slate-800 rounded-lg flex items-center justify-center overflow-hidden">
|
||||
<img
|
||||
src={howItWorksSteps[1].image}
|
||||
alt="Dashboard do sistema"
|
||||
className="max-w-full max-h-full object-contain rounded-lg group-hover/card:shadow-xl"
|
||||
onError={(e) => {
|
||||
console.error(`Failed to load image: ${howItWorksSteps[1].image}`);
|
||||
e.currentTarget.style.display = 'none';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</CardItem>
|
||||
</CardBody>
|
||||
</CardContainer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Step 3 - Calendar */}
|
||||
{/* Step 3 - Calendar with 3D Card */}
|
||||
<div className="text-center">
|
||||
<div className="inline-flex items-center justify-center w-12 h-12 bg-purple-500 text-white rounded-full text-xl font-bold mb-6">
|
||||
{howItWorksSteps[2].step}
|
||||
@ -244,19 +267,23 @@ const Landing = () => {
|
||||
<p className="text-lg text-slate-300 mb-8">{howItWorksSteps[2].description}</p>
|
||||
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<div className="bg-slate-700/50 p-6 rounded-xl border border-slate-600">
|
||||
<div className="aspect-video bg-slate-800 rounded-lg flex items-center justify-center overflow-hidden">
|
||||
<img
|
||||
src={howItWorksSteps[2].image}
|
||||
alt="Calendário do sistema"
|
||||
className="max-w-full max-h-full object-contain rounded-lg"
|
||||
onError={(e) => {
|
||||
console.error(`Failed to load image: ${howItWorksSteps[2].image}`);
|
||||
e.currentTarget.style.display = 'none';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<CardContainer className="inter-var" containerClassName="py-10">
|
||||
<CardBody className="bg-slate-700/50 relative group/card hover:shadow-2xl hover:shadow-purple-500/[0.1] border-slate-600 w-full h-auto rounded-xl p-6 border">
|
||||
<CardItem translateZ="100" className="w-full">
|
||||
<div className="aspect-video bg-slate-800 rounded-lg flex items-center justify-center overflow-hidden">
|
||||
<img
|
||||
src={howItWorksSteps[2].image}
|
||||
alt="Calendário do sistema"
|
||||
className="max-w-full max-h-full object-contain rounded-lg group-hover/card:shadow-xl"
|
||||
onError={(e) => {
|
||||
console.error(`Failed to load image: ${howItWorksSteps[2].image}`);
|
||||
e.currentTarget.style.display = 'none';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</CardItem>
|
||||
</CardBody>
|
||||
</CardContainer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -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<string>(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 (
|
||||
<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>
|
||||
{/* Exibe mensagem para trial OU para admin-liberou */}
|
||||
{!access.loading && access.podeAdicionarTransacao && (
|
||||
access.adminLiberou ? (
|
||||
<div className="mt-1 text-xs text-green-600 font-bold">
|
||||
{access.motivo}
|
||||
</div>
|
||||
) : (access.diasRestantesTrial > 0 && (
|
||||
<div className="mt-1 text-xs text-blue-600 font-medium">
|
||||
{`Você está em período gratuito. ${access.diasRestantesTrial} dia(s) restante(s) de teste.`}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
<MonthFilter
|
||||
selectedMonth={selectedMonth}
|
||||
onMonthChange={setSelectedMonth}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Mensagem de bloqueio */}
|
||||
{!access.loading && !access.podeAdicionarTransacao && (
|
||||
<div className="bg-yellow-100 border border-yellow-300 text-yellow-800 px-4 py-3 rounded relative mb-4">
|
||||
<span className="font-medium">Atenção:</span> {access.motivo}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Só passa as funções de adicionar se permitido */}
|
||||
<TransactionHeader
|
||||
onOpenDialog={access.podeAdicionarTransacao ? handleOpenDialog : () => {}}
|
||||
onOpenCartaoCreditoDialog={access.podeAdicionarTransacao ? handleOpenCartaoCreditoDialog : () => {}}
|
||||
disableAdicionar={!access.podeAdicionarTransacao}
|
||||
onOpenDialog={handleOpenDialog}
|
||||
onOpenCartaoCreditoDialog={handleOpenCartaoCreditoDialog}
|
||||
/>
|
||||
|
||||
{/* Resumo em Cards */}
|
||||
<TransactionSummaryCards
|
||||
totalReceitas={totalReceitas}
|
||||
totalDespesas={totalDespesas}
|
||||
@ -101,25 +44,22 @@ const TransacoesPage = () => {
|
||||
formatCurrency={formatCurrency}
|
||||
/>
|
||||
|
||||
{/* Tabela completa de transações */}
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-xl font-semibold">Todas as Transações</h2>
|
||||
<SimpleCard title="Todas as Transações" className="border-gray-200">
|
||||
<TransactionsTable
|
||||
transactions={transactions}
|
||||
transactions={transactions}
|
||||
isLoading={isLoading}
|
||||
showPagination={true}
|
||||
onEdit={handleEditTransaction}
|
||||
onDelete={loadTransactions}
|
||||
/>
|
||||
</div>
|
||||
</SimpleCard>
|
||||
|
||||
{/* Diálogos */}
|
||||
<TransactionDialogs
|
||||
<TransactionDialogs
|
||||
isDialogOpen={isDialogOpen}
|
||||
isCartaoCreditoDialogOpen={isCartaoCreditoDialogOpen}
|
||||
tipoForm={tipoForm}
|
||||
selectedTransaction={selectedTransaction}
|
||||
isEditing={isEditing}
|
||||
isCartaoCreditoDialogOpen={isCartaoCreditoDialogOpen}
|
||||
cartoes={cartoes}
|
||||
onTransactionSuccess={handleTransactionSuccess}
|
||||
onDespesaCartaoSuccess={handleDespesaCartaoSuccess}
|
||||
@ -130,4 +70,4 @@ const TransacoesPage = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default TransacoesPage;
|
||||
export default Transacoes;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user