@@ -56,7 +56,6 @@ import {
5656 XAxis ,
5757 YAxis ,
5858 Tooltip ,
59- Legend ,
6059 CartesianGrid ,
6160 PieChart ,
6261 Pie ,
@@ -1372,7 +1371,27 @@ function PaginaTransacoes({ tipo, espaco, empresarial, ano, mesIdx, acoes, showT
13721371 DASHBOARD
13731372 ============================================================ */
13741373
1375- const PIE_COLORS = [ "#10b981" , "#34d399" , "#6ee7b7" , "#fbbf24" , "#f87171" , "#94a3b8" , "#60a5fa" , "#c084fc" ] ;
1374+ // Tooltip dos gráficos: cartão flutuante com bolinha colorida, ciente do tema
1375+ // escuro. `cores` mapeia série -> cor (barras); na rosca a cor vem do próprio
1376+ // dado (payload.gCss = gradiente da categoria).
1377+ function TooltipGrafico ( { active, payload, label, cores = { } } ) {
1378+ if ( ! active || ! payload ?. length ) return null ;
1379+ return (
1380+ < div className = "rounded-xl border border-slate-200 bg-white/95 px-3 py-2 text-xs shadow-lg backdrop-blur dark:border-slate-700 dark:bg-slate-900/95" >
1381+ { label != null && < p className = "mb-1 font-semibold text-slate-600 dark:text-slate-300" > { label } </ p > }
1382+ { payload . map ( ( p ) => (
1383+ < p key = { p . name } className = "flex items-center gap-1.5 py-0.5 text-slate-500 dark:text-slate-400" >
1384+ < span
1385+ className = "inline-block h-2.5 w-2.5 shrink-0 rounded-full"
1386+ style = { { background : p . payload ?. gCss || cores [ p . name ] || p . color } }
1387+ />
1388+ < span className = "font-medium text-slate-700 dark:text-slate-200" > { p . name } </ span >
1389+ < span className = "ml-auto pl-3 font-semibold tabular-nums text-slate-700 dark:text-slate-100" > { fmtBRL ( p . value ) } </ span >
1390+ </ p >
1391+ ) ) }
1392+ </ div >
1393+ ) ;
1394+ }
13761395
13771396function PaginaDashboard ( { espaco, ano, mesIdx, escuro, irPara } ) {
13781397 const ts = espaco . transacoes ;
@@ -1417,17 +1436,22 @@ function PaginaDashboard({ espaco, ano, mesIdx, escuro, irPara }) {
14171436 } ) ;
14181437 }
14191438
1420- // pizza — despesas do mês por categoria
1439+ // rosca — despesas do mês por categoria, cada fatia com a COR da categoria
14211440 const porCat = { } ;
14221441 doMes
14231442 . filter ( ( t ) => t . tipo === "despesa" && t . status === "ok" )
14241443 . forEach ( ( t ) => {
1425- const nome = espaco . categorias . find ( ( c ) => c . id === t . categoriaId ) ?. nome || "Outros" ;
1426- porCat [ nome ] = ( porCat [ nome ] || 0 ) + t . valor ;
1444+ const cat = espaco . categorias . find ( ( c ) => c . id === t . categoriaId ) || null ;
1445+ const nome = cat ?. nome || "Outros" ;
1446+ if ( ! porCat [ nome ] ) porCat [ nome ] = { value : 0 , g : cat ? gradCat ( cat ) : gradPorId ( "grafite" ) } ;
1447+ porCat [ nome ] . value += t . valor ;
14271448 } ) ;
14281449 const pieData = Object . entries ( porCat )
1429- . map ( ( [ name , value ] ) => ( { name, value } ) )
1450+ . map ( ( [ name , { value, g } ] ) => ( { name, value, g , gCss : cssGrad ( g ) } ) )
14301451 . sort ( ( a , b ) => b . value - a . value ) ;
1452+ const totalPie = pieData . reduce ( ( a , p ) => a + p . value , 0 ) ;
1453+ // gradientes únicos usados (viram <linearGradient> no SVG da rosca)
1454+ const gradsPie = [ ...new Map ( pieData . map ( ( p ) => [ p . g . id , p . g ] ) ) . values ( ) ] ;
14311455
14321456 return (
14331457 < div className = "space-y-4" >
@@ -1476,48 +1500,120 @@ function PaginaDashboard({ espaco, ano, mesIdx, escuro, irPara }) {
14761500
14771501 < div className = "grid gap-4 lg:grid-cols-2" >
14781502 < Card >
1479- < SectionTitle > Entradas × Saídas (últimos 6 meses)</ SectionTitle >
1503+ < SectionTitle
1504+ right = {
1505+ < div className = "flex items-center gap-3 text-[11px] text-slate-400" >
1506+ < span className = "flex items-center gap-1.5" >
1507+ < span className = "h-2.5 w-2.5 rounded-full" style = { { background : "linear-gradient(180deg,#34d399,#059669)" } } />
1508+ Entradas
1509+ </ span >
1510+ < span className = "flex items-center gap-1.5" >
1511+ < span className = "h-2.5 w-2.5 rounded-full" style = { { background : escuro ? "linear-gradient(180deg,#64748b,#334155)" : "linear-gradient(180deg,#cbd5e1,#94a3b8)" } } />
1512+ Saídas
1513+ </ span >
1514+ </ div >
1515+ }
1516+ >
1517+ Entradas × Saídas (últimos 6 meses)
1518+ </ SectionTitle >
14801519 < div className = "h-64" >
14811520 < ResponsiveContainer width = "100%" height = "100%" >
1482- < BarChart data = { barData } margin = { { top : 5 , right : 5 , left : - 10 , bottom : 0 } } >
1521+ < BarChart data = { barData } margin = { { top : 8 , right : 5 , left : - 10 , bottom : 0 } } barGap = { 5 } >
1522+ < defs >
1523+ < linearGradient id = "gradEntradas" x1 = "0" y1 = "0" x2 = "0" y2 = "1" >
1524+ < stop offset = "0%" stopColor = "#34d399" />
1525+ < stop offset = "100%" stopColor = "#059669" />
1526+ </ linearGradient >
1527+ < linearGradient id = "gradSaidas" x1 = "0" y1 = "0" x2 = "0" y2 = "1" >
1528+ { escuro ? (
1529+ < >
1530+ < stop offset = "0%" stopColor = "#64748b" />
1531+ < stop offset = "100%" stopColor = "#334155" />
1532+ </ >
1533+ ) : (
1534+ < >
1535+ < stop offset = "0%" stopColor = "#cbd5e1" />
1536+ < stop offset = "100%" stopColor = "#94a3b8" />
1537+ </ >
1538+ ) }
1539+ </ linearGradient >
1540+ </ defs >
14831541 < CartesianGrid strokeDasharray = "3 3" stroke = { escuro ? "#1e293b" : "#f1f5f9" } vertical = { false } />
1484- < XAxis dataKey = "name" tick = { { fontSize : 12 , fill : "#64748b" } } axisLine = { false } tickLine = { false } />
1542+ < XAxis dataKey = "name" tick = { { fontSize : 12 , fill : "#64748b" } } axisLine = { false } tickLine = { false } dy = { 4 } />
14851543 < YAxis
14861544 tick = { { fontSize : 11 , fill : "#94a3b8" } }
14871545 axisLine = { false }
14881546 tickLine = { false }
14891547 tickFormatter = { ( v ) => ( v >= 1000 ? ( v / 1000 ) . toLocaleString ( "pt-BR" ) + "k" : v ) }
14901548 />
1491- < Tooltip formatter = { ( v ) => fmtBRL ( v ) } contentStyle = { { borderRadius : 12 , border : "1px solid #e2e8f0" , fontSize : 13 } } />
1492- < Legend wrapperStyle = { { fontSize : 12 } } />
1493- < Bar dataKey = "Entradas" fill = "#10b981" radius = { [ 6 , 6 , 0 , 0 ] } />
1494- < Bar dataKey = "Saídas" fill = { escuro ? "#475569" : "#cbd5e1" } radius = { [ 6 , 6 , 0 , 0 ] } />
1549+ < Tooltip
1550+ cursor = { { fill : escuro ? "rgba(148,163,184,0.07)" : "rgba(100,116,139,0.06)" , radius : 8 } }
1551+ content = { < TooltipGrafico cores = { { Entradas : "#10b981" , Saídas : "#94a3b8" } } /> }
1552+ />
1553+ < Bar dataKey = "Entradas" fill = "url(#gradEntradas)" radius = { [ 7 , 7 , 2 , 2 ] } maxBarSize = { 26 } />
1554+ < Bar dataKey = "Saídas" fill = "url(#gradSaidas)" radius = { [ 7 , 7 , 2 , 2 ] } maxBarSize = { 26 } />
14951555 </ BarChart >
14961556 </ ResponsiveContainer >
14971557 </ div >
14981558 </ Card >
14991559
15001560 < Card >
15011561 < SectionTitle > Despesas do mês por categoria</ SectionTitle >
1502- < div className = "h-64" >
1503- { pieData . length === 0 ? (
1504- < div className = "flex h-full items-center justify-center text-sm text-slate-400" >
1505- Sem despesas pagas neste mês.
1562+ { pieData . length === 0 ? (
1563+ < div className = "flex h-64 items-center justify-center text-sm text-slate-400" >
1564+ Sem despesas pagas neste mês.
1565+ </ div >
1566+ ) : (
1567+ < div className = "flex flex-col items-center gap-2 sm:flex-row sm:gap-4" >
1568+ { /* rosca com as cores das categorias + total no centro */ }
1569+ < div className = "relative h-52 w-52 shrink-0" >
1570+ < ResponsiveContainer width = "100%" height = "100%" >
1571+ < PieChart >
1572+ < defs >
1573+ { gradsPie . map ( ( g ) => (
1574+ < linearGradient key = { g . id } id = { "gradPie-" + g . id } x1 = "0" y1 = "0" x2 = "1" y2 = "1" >
1575+ < stop offset = "0%" stopColor = { g . de } />
1576+ < stop offset = "100%" stopColor = { g . para } />
1577+ </ linearGradient >
1578+ ) ) }
1579+ </ defs >
1580+ < Pie
1581+ data = { pieData }
1582+ dataKey = "value"
1583+ nameKey = "name"
1584+ innerRadius = { 62 }
1585+ outerRadius = { 88 }
1586+ paddingAngle = { pieData . length > 1 ? 3 : 0 }
1587+ cornerRadius = { 6 }
1588+ strokeWidth = { 0 }
1589+ >
1590+ { pieData . map ( ( p ) => (
1591+ < Cell key = { p . name } fill = { `url(#gradPie-${ p . g . id } )` } />
1592+ ) ) }
1593+ </ Pie >
1594+ < Tooltip content = { < TooltipGrafico /> } />
1595+ </ PieChart >
1596+ </ ResponsiveContainer >
1597+ < div className = "pointer-events-none absolute inset-0 flex flex-col items-center justify-center" >
1598+ < p className = "text-[10px] font-medium uppercase tracking-wide text-slate-400" > Total</ p >
1599+ < p className = "max-w-[7.5rem] break-words text-center text-sm font-bold text-slate-700 dark:text-slate-100" >
1600+ { fmtBRL ( totalPie ) }
1601+ </ p >
1602+ </ div >
15061603 </ div >
1507- ) : (
1508- < ResponsiveContainer width = "100%" height = "100%" >
1509- < PieChart >
1510- < Pie data = { pieData } dataKey = "value" nameKey = "name" innerRadius = { 55 } outerRadius = { 85 } paddingAngle = { 3 } >
1511- { pieData . map ( ( _ , i ) => (
1512- < Cell key = { i } fill = { PIE_COLORS [ i % PIE_COLORS . length ] } />
1513- ) ) }
1514- </ Pie >
1515- < Tooltip formatter = { ( v ) => fmtBRL ( v ) } contentStyle = { { borderRadius : 12 , border : "1px solid #e2e8f0" , fontSize : 13 } } />
1516- < Legend wrapperStyle = { { fontSize : 12 } } />
1517- </ PieChart >
1518- </ ResponsiveContainer >
1519- ) }
1520- </ div >
1604+ { /* legenda: bolinha gradiente + nome + % */ }
1605+ < div className = "max-h-52 w-full flex-1 space-y-1 overflow-y-auto pr-1" >
1606+ { pieData . map ( ( p ) => (
1607+ < div key = { p . name } className = "flex items-center gap-2 rounded-lg px-2 py-1 text-xs hover:bg-slate-50 dark:hover:bg-slate-800/60" >
1608+ < span className = "h-2.5 w-2.5 shrink-0 rounded-full" style = { { background : p . gCss } } />
1609+ < span className = "min-w-0 flex-1 truncate font-medium text-slate-600 dark:text-slate-300" > { p . name } </ span >
1610+ < span className = "tabular-nums text-slate-400" > { ( ( p . value / totalPie ) * 100 ) . toFixed ( 0 ) } %</ span >
1611+ < span className = "w-20 text-right font-semibold tabular-nums text-slate-600 dark:text-slate-200" > { fmtBRL ( p . value ) } </ span >
1612+ </ div >
1613+ ) ) }
1614+ </ div >
1615+ </ div >
1616+ ) }
15211617 </ Card >
15221618 </ div >
15231619 </ div >
0 commit comments