From c59ba67daa26ed1c5842a4f8ed0df32597df0416 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 17 May 2025 23:25:52 +0000 Subject: [PATCH] feat: Create finance dashboard Implement a dashboard to display financial information from a Supabase database. The data will be fetched from the 'transacoes' table, which includes columns like id, user, created_at, valor, quando, detalhes, estabelecimento, tipo, and categoria. --- index.html | 13 +- src/App.tsx | 12 +- src/components/dashboard/CategoryChart.tsx | 85 ++++++++ src/components/dashboard/MonthlyChart.tsx | 84 ++++++++ src/components/dashboard/SummaryCard.tsx | 50 +++++ .../dashboard/TransactionsTable.tsx | 198 ++++++++++++++++++ src/components/layout/Layout.tsx | 23 ++ src/components/layout/Sidebar.tsx | 83 ++++++++ src/data/mockData.ts | 120 +++++++++++ src/index.css | 71 ++++--- src/pages/Index.tsx | 95 ++++++++- src/pages/NotFound.tsx | 20 +- src/types/financialTypes.ts | 39 ++++ tailwind.config.ts | 29 ++- 14 files changed, 870 insertions(+), 52 deletions(-) create mode 100644 src/components/dashboard/CategoryChart.tsx create mode 100644 src/components/dashboard/MonthlyChart.tsx create mode 100644 src/components/dashboard/SummaryCard.tsx create mode 100644 src/components/dashboard/TransactionsTable.tsx create mode 100644 src/components/layout/Layout.tsx create mode 100644 src/components/layout/Sidebar.tsx create mode 100644 src/data/mockData.ts create mode 100644 src/types/financialTypes.ts diff --git a/index.html b/index.html index 70fda46..cb3405a 100644 --- a/index.html +++ b/index.html @@ -1,14 +1,15 @@ + - + - budget-view-finance - - + Dashboard Financeiro + + - - + + diff --git a/src/App.tsx b/src/App.tsx index 18daf2e..7357562 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,3 +1,4 @@ + import { Toaster } from "@/components/ui/toaster"; import { Toaster as Sonner } from "@/components/ui/sonner"; import { TooltipProvider } from "@/components/ui/tooltip"; @@ -6,7 +7,14 @@ import { BrowserRouter, Routes, Route } from "react-router-dom"; import Index from "./pages/Index"; import NotFound from "./pages/NotFound"; -const queryClient = new QueryClient(); +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + refetchOnWindowFocus: false, + retry: 1, + }, + }, +}); const App = () => ( @@ -16,7 +24,7 @@ const App = () => ( } /> - {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} + {/* Adicione novas rotas acima desta linha */} } /> diff --git a/src/components/dashboard/CategoryChart.tsx b/src/components/dashboard/CategoryChart.tsx new file mode 100644 index 0000000..0febc49 --- /dev/null +++ b/src/components/dashboard/CategoryChart.tsx @@ -0,0 +1,85 @@ + +import React from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { PieChart, Pie, Cell, ResponsiveContainer, Legend, Tooltip } from 'recharts'; +import { CategorySummary } from '@/types/financialTypes'; + +interface CategoryChartProps { + categories: CategorySummary[]; + isLoading?: boolean; +} + +const CategoryChart: React.FC = ({ categories, isLoading = false }) => { + const renderCustomizedLabel = ({ cx, cy, midAngle, innerRadius, outerRadius, percent }: any) => { + const radius = innerRadius + (outerRadius - innerRadius) * 0.5; + const x = cx + radius * Math.cos(-midAngle * Math.PI / 180); + const y = cy + radius * Math.sin(-midAngle * Math.PI / 180); + + return ( + + {`${(percent * 100).toFixed(0)}%`} + + ); + }; + + const formatCurrency = (value: number) => { + return new Intl.NumberFormat('pt-BR', { + style: 'currency', + currency: 'BRL', + }).format(value); + }; + + return ( + + + Gastos por Categoria + + + {isLoading ? ( +
+
+
+ ) : categories.length === 0 ? ( +
+ Sem dados disponíveis +
+ ) : ( +
+ + + + {categories.map((entry, index) => ( + + ))} + + formatCurrency(value)} + labelFormatter={(index) => categories[index].categoria} + /> + + + +
+ )} + + + ); +}; + +export default CategoryChart; diff --git a/src/components/dashboard/MonthlyChart.tsx b/src/components/dashboard/MonthlyChart.tsx new file mode 100644 index 0000000..87233e9 --- /dev/null +++ b/src/components/dashboard/MonthlyChart.tsx @@ -0,0 +1,84 @@ + +import React from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { + BarChart, + Bar, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend, + ResponsiveContainer, +} from 'recharts'; +import { MonthlyData } from '@/types/financialTypes'; + +interface MonthlyChartProps { + data: MonthlyData[]; + isLoading?: boolean; +} + +const MonthlyChart: React.FC = ({ data, isLoading = false }) => { + const formatCurrency = (value: number) => { + return new Intl.NumberFormat('pt-BR', { + style: 'currency', + currency: 'BRL', + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }).format(value); + }; + + return ( + + + Receitas vs Despesas + + + {isLoading ? ( +
+
+
+ ) : data.length === 0 ? ( +
+ Sem dados disponíveis +
+ ) : ( +
+ + + + + + formatCurrency(value)} + labelFormatter={(label) => `Mês: ${label}`} + /> + + + + + +
+ )} + + + ); +}; + +export default MonthlyChart; diff --git a/src/components/dashboard/SummaryCard.tsx b/src/components/dashboard/SummaryCard.tsx new file mode 100644 index 0000000..bb94bed --- /dev/null +++ b/src/components/dashboard/SummaryCard.tsx @@ -0,0 +1,50 @@ + +import React from 'react'; +import { Card, CardContent } from "@/components/ui/card"; +import { cn } from '@/lib/utils'; + +interface SummaryCardProps { + title: string; + value: string; + icon: React.ReactNode; + trend?: number; + className?: string; + iconClass?: string; + valueClass?: string; +} + +const SummaryCard: React.FC = ({ + title, + value, + icon, + trend, + className, + iconClass, + valueClass, +}) => { + return ( + + +
+
+ {icon} +
+ {trend !== undefined && ( +
0 ? "text-finance-green" : trend < 0 ? "text-finance-red" : "text-muted-foreground" + )}> + {trend > 0 && '+'}{trend}% +
+ )} +
+
+

{title}

+

{value}

+
+
+
+ ); +}; + +export default SummaryCard; diff --git a/src/components/dashboard/TransactionsTable.tsx b/src/components/dashboard/TransactionsTable.tsx new file mode 100644 index 0000000..35bdeda --- /dev/null +++ b/src/components/dashboard/TransactionsTable.tsx @@ -0,0 +1,198 @@ + +import { useState } from 'react'; +import { format } from 'date-fns'; +import { ChevronDown, Search } from 'lucide-react'; +import { Transaction } from '@/types/financialTypes'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/table'; +import { cn } from '@/lib/utils'; + +interface TransactionsTableProps { + transactions: Transaction[]; + isLoading?: boolean; +} + +const TransactionsTable = ({ transactions, isLoading = false }: TransactionsTableProps) => { + const [searchQuery, setSearchQuery] = useState(''); + const [sortColumn, setSortColumn] = useState('quando'); + const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc'); + + const handleSort = (column: string) => { + if (sortColumn === column) { + setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc'); + } else { + setSortColumn(column); + setSortDirection('asc'); + } + }; + + 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) + ); + }); + + const sortedTransactions = [...filteredTransactions].sort((a, b) => { + if (sortColumn === 'valor') { + return sortDirection === 'asc' ? a.valor - b.valor : b.valor - a.valor; + } + + if (sortColumn === 'quando') { + return sortDirection === 'asc' + ? new Date(a.quando).getTime() - new Date(b.quando).getTime() + : new Date(b.quando).getTime() - new Date(a.quando).getTime(); + } + + const aValue = a[sortColumn as keyof Transaction]?.toString().toLowerCase() || ''; + const bValue = b[sortColumn as keyof Transaction]?.toString().toLowerCase() || ''; + + return sortDirection === 'asc' + ? aValue.localeCompare(bValue) + : bValue.localeCompare(aValue); + }); + + const formatCurrency = (value: number) => { + return new Intl.NumberFormat('pt-BR', { + style: 'currency', + currency: 'BRL', + }).format(value); + }; + + return ( +
+
+
+ + setSearchQuery(e.target.value)} + className="pl-8" + /> +
+ + + + + + setSearchQuery('')}> + Todas + + setSearchQuery('entrada')}> + Receitas + + setSearchQuery('saida')}> + Despesas + + + +
+ +
+ + + + handleSort('quando')} + > + Data {sortColumn === 'quando' && (sortDirection === 'asc' ? '↑' : '↓')} + + handleSort('estabelecimento')} + > + Estabelecimento {sortColumn === 'estabelecimento' && (sortDirection === 'asc' ? '↑' : '↓')} + + handleSort('detalhes')} + > + Detalhes {sortColumn === 'detalhes' && (sortDirection === 'asc' ? '↑' : '↓')} + + handleSort('categoria')} + > + Categoria {sortColumn === 'categoria' && (sortDirection === 'asc' ? '↑' : '↓')} + + handleSort('valor')} + > + Valor {sortColumn === 'valor' && (sortDirection === 'asc' ? '↑' : '↓')} + + + + + {isLoading ? ( + Array(5).fill(0).map((_, i) => ( + + {Array(5).fill(0).map((_, j) => ( + +
+ + ))} + + )) + ) : sortedTransactions.length === 0 ? ( + + + {searchQuery ? 'Nenhuma transação encontrada' : 'Não há transações disponíveis'} + + + ) : ( + sortedTransactions.slice(0, 5).map((transaction) => ( + + + {format(new Date(transaction.quando), 'dd/MM/yyyy')} + + {transaction.estabelecimento} + {transaction.detalhes} + + + {transaction.categoria} + + + + {transaction.tipo === 'entrada' ? '+' : '-'}{formatCurrency(Math.abs(transaction.valor))} + + + )) + )} + +
+
+ +
+ +
+
+ ); +}; + +export default TransactionsTable; diff --git a/src/components/layout/Layout.tsx b/src/components/layout/Layout.tsx new file mode 100644 index 0000000..e506dbb --- /dev/null +++ b/src/components/layout/Layout.tsx @@ -0,0 +1,23 @@ + +import React from 'react'; +import Sidebar from '@/components/layout/Sidebar'; +import { useToast } from "@/components/ui/use-toast"; + +interface LayoutProps { + children: React.ReactNode; +} + +const Layout: React.FC = ({ children }) => { + const { toast } = useToast(); + + return ( +
+ +
+ {children} +
+
+ ); +}; + +export default Layout; diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx new file mode 100644 index 0000000..a354193 --- /dev/null +++ b/src/components/layout/Sidebar.tsx @@ -0,0 +1,83 @@ + +import React, { useState } from 'react'; +import { Link, useLocation } from 'react-router-dom'; +import { cn } from '@/lib/utils'; +import { Button } from "@/components/ui/button"; +import { ChevronLeft, ChevronRight, Home, CalendarDays, DollarSign, PieChart } from 'lucide-react'; + +const navItems = [ + { + name: 'Dashboard', + path: '/', + icon: + }, + { + name: 'Transações', + path: '/transacoes', + icon: + }, + { + name: 'Categorias', + path: '/categorias', + icon: + }, + { + name: 'Calendário', + path: '/calendario', + icon: + }, +]; + +interface SidebarProps { + className?: string; +} + +const Sidebar = ({ className }: SidebarProps) => { + const location = useLocation(); + const [collapsed, setCollapsed] = useState(false); + + return ( +
+
+ {!collapsed && ( +

FinDash

+ )} + +
+ + +
+ ); +}; + +export default Sidebar; diff --git a/src/data/mockData.ts b/src/data/mockData.ts new file mode 100644 index 0000000..d19db4d --- /dev/null +++ b/src/data/mockData.ts @@ -0,0 +1,120 @@ + +import { Transaction, CategorySummary, MonthlyData } from "@/types/financialTypes"; + +// Mock transaction data +export const mockTransactions: Transaction[] = [ + { + id: "1", + user: "user123", + created_at: "2023-05-15T10:30:00Z", + valor: 1500.00, + quando: "2023-05-15", + detalhes: "Salário mensal", + estabelecimento: "Empresa XYZ", + tipo: "entrada", + categoria: "Salário" + }, + { + id: "2", + user: "user123", + created_at: "2023-05-16T14:20:00Z", + valor: 120.50, + quando: "2023-05-16", + detalhes: "Compras semanais", + estabelecimento: "Supermercado Bom Preço", + tipo: "saida", + categoria: "Alimentação" + }, + { + id: "3", + user: "user123", + created_at: "2023-05-17T09:15:00Z", + valor: 75.00, + quando: "2023-05-17", + detalhes: "Combustível", + estabelecimento: "Posto Shell", + tipo: "saida", + categoria: "Transporte" + }, + { + id: "4", + user: "user123", + created_at: "2023-05-18T18:45:00Z", + valor: 200.00, + quando: "2023-05-18", + detalhes: "Jantar com amigos", + estabelecimento: "Restaurante Sabor & Arte", + tipo: "saida", + categoria: "Lazer" + }, + { + id: "5", + user: "user123", + created_at: "2023-05-20T11:30:00Z", + valor: 450.00, + quando: "2023-05-20", + detalhes: "Aluguel", + estabelecimento: "Imobiliária Central", + tipo: "saida", + categoria: "Moradia" + }, + { + id: "6", + user: "user123", + created_at: "2023-05-22T15:10:00Z", + valor: 89.90, + quando: "2023-05-22", + detalhes: "Internet", + estabelecimento: "Telecomunicações Brasil", + tipo: "saida", + categoria: "Serviços" + }, + { + id: "7", + user: "user123", + created_at: "2023-05-25T08:20:00Z", + valor: 300.00, + quando: "2023-05-25", + detalhes: "Freelance design", + estabelecimento: "Cliente Particular", + tipo: "entrada", + categoria: "Freelance" + }, + { + id: "8", + user: "user123", + created_at: "2023-05-27T14:00:00Z", + valor: 65.00, + quando: "2023-05-27", + detalhes: "Farmácia", + estabelecimento: "Drogaria Saúde", + tipo: "saida", + categoria: "Saúde" + } +]; + +// Mock category data +export const mockCategories: CategorySummary[] = [ + { categoria: "Alimentação", valor: 250.50, percentage: 0.25, color: "#F59E0B" }, + { categoria: "Transporte", valor: 150.00, percentage: 0.15, color: "#60A5FA" }, + { categoria: "Lazer", valor: 200.00, percentage: 0.2, color: "#8B5CF6" }, + { categoria: "Moradia", valor: 450.00, percentage: 0.45, color: "#EF4444" }, + { categoria: "Serviços", valor: 89.90, percentage: 0.09, color: "#10B981" } +]; + +// Mock monthly data +export const mockMonthlyData: MonthlyData[] = [ + { month: "Jan", receitas: 2000, despesas: 1500 }, + { month: "Fev", receitas: 2200, despesas: 1800 }, + { month: "Mar", receitas: 1900, despesas: 1700 }, + { month: "Abr", receitas: 2400, despesas: 1600 }, + { month: "Mai", receitas: 1800, despesas: 1900 }, + { month: "Jun", receitas: 2100, despesas: 1750 } +]; + +// Mock total values +export const mockTotals = { + receitas: 1800, + despesas: 1000, + saldo: 800 +}; diff --git a/src/index.css b/src/index.css index 33fdf9d..94642e2 100644 --- a/src/index.css +++ b/src/index.css @@ -1,10 +1,11 @@ + @tailwind base; @tailwind components; @tailwind utilities; @layer base { :root { - --background: 0 0% 100%; + --background: 210 40% 98%; --foreground: 222.2 84% 4.9%; --card: 0 0% 100%; @@ -13,7 +14,7 @@ --popover: 0 0% 100%; --popover-foreground: 222.2 84% 4.9%; - --primary: 222.2 47.4% 11.2%; + --primary: 221.2 83% 53.3%; --primary-foreground: 210 40% 98%; --secondary: 210 40% 96.1%; @@ -30,25 +31,18 @@ --border: 214.3 31.8% 91.4%; --input: 214.3 31.8% 91.4%; - --ring: 222.2 84% 4.9%; + --ring: 221.2 83% 53.3%; --radius: 0.5rem; - --sidebar-background: 0 0% 98%; - - --sidebar-foreground: 240 5.3% 26.1%; - - --sidebar-primary: 240 5.9% 10%; - + --sidebar-background: 210 40% 98%; + --sidebar-foreground: 222.2 84% 4.9%; + --sidebar-primary: 221.2 83% 53.3%; --sidebar-primary-foreground: 0 0% 98%; - - --sidebar-accent: 240 4.8% 95.9%; - - --sidebar-accent-foreground: 240 5.9% 10%; - - --sidebar-border: 220 13% 91%; - - --sidebar-ring: 217.2 91.2% 59.8%; + --sidebar-accent: 210 40% 96.1%; + --sidebar-accent-foreground: 222.2 47.4% 11.2%; + --sidebar-border: 214.3 31.8% 91.4%; + --sidebar-ring: 221.2 83% 53.3%; } .dark { @@ -61,7 +55,7 @@ --popover: 222.2 84% 4.9%; --popover-foreground: 210 40% 98%; - --primary: 210 40% 98%; + --primary: 217.2 91.2% 59.8%; --primary-foreground: 222.2 47.4% 11.2%; --secondary: 217.2 32.6% 17.5%; @@ -78,15 +72,16 @@ --border: 217.2 32.6% 17.5%; --input: 217.2 32.6% 17.5%; - --ring: 212.7 26.8% 83.9%; - --sidebar-background: 240 5.9% 10%; - --sidebar-foreground: 240 4.8% 95.9%; - --sidebar-primary: 224.3 76.3% 48%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 240 3.7% 15.9%; - --sidebar-accent-foreground: 240 4.8% 95.9%; - --sidebar-border: 240 3.7% 15.9%; - --sidebar-ring: 217.2 91.2% 59.8%; + --ring: 224.3 76.3% 48%; + + --sidebar-background: 222.2 84% 4.9%; + --sidebar-foreground: 210 40% 98%; + --sidebar-primary: 217.2 91.2% 59.8%; + --sidebar-primary-foreground: 222.2 47.4% 11.2%; + --sidebar-accent: 217.2 32.6% 17.5%; + --sidebar-accent-foreground: 210 40% 98%; + --sidebar-border: 217.2 32.6% 17.5%; + --sidebar-ring: 224.3 76.3% 48%; } } @@ -97,5 +92,25 @@ body { @apply bg-background text-foreground; + font-feature-settings: "rlig" 1, "calt" 1; } -} \ No newline at end of file +} + +@layer utilities { + .scrollbar-hide::-webkit-scrollbar { + display: none; + } + + .scrollbar-hide { + -ms-overflow-style: none; + scrollbar-width: none; + } + + .dashboard-card { + @apply bg-card rounded-lg border p-5 shadow-sm transition-all hover:shadow-md; + } + + .data-card { + @apply bg-card rounded-lg border p-4 shadow-sm transition-all; + } +} diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index 52ea22c..b610896 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -1,14 +1,93 @@ -// Update this page (the content is just a fallback if you fail to update the page) -const Index = () => { +import { useState, useEffect } from 'react'; +import Layout from '@/components/layout/Layout'; +import SummaryCard from '@/components/dashboard/SummaryCard'; +import TransactionsTable from '@/components/dashboard/TransactionsTable'; +import CategoryChart from '@/components/dashboard/CategoryChart'; +import MonthlyChart from '@/components/dashboard/MonthlyChart'; +import { DollarSign, TrendingUp, TrendingDown, PiggyBank } from 'lucide-react'; +import { mockTransactions, mockCategories, mockMonthlyData, mockTotals } from '@/data/mockData'; +import { useToast } from "@/components/ui/use-toast"; + +const Dashboard = () => { + const [isLoading, setIsLoading] = useState(true); + const { toast } = useToast(); + + useEffect(() => { + // Simulando carregamento de dados + const timer = setTimeout(() => { + setIsLoading(false); + toast({ + title: "Dados carregados com sucesso", + description: "Conecte ao Supabase para ver seus dados reais" + }); + }, 1500); + + return () => clearTimeout(timer); + }, [toast]); + + const formatCurrency = (value: number) => { + return new Intl.NumberFormat('pt-BR', { + style: 'currency', + currency: 'BRL', + }).format(value); + }; + return ( -
-
-

Welcome to Your Blank App

-

Start building your amazing project here!

+ +
+
+

Dashboard Financeiro

+

+ Última atualização: {new Date().toLocaleDateString('pt-BR')} +

+
+ +
+ } + trend={5} + iconClass="bg-finance-green/10" + valueClass="text-finance-green" + /> + } + trend={-2} + iconClass="bg-finance-red/10" + valueClass="text-finance-red" + /> + } + iconClass="bg-finance-blue/10" + valueClass="text-finance-blue" + /> + } + iconClass="bg-finance-purple/10" + valueClass="text-finance-purple" + /> +
+ +
+ + +
+ +
+

Transações Recentes

+ +
-
+ ); }; -export default Index; +export default Dashboard; diff --git a/src/pages/NotFound.tsx b/src/pages/NotFound.tsx index cda36da..485d155 100644 --- a/src/pages/NotFound.tsx +++ b/src/pages/NotFound.tsx @@ -1,5 +1,8 @@ + import { useLocation } from "react-router-dom"; import { useEffect } from "react"; +import { Button } from "@/components/ui/button"; +import { Link } from "react-router-dom"; const NotFound = () => { const location = useLocation(); @@ -12,13 +15,16 @@ const NotFound = () => { }, [location.pathname]); return ( -
-
-

404

-

Oops! Page not found

- - Return to Home - +
+
+

404

+

Página não encontrada

+

+ A página que você está procurando não existe ou foi movida para outro lugar. +

+
); diff --git a/src/types/financialTypes.ts b/src/types/financialTypes.ts new file mode 100644 index 0000000..69f8f94 --- /dev/null +++ b/src/types/financialTypes.ts @@ -0,0 +1,39 @@ + +export interface Transaction { + id: string; + user: string; + created_at: string; + valor: number; + quando: string; + detalhes: string; + estabelecimento: string; + tipo: 'entrada' | 'saida'; + categoria: string; +} + +export interface TransactionSummary { + totalReceitas: number; + totalDespesas: number; + saldo: number; +} + +export interface CategorySummary { + categoria: string; + valor: number; + percentage: number; + color: string; +} + +export interface MonthlyData { + month: string; + receitas: number; + despesas: number; +} + +export interface TransactionFilters { + search: string; + startDate: Date | null; + endDate: Date | null; + tipo: string; + categoria: string; +} diff --git a/tailwind.config.ts b/tailwind.config.ts index 8706086..c023174 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,3 +1,4 @@ + import type { Config } from "tailwindcss"; export default { @@ -61,6 +62,16 @@ export default { 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))', border: 'hsl(var(--sidebar-border))', ring: 'hsl(var(--sidebar-ring))' + }, + // Financial dashboard specific colors + finance: { + 'blue': '#1D4ED8', + 'light-blue': '#60A5FA', + 'green': '#10B981', + 'light-green': '#34D399', + 'red': '#EF4444', + 'yellow': '#F59E0B', + 'purple': '#8B5CF6' } }, borderRadius: { @@ -84,11 +95,27 @@ export default { to: { height: '0' } + }, + 'fade-in': { + '0%': { + opacity: '0', + transform: 'translateY(10px)' + }, + '100%': { + opacity: '1', + transform: 'translateY(0)' + } + }, + 'pulse-gentle': { + '0%, 100%': { opacity: '1' }, + '50%': { opacity: '0.8' } } }, animation: { 'accordion-down': 'accordion-down 0.2s ease-out', - 'accordion-up': 'accordion-up 0.2s ease-out' + 'accordion-up': 'accordion-up 0.2s ease-out', + 'fade-in': 'fade-in 0.5s ease-out', + 'pulse-gentle': 'pulse-gentle 2s infinite' } } },