fix: date hydration error in financial dashboard

This commit is contained in:
Erik Silva
2026-02-04 20:18:35 -03:00
parent 6180b3fb4d
commit a7e9e4ccfd
4 changed files with 161 additions and 31 deletions

View File

@@ -20,18 +20,19 @@ export default async function FinancialPage({ searchParams }: FinancialPageProps
<header className="flex items-center justify-between"> <header className="flex items-center justify-between">
<div className="space-y-2"> <div className="space-y-2">
<h2 className="text-3xl font-bold tracking-tight"> <h2 className="text-3xl font-bold tracking-tight">
{tab === 'TRANSACTIONS' ? 'Fluxo de Caixa' : tab === 'REPORTS' ? 'Relatórios Financeiros' : 'Eventos Financeiros'} {tab === 'TRANSACTIONS' ? 'Fluxo de Caixa' : tab === 'REPORTS' ? 'Relatórios Financeiros' : tab === 'DASHBOARD' ? 'Resumo Financeiro' : 'Eventos Financeiros'}
</h2> </h2>
<p className="text-muted text-lg"> <p className="text-muted text-lg">
{tab === 'TRANSACTIONS' {tab === 'TRANSACTIONS'
? 'Acompanhe as entradas e saídas do caixa do grupo.' ? 'Acompanhe as entradas e saídas do caixa do grupo.'
: tab === 'REPORTS' : tab === 'REPORTS'
? 'Análise detalhada da saúde financeira do time.' ? 'Análise detalhada da saúde financeira do time.'
: 'Gerencie mensalidades e arrecadações extras.'} : tab === 'DASHBOARD'
? 'Visão geral das finanças e indicadores de saúde do grupo.'
: 'Gerencie mensalidades e arrecadações extras.'}
</p> </p>
</div> </div>
</header> </header>
<FinancialDashboard <FinancialDashboard
events={events} events={events}
transactions={transactions} transactions={transactions}

View File

@@ -19,7 +19,7 @@ interface FinancialPageProps {
transactions: any[] transactions: any[]
players: any[] players: any[]
group: any group: any
initialTab: 'EVENTS' | 'TRANSACTIONS' | 'REPORTS' initialTab: 'EVENTS' | 'TRANSACTIONS' | 'REPORTS' | 'DASHBOARD'
} }
export function FinancialDashboard({ events, transactions, players, group, initialTab }: FinancialPageProps) { export function FinancialDashboard({ events, transactions, players, group, initialTab }: FinancialPageProps) {
@@ -48,7 +48,7 @@ export function FinancialDashboard({ events, transactions, players, group, initi
const [privacyPendingId, setPrivacyPendingId] = useState<string | null>(null) const [privacyPendingId, setPrivacyPendingId] = useState<string | null>(null)
// Filter & View State // Filter & View State
const [mainTab, setMainTab] = useState<'EVENTS' | 'TRANSACTIONS' | 'REPORTS'>(initialTab) const [mainTab, setMainTab] = useState<'EVENTS' | 'TRANSACTIONS' | 'REPORTS' | 'DASHBOARD'>(initialTab)
// Sync state when prop changes (from sidebar clicks) // Sync state when prop changes (from sidebar clicks)
React.useEffect(() => { React.useEffect(() => {
@@ -292,11 +292,6 @@ export function FinancialDashboard({ events, transactions, players, group, initi
return ( return (
<div className="space-y-8 pb-20"> <div className="space-y-8 pb-20">
<div className="bg-red-500 text-white p-4 text-center font-bold">
DEBUG: DASHBOARD REESTRUTURADO ATIVO
</div>
<div className="space-y-6"> <div className="space-y-6">
<div className="flex flex-col lg:flex-row lg:items-center justify-between gap-6"> <div className="flex flex-col lg:flex-row lg:items-center justify-between gap-6">
<div className="flex-1 w-full lg:w-auto"> <div className="flex-1 w-full lg:w-auto">
@@ -316,7 +311,7 @@ export function FinancialDashboard({ events, transactions, players, group, initi
</div> </div>
) : ( ) : (
<div className="flex flex-col sm:flex-row items-center gap-4 w-full"> <div className="flex flex-col sm:flex-row items-center gap-4 w-full">
{mainTab !== 'REPORTS' && ( {mainTab !== 'REPORTS' && mainTab !== 'DASHBOARD' && (
<div className="relative group w-full sm:flex-1 max-w-md"> <div className="relative group w-full sm:flex-1 max-w-md">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted group-focus-within:text-primary transition-colors" /> <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted group-focus-within:text-primary transition-colors" />
<input <input
@@ -329,20 +324,35 @@ export function FinancialDashboard({ events, transactions, players, group, initi
)} )}
<div className="flex items-center gap-4 w-full sm:w-auto"> <div className="flex items-center gap-4 w-full sm:w-auto">
<div className="flex p-1 bg-surface-raised border border-border rounded-lg"> {mainTab === 'DASHBOARD' && (
<button <div className="flex items-center gap-2">
onClick={() => setViewMode('grid')} <DateRangePicker
className={clsx("p-2 rounded-md transition-all", viewMode === 'grid' ? "bg-white/10 text-primary shadow-sm" : "text-muted")} startDate={startDate}
> endDate={endDate}
<LayoutGrid className="w-4 h-4" /> onChange={(start, end) => {
</button> setStartDate(start)
<button setEndDate(end)
onClick={() => setViewMode('list')} }}
className={clsx("p-2 rounded-md transition-all", viewMode === 'list' ? "bg-white/10 text-primary shadow-sm" : "text-muted")} className="w-64"
> />
<List className="w-4 h-4" /> </div>
</button> )}
</div> {mainTab !== 'DASHBOARD' && (
<div className="flex p-1 bg-surface-raised border border-border rounded-lg">
<button
onClick={() => setViewMode('grid')}
className={clsx("p-2 rounded-md transition-all", viewMode === 'grid' ? "bg-white/10 text-primary shadow-sm" : "text-muted")}
>
<LayoutGrid className="w-4 h-4" />
</button>
<button
onClick={() => setViewMode('list')}
className={clsx("p-2 rounded-md transition-all", viewMode === 'list' ? "bg-white/10 text-primary shadow-sm" : "text-muted")}
>
<List className="w-4 h-4" />
</button>
</div>
)}
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<button <button
@@ -389,7 +399,7 @@ export function FinancialDashboard({ events, transactions, players, group, initi
<div className={clsx( <div className={clsx(
"grid gap-4 transition-all duration-500", "grid gap-4 transition-all duration-500",
mainTab === 'REPORTS' ? "grid-cols-1 md:grid-cols-2" : (viewMode === 'grid' ? "grid-cols-1 md:grid-cols-2 xl:grid-cols-3" : "grid-cols-1") mainTab === 'REPORTS' || mainTab === 'DASHBOARD' ? "grid-cols-1" : (viewMode === 'grid' ? "grid-cols-1 md:grid-cols-2 xl:grid-cols-3" : "grid-cols-1")
)}> )}>
{mainTab !== 'REPORTS' && (mainTab === 'EVENTS' ? filteredEvents.length > 0 : filteredTransactions.length > 0) && ( {mainTab !== 'REPORTS' && (mainTab === 'EVENTS' ? filteredEvents.length > 0 : filteredTransactions.length > 0) && (
<div className={clsx("col-span-full flex items-center px-2 mb-2", viewMode === 'grid' ? "justify-end" : "justify-start")}> <div className={clsx("col-span-full flex items-center px-2 mb-2", viewMode === 'grid' ? "justify-end" : "justify-start")}>
@@ -436,7 +446,123 @@ export function FinancialDashboard({ events, transactions, players, group, initi
)} )}
<AnimatePresence mode='popLayout'> <AnimatePresence mode='popLayout'>
{mainTab === 'EVENTS' ? ( {mainTab === 'DASHBOARD' ? (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 20 }}
className="col-span-full space-y-8"
>
{/* Stats Overview */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="ui-card p-8 bg-gradient-to-br from-surface-raised via-surface-raised to-primary/5 border-primary/20 relative overflow-hidden group">
<div className="absolute top-0 right-0 w-32 h-32 bg-primary/5 blur-3xl rounded-full -mr-16 -mt-16 group-hover:bg-primary/10 transition-colors" />
<div className="flex items-center gap-6 relative z-10">
<div className="p-4 bg-primary/10 rounded-2xl border border-primary/20 shadow-xl">
<TrendingUp className="w-8 h-8 text-primary" />
</div>
<div>
<p className="text-xs text-muted font-bold uppercase tracking-[0.2em]">Saldo Atual</p>
<h3 className="text-4xl font-black text-foreground mt-1">R$ {totalStats.balance.toFixed(2)}</h3>
<div className="flex items-center gap-2 mt-2">
<div className="h-1.5 w-1.5 rounded-full bg-primary animate-pulse" />
<p className="text-[10px] text-muted font-bold uppercase">Disponível em caixa</p>
</div>
</div>
</div>
</div>
<div className="ui-card p-8 border-white/5 relative overflow-hidden group">
<div className="flex items-center gap-6 relative z-10">
<div className="p-4 bg-secondary/10 rounded-2xl border border-secondary/20 shadow-xl">
<ArrowUpCircle className="w-8 h-8 text-secondary-foreground" />
</div>
<div>
<p className="text-xs text-muted font-bold uppercase tracking-[0.2em]">Total Entradas</p>
<h3 className="text-4xl font-black text-secondary-foreground mt-1">R$ {(totalStats.paid + totalStats.transIncome).toFixed(2)}</h3>
<p className="text-[10px] text-muted font-bold uppercase mt-2">Mesas + Fluxo Manual</p>
</div>
</div>
</div>
<div className="ui-card p-8 border-red-500/10 relative overflow-hidden group">
<div className="flex items-center gap-6 relative z-10">
<div className="p-4 bg-red-500/10 rounded-2xl border border-red-500/20 shadow-xl">
<ArrowDownCircle className="w-8 h-8 text-red-500" />
</div>
<div>
<p className="text-xs text-muted font-bold uppercase tracking-[0.2em]">Total Saídas</p>
<h3 className="text-4xl font-black text-red-500 mt-1">R$ {totalStats.transExpense.toFixed(2)}</h3>
<p className="text-[10px] text-muted font-bold uppercase mt-2">Despesas pagas</p>
</div>
</div>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Distribution or Top Payers */}
<div className="ui-card p-6 border-white/5">
<div className="flex items-center justify-between mb-6">
<h4 className="text-sm font-black uppercase tracking-widest text-white flex items-center gap-2">
<Sparkles className="w-4 h-4 text-primary" /> Melhores Pagadores
</h4>
<span className="text-[10px] font-bold text-muted bg-white/5 px-2 py-1 rounded">Ranking Total</span>
</div>
<div className="space-y-4">
{totalStats.allPlayerStats.slice(0, 5).map((player, idx) => (
<div key={player.id} className="flex items-center justify-between p-3 bg-surface border border-white/5 rounded-xl hover:border-primary/20 transition-all hover:translate-x-1">
<div className="flex items-center gap-4">
<div className="w-8 h-8 rounded-full bg-surface-raised flex items-center justify-center font-black text-xs border border-white/10">
{idx + 1}
</div>
<div>
<p className="text-sm font-bold text-foreground leading-none">{player.name}</p>
<p className="text-[10px] text-muted font-bold uppercase mt-1">Contribuinte Ativo</p>
</div>
</div>
<p className="text-sm font-black text-primary">R$ {player.totalPaid.toFixed(2)}</p>
</div>
))}
</div>
</div>
{/* Expense categories */}
<div className="ui-card p-6 border-white/5">
<div className="flex items-center justify-between mb-6">
<h4 className="text-sm font-black uppercase tracking-widest text-white flex items-center gap-2">
<PieChart className="w-4 h-4 text-secondary-foreground" /> Gastos por Categoria
</h4>
</div>
<div className="space-y-6">
{totalStats.categoryExpenses.length > 0 ? (
totalStats.categoryExpenses.map((cat) => (
<div key={cat.name} className="space-y-2">
<div className="flex justify-between text-[10px] font-bold uppercase tracking-widest leading-none">
<span className="text-muted">{cat.name}</span>
<span className="text-white">R$ {cat.value.toFixed(2)}</span>
</div>
<div className="h-2 w-full bg-surface-raised rounded-full overflow-hidden">
<motion.div
initial={{ width: 0 }}
animate={{ width: `${(cat.value / totalStats.transExpense) * 100}%` }}
className="h-full bg-secondary shadow-[0_0_10px_rgba(234,179,8,0.3)]"
/>
</div>
</div>
))
) : (
<div className="flex flex-col items-center justify-center py-12 text-center border-2 border-dashed border-white/5 rounded-2xl">
<div className="p-4 bg-white/5 rounded-full mb-4">
<Receipt className="w-8 h-8 text-muted/30" />
</div>
<p className="text-xs font-bold text-muted uppercase tracking-widest">Nenhuma despesa registrada</p>
</div>
)}
</div>
</div>
</div>
</motion.div>
) : mainTab === 'EVENTS' ? (
filteredEvents.map((event) => { filteredEvents.map((event) => {
const percent = event.stats.totalExpected > 0 ? (event.stats.totalPaid / event.stats.totalExpected) * 100 : 0 const percent = event.stats.totalExpected > 0 ? (event.stats.totalPaid / event.stats.totalExpected) * 100 : 0
const isExpanded = expandedEventId === event.id const isExpanded = expandedEventId === event.id
@@ -661,11 +787,11 @@ export function FinancialDashboard({ events, transactions, players, group, initi
{/* Date Header */} {/* Date Header */}
<div className="flex items-center gap-4 sticky top-0 z-20 py-2"> <div className="flex items-center gap-4 sticky top-0 z-20 py-2">
<div className="w-10 h-10 rounded-xl bg-surface border border-primary/20 flex items-center justify-center text-[10px] font-black italic shadow-lg shadow-primary/5 text-primary"> <div className="w-10 h-10 rounded-xl bg-surface border border-primary/20 flex items-center justify-center text-[10px] font-black italic shadow-lg shadow-primary/5 text-primary">
{new Date(date + 'T12:00:00').getDate()} {mounted ? new Date(date + 'T12:00:00').getDate() : '--'}
</div> </div>
<div className="bg-surface/80 backdrop-blur-md px-4 py-1.5 rounded-full border border-border shadow-sm"> <div className="bg-surface/80 backdrop-blur-md px-4 py-1.5 rounded-full border border-border shadow-sm">
<span className="text-[10px] font-black uppercase tracking-[0.2em] text-muted"> <span className="text-[10px] font-black uppercase tracking-[0.2em] text-muted">
{new Date(date + 'T12:00:00').toLocaleDateString('pt-BR', { weekday: 'short', month: 'long', year: 'numeric' })} {mounted ? new Date(date + 'T12:00:00').toLocaleDateString('pt-BR', { weekday: 'short', month: 'long', year: 'numeric' }) : '...'}
</span> </span>
</div> </div>
</div> </div>

View File

@@ -3,7 +3,7 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import Link from 'next/link' import Link from 'next/link'
import { usePathname } from 'next/navigation' import { usePathname } from 'next/navigation'
import { Menu, X, Trophy, Home, Calendar, Users, Settings, LogOut, ChevronRight, Banknote, History, Receipt, Sparkles } from 'lucide-react' import { Menu, X, Trophy, Home, Calendar, Users, Settings, LogOut, ChevronRight, Banknote, History, Receipt, Sparkles, PieChart } from 'lucide-react'
import { motion, AnimatePresence } from 'framer-motion' import { motion, AnimatePresence } from 'framer-motion'
import { clsx } from 'clsx' import { clsx } from 'clsx'
@@ -32,6 +32,7 @@ export function MobileNav({ group }: { group: any }) {
href: '/dashboard/financial', href: '/dashboard/financial',
icon: Banknote, icon: Banknote,
subItems: [ subItems: [
{ name: 'Resumo', href: '/dashboard/financial?tab=DASHBOARD', icon: PieChart },
{ name: 'Eventos', href: '/dashboard/financial?tab=EVENTS', icon: History }, { name: 'Eventos', href: '/dashboard/financial?tab=EVENTS', icon: History },
{ name: 'Caixa', href: '/dashboard/financial?tab=TRANSACTIONS', icon: Receipt }, { name: 'Caixa', href: '/dashboard/financial?tab=TRANSACTIONS', icon: Receipt },
{ name: 'Relatórios', href: '/dashboard/financial?tab=REPORTS', icon: Sparkles }, { name: 'Relatórios', href: '/dashboard/financial?tab=REPORTS', icon: Sparkles },

View File

@@ -14,7 +14,8 @@ import {
Eye, Eye,
History, History,
Receipt, Receipt,
Sparkles Sparkles,
PieChart
} from 'lucide-react' } from 'lucide-react'
import { clsx } from 'clsx' import { clsx } from 'clsx'
@@ -31,6 +32,7 @@ export function Sidebar({ group }: { group: any }) {
href: '/dashboard/financial', href: '/dashboard/financial',
icon: Banknote, icon: Banknote,
subItems: [ subItems: [
{ name: 'Resumo', href: '/dashboard/financial?tab=DASHBOARD', icon: PieChart },
{ name: 'Eventos', href: '/dashboard/financial?tab=EVENTS', icon: History }, { name: 'Eventos', href: '/dashboard/financial?tab=EVENTS', icon: History },
{ name: 'Caixa', href: '/dashboard/financial?tab=TRANSACTIONS', icon: Receipt }, { name: 'Caixa', href: '/dashboard/financial?tab=TRANSACTIONS', icon: Receipt },
{ name: 'Relatórios', href: '/dashboard/financial?tab=REPORTS', icon: Sparkles }, { name: 'Relatórios', href: '/dashboard/financial?tab=REPORTS', icon: Sparkles },