@@ -4,9 +4,9 @@ import { useState } from 'react';
44import { useRouter } from 'next/navigation' ;
55import { CurrencyCode , GroupDefaultSplitDto , GroupMemberSummaryDto } from '@fairshare/shared-types' ;
66import Link from 'next/link' ;
7- import { Download , PlusCircle , Wallet } from 'lucide-react' ;
7+ import { Download , PlusCircle , Scale , Wallet } from 'lucide-react' ;
88import dynamic from 'next/dynamic' ;
9- import { exportExpensesCsvAction } from '../../lib/actions' ;
9+ import { exportBalancesCsvAction , exportExpensesCsvAction } from '../../lib/actions' ;
1010import { useToast } from '../ui/Toaster' ;
1111
1212const CreateExpenseModal = dynamic (
@@ -21,62 +21,102 @@ type GroupActionsProps = {
2121 defaultSplitPreference ?: GroupDefaultSplitDto | null ;
2222} ;
2323
24- export function GroupActions ( { groupId, currency, members, defaultSplitPreference } : GroupActionsProps ) {
24+ export function GroupActions ( {
25+ groupId,
26+ currency,
27+ members,
28+ defaultSplitPreference,
29+ } : GroupActionsProps ) {
2530 const [ open , setOpen ] = useState ( false ) ;
26- const [ exporting , setExporting ] = useState ( false ) ;
31+ const [ exportingExpenses , setExportingExpenses ] = useState ( false ) ;
32+ const [ exportingBalances , setExportingBalances ] = useState ( false ) ;
2733 const router = useRouter ( ) ;
2834 const { toast } = useToast ( ) ;
2935
30- const handleExport = async ( ) => {
36+ const downloadCsv = ( csv : string , fileName : string ) => {
37+ const blob = new Blob ( [ csv ] , { type : 'text/csv;charset=utf-8' } ) ;
38+ const url = window . URL . createObjectURL ( blob ) ;
39+ const anchor = document . createElement ( 'a' ) ;
40+ anchor . href = url ;
41+ anchor . download = fileName ;
42+ document . body . appendChild ( anchor ) ;
43+ anchor . click ( ) ;
44+ anchor . remove ( ) ;
45+ window . URL . revokeObjectURL ( url ) ;
46+ } ;
47+
48+ const handleExpenseExport = async ( ) => {
3149 try {
32- setExporting ( true ) ;
50+ setExportingExpenses ( true ) ;
3351 const result = await exportExpensesCsvAction ( groupId ) ;
34- const csv = result . success ? result . csv ?? '' : '' ;
52+ const csv = result . success ? ( result . csv ?? '' ) : '' ;
3553 if ( ! csv ) {
36- throw new Error ( result . success ? 'Failed to export CSV' : result . message ) ;
54+ throw new Error ( result . success ? 'Failed to export expenses CSV' : result . message ) ;
3755 }
3856
39- const blob = new Blob ( [ csv ] , { type : 'text/csv;charset=utf-8' } ) ;
40- const url = window . URL . createObjectURL ( blob ) ;
41- const anchor = document . createElement ( 'a' ) ;
42- anchor . href = url ;
43- anchor . download = `fairshare-${ groupId } .csv` ;
44- document . body . appendChild ( anchor ) ;
45- anchor . click ( ) ;
46- anchor . remove ( ) ;
47- window . URL . revokeObjectURL ( url ) ;
48- toast ( 'CSV exported' ) ;
57+ downloadCsv ( csv , `fairshare-${ groupId } .csv` ) ;
58+ toast ( 'Expenses CSV exported' ) ;
4959 } catch ( error ) {
50- toast ( ( error as Error ) . message || 'Failed to export CSV' , 'error' ) ;
60+ toast ( ( error as Error ) . message || 'Failed to export expenses CSV' , 'error' ) ;
5161 } finally {
52- setExporting ( false ) ;
62+ setExportingExpenses ( false ) ;
63+ }
64+ } ;
65+
66+ const handleBalanceExport = async ( ) => {
67+ try {
68+ setExportingBalances ( true ) ;
69+ const result = await exportBalancesCsvAction ( groupId ) ;
70+ const csv = result . success ? ( result . csv ?? '' ) : '' ;
71+ if ( ! csv ) {
72+ throw new Error ( result . success ? 'Failed to export balances CSV' : result . message ) ;
73+ }
74+
75+ downloadCsv ( csv , `fairshare-${ groupId } -balances.csv` ) ;
76+ toast ( 'Balances CSV exported' ) ;
77+ } catch ( error ) {
78+ toast ( ( error as Error ) . message || 'Failed to export balances CSV' , 'error' ) ;
79+ } finally {
80+ setExportingBalances ( false ) ;
5381 }
5482 } ;
5583
5684 return (
5785 < >
58- < div className = "rounded-3xl border border-[var(--fs-border)] bg-[var(--fs-card)] p-4 sm:p-6 shadow-[var(--fs-shadow-soft)]" >
59- < h3 className = "text-lg font-bold text-[var(--fs-text-primary)] mb-4 " > Actions</ h3 >
86+ < div className = "rounded-3xl border border-[var(--fs-border)] bg-[var(--fs-card)] p-4 shadow-[var(--fs-shadow-soft)] sm:p-6 " >
87+ < h3 className = "mb-4 text-lg font-bold text-[var(--fs-text-primary)]" > Actions</ h3 >
6088 < div className = "grid gap-3" >
61- < button className = "btn-royal w-full inline-flex items-center justify-center gap-2" onClick = { ( ) => setOpen ( true ) } >
62- < PlusCircle className = "w-4 h-4" />
89+ < button
90+ className = "btn-royal inline-flex w-full items-center justify-center gap-2"
91+ onClick = { ( ) => setOpen ( true ) }
92+ >
93+ < PlusCircle className = "h-4 w-4" />
6394 Record expense
6495 </ button >
6596 < Link
6697 href = { `/dashboard/groups/${ groupId } /settle` }
67- className = "rounded-xl border border-[var(--fs-border)] bg-[var(--fs-background)] px-4 py-3 text-sm font-bold text-[var(--fs-text-primary)] hover:border-[var(--fs-primary)] flex items-center gap-2 justify-center "
98+ className = "flex items-center justify-center gap-2 rounded-xl border border-[var(--fs-border)] bg-[var(--fs-background)] px-4 py-3 text-sm font-bold text-[var(--fs-text-primary)] hover:border-[var(--fs-primary)]"
6899 >
69- < Wallet className = "w -4 h -4 text-[var(--fs-primary)]" />
100+ < Wallet className = "h -4 w -4 text-[var(--fs-primary)]" />
70101 Settle up
71102 </ Link >
72103 < button
73104 type = "button"
74- onClick = { ( ) => void handleExport ( ) }
75- disabled = { exporting }
76- className = "rounded-xl border border-[var(--fs-border)] bg-[var(--fs-background)] px-4 py-3 text-sm font-bold text-[var(--fs-text-primary)] hover:border-[var(--fs-primary)] inline-flex items-center justify-center gap-2 disabled:opacity-60"
105+ onClick = { ( ) => void handleExpenseExport ( ) }
106+ disabled = { exportingExpenses }
107+ className = "inline-flex items-center justify-center gap-2 rounded-xl border border-[var(--fs-border)] bg-[var(--fs-background)] px-4 py-3 text-sm font-bold text-[var(--fs-text-primary)] hover:border-[var(--fs-primary)] disabled:opacity-60"
108+ >
109+ < Download className = "h-4 w-4 text-[var(--fs-primary)]" />
110+ { exportingExpenses ? 'Exporting...' : 'Export expenses CSV' }
111+ </ button >
112+ < button
113+ type = "button"
114+ onClick = { ( ) => void handleBalanceExport ( ) }
115+ disabled = { exportingBalances }
116+ className = "inline-flex items-center justify-center gap-2 rounded-xl border border-[var(--fs-border)] bg-[var(--fs-background)] px-4 py-3 text-sm font-bold text-[var(--fs-text-primary)] hover:border-[var(--fs-primary)] disabled:opacity-60"
77117 >
78- < Download className = "w -4 h -4 text-[var(--fs-primary)]" />
79- { exporting ? 'Exporting...' : 'Export CSV' }
118+ < Scale className = "h -4 w -4 text-[var(--fs-primary)]" />
119+ { exportingBalances ? 'Exporting...' : 'Export balances CSV' }
80120 </ button >
81121 < div className = "rounded-xl border border-[var(--fs-border)] bg-[var(--fs-background)] px-4 py-3 text-sm font-medium text-[var(--fs-text-muted)]" >
82122 Invite teammates from the member panel to keep your ledger accurate.
0 commit comments