fix: date hydration error in financial dashboard
This commit is contained in:
@@ -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}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 },
|
||||||
|
|||||||
@@ -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 },
|
||||||
|
|||||||
Reference in New Issue
Block a user