11import Link from 'next/link' ;
2- import { ActivityDto , RecurringExpenseDto , SimplifySuggestionDto } from '@fairshare/shared-types' ;
2+ import { ActivityDto , GroupDashboardDto } from '@fairshare/shared-types' ;
33import {
44 ArrowUpRight ,
55 CreditCard ,
@@ -17,14 +17,6 @@ import { GlassCard } from '../../components/ui/GlassCard';
1717
1818type Group = { id : string ; name : string ; currency : string } ;
1919
20- type AttentionItem = {
21- groupId : string ;
22- groupName : string ;
23- currency : string ;
24- settlementCount : number ;
25- dueRecurringCount : number ;
26- } ;
27-
2820function formatMoney ( cents : string | null , currency : string | null ) : string {
2921 if ( ! cents || ! currency ) {
3022 return '$0.00' ;
@@ -34,41 +26,16 @@ function formatMoney(cents: string | null, currency: string | null): string {
3426 return amount . toLocaleString ( undefined , { style : 'currency' , currency } ) ;
3527}
3628
37- function isRecurringDue ( nextOccurrenceAt : string ) : boolean {
38- return new Date ( nextOccurrenceAt ) . getTime ( ) <= Date . now ( ) ;
39- }
40-
4129export default async function DashboardPage ( ) {
42- const [ groups , recentActivityData , summary ] = await Promise . all ( [
43- backendFetch < Group [ ] > ( '/groups' ) ,
30+ const [ dashboard , recentActivityData ] = await Promise . all ( [
31+ backendFetch < GroupDashboardDto > ( '/groups/dashboard ' ) ,
4432 backendFetch < { items : ActivityDto [ ] } > ( '/activity' ) ,
45- backendFetch < { totalBalanceCents : string } > ( '/groups/summary' ) ,
4633 ] ) ;
4734
48- const attentionCandidates = await Promise . all (
49- groups . slice ( 0 , 6 ) . map ( async ( group ) => {
50- const [ suggestions , recurringExpenses ] = await Promise . all ( [
51- backendFetch < SimplifySuggestionDto [ ] > ( `/groups/${ group . id } /simplify` ) . catch ( ( ) => [ ] ) ,
52- backendFetch < RecurringExpenseDto [ ] > ( `/groups/${ group . id } /recurring-expenses` ) . catch ( ( ) => [ ] ) ,
53- ] ) ;
54-
55- return {
56- groupId : group . id ,
57- groupName : group . name ,
58- currency : group . currency ,
59- settlementCount : suggestions . length ,
60- dueRecurringCount : recurringExpenses . filter ( ( item ) => isRecurringDue ( item . nextOccurrenceAt ) ) . length ,
61- } satisfies AttentionItem ;
62- } ) ,
63- ) ;
64-
65- const attentionItems = attentionCandidates
66- . filter ( ( item ) => item . settlementCount > 0 || item . dueRecurringCount > 0 )
67- . sort ( ( a , b ) => ( b . settlementCount + b . dueRecurringCount ) - ( a . settlementCount + a . dueRecurringCount ) )
68- . slice ( 0 , 4 ) ;
69-
35+ const groups : Group [ ] = dashboard . groups ;
36+ const attentionItems = dashboard . attentionItems . slice ( 0 , 4 ) ;
7037 const recentActivity = recentActivityData . items || [ ] ;
71- const totalBalanceCents = summary . totalBalanceCents ;
38+ const totalBalanceCents = dashboard . totalBalanceCents ;
7239 const isPositive = Number ( totalBalanceCents ) >= 0 ;
7340
7441 return (
@@ -85,7 +52,7 @@ export default async function DashboardPage() {
8552 />
8653 < SummaryCard
8754 title = "Active Crews"
88- value = { groups . length . toString ( ) }
55+ value = { dashboard . activeGroupCount . toString ( ) }
8956 icon = "users"
9057 change = "Live"
9158 trend = "neutral"
@@ -113,20 +80,31 @@ export default async function DashboardPage() {
11380 < GlassCard className = "p-5 sm:p-6 border-white/5 bg-white/[0.01]" >
11481 < div className = "flex items-start justify-between gap-4 mb-5" >
11582 < div >
116- < p className = "text-[10px] font-bold tracking-widest text-zinc-500 uppercase" > Needs Attention</ p >
117- < h2 className = "mt-1 text-xl font-black italic tracking-tight text-white uppercase" > Resolve the next blockers</ h2 >
83+ < p className = "text-[10px] font-bold tracking-widest text-zinc-500 uppercase" >
84+ Needs Attention
85+ </ p >
86+ < h2 className = "mt-1 text-xl font-black italic tracking-tight text-white uppercase" >
87+ Resolve the next blockers
88+ </ h2 >
11889 </ div >
11990 < div className = "h-10 w-10 rounded-2xl border border-amber-500/20 bg-amber-500/10 flex items-center justify-center text-amber-400" >
12091 < AlertTriangle size = { 18 } />
12192 </ div >
12293 </ div >
12394 < div className = "grid gap-3 md:grid-cols-2" >
12495 { attentionItems . map ( ( item ) => (
125- < div key = { item . groupId } className = "rounded-2xl border border-white/5 bg-white/5 p-4" >
96+ < div
97+ key = { item . groupId }
98+ className = "rounded-2xl border border-white/5 bg-white/5 p-4"
99+ >
126100 < div className = "flex items-start justify-between gap-3" >
127101 < div >
128- < p className = "text-sm font-black tracking-tight text-white uppercase" > { item . groupName } </ p >
129- < p className = "mt-1 text-[11px] font-bold uppercase tracking-[0.16em] text-zinc-500" > { item . currency } </ p >
102+ < p className = "text-sm font-black tracking-tight text-white uppercase" >
103+ { item . groupName }
104+ </ p >
105+ < p className = "mt-1 text-[11px] font-bold uppercase tracking-[0.16em] text-zinc-500" >
106+ { item . currency } · { item . memberCount } members
107+ </ p >
130108 </ div >
131109 < Link
132110 href = { `/dashboard/groups/${ item . groupId } ` }
@@ -154,6 +132,9 @@ export default async function DashboardPage() {
154132 { item . dueRecurringCount } due recurring
155133 </ Link >
156134 ) : null }
135+ < span className = "inline-flex items-center gap-2 rounded-full bg-white/5 px-3 py-1.5 text-[11px] font-bold uppercase tracking-[0.12em] text-zinc-300" >
136+ { formatMoney ( item . netBalanceCents , item . currency ) }
137+ </ span >
157138 </ div >
158139 </ div >
159140 ) ) }
@@ -176,8 +157,12 @@ export default async function DashboardPage() {
176157 < GlassCard className = "p-5 sm:p-8 border-white/5 bg-white/[0.01]" >
177158 < div className = "flex items-center justify-between mb-8" >
178159 < div >
179- < h2 className = "text-sm font-black italic tracking-tight text-white uppercase" > Crews & Nodes </ h2 >
180- < p className = "text-[10px] font-bold tracking-widest text-zinc-600 uppercase mt-0.5" > Distributed spending groups</ p >
160+ < h2 className = "text-sm font-black italic tracking-tight text-white uppercase" >
161+ Crews & Nodes
162+ </ h2 >
163+ < p className = "text-[10px] font-bold tracking-widest text-zinc-600 uppercase mt-0.5" >
164+ Distributed spending groups
165+ </ p >
181166 </ div >
182167 < button
183168 title = "Create New Crew"
@@ -189,22 +174,35 @@ export default async function DashboardPage() {
189174
190175 < div className = "grid gap-3 grid-cols-1 sm:grid-cols-2" >
191176 { groups . map ( ( group ) => (
192- < Link href = { `/dashboard/groups/${ group . id } ` } key = { group . id } className = "flex items-center justify-between p-4 rounded-xl border border-white/5 bg-white/5 hover:bg-white/10 transition-all cursor-pointer group" >
177+ < Link
178+ href = { `/dashboard/groups/${ group . id } ` }
179+ key = { group . id }
180+ className = "flex items-center justify-between p-4 rounded-xl border border-white/5 bg-white/5 hover:bg-white/10 transition-all cursor-pointer group"
181+ >
193182 < div className = "flex items-center gap-3" >
194183 < div className = "h-8 w-8 rounded-lg bg-zinc-900 flex items-center justify-center text-zinc-500 group-hover:text-purple-400 transition-colors" >
195184 < CreditCard size = { 14 } />
196185 </ div >
197- < span className = "text-xs font-black tracking-tight text-white uppercase" > { group . name } </ span >
186+ < span className = "text-xs font-black tracking-tight text-white uppercase" >
187+ { group . name }
188+ </ span >
198189 </ div >
199190 < div className = "flex items-center gap-2" >
200- < span className = "text-[10px] font-bold text-zinc-500 uppercase tracking-widest" > { group . currency } </ span >
201- < ArrowUpRight size = { 12 } className = "text-zinc-800 group-hover:text-purple-500 transition-colors" />
191+ < span className = "text-[10px] font-bold text-zinc-500 uppercase tracking-widest" >
192+ { group . currency }
193+ </ span >
194+ < ArrowUpRight
195+ size = { 12 }
196+ className = "text-zinc-800 group-hover:text-purple-500 transition-colors"
197+ />
202198 </ div >
203199 </ Link >
204200 ) ) }
205201 { groups . length === 0 && (
206202 < div className = "col-span-full py-12 text-center rounded-2xl border border-dashed border-white/5" >
207- < p className = "text-[10px] font-black uppercase tracking-[0.2em] text-zinc-700" > NO_ACTIVE_GROUPS_LOCATED</ p >
203+ < p className = "text-[10px] font-black uppercase tracking-[0.2em] text-zinc-700" >
204+ NO_ACTIVE_GROUPS_LOCATED
205+ </ p >
208206 </ div >
209207 ) }
210208 </ div >
0 commit comments