@@ -305,7 +305,7 @@ async function handleFetchResponse(response) {
305305
306306 // Check content type
307307 const contentType = response . headers . get ( 'content-type' ) ;
308- if ( ! contentType || ( ! contentType . includes ( 'application/json' ) && ! contentType . includes ( ' text/csv') ) ) {
308+ if ( ! contentType || ( ! contentType . includes ( 'application/json' ) && contentType . split ( ';' ) [ 0 ] . trim ( ) !== ' text/csv') ) {
309309 debugLog ( 'Response is not JSON or CSV, session likely expired' ) ;
310310 window . location . href = joinPath ( 'login' ) ;
311311 return null ;
@@ -1000,6 +1000,85 @@ async function initMainPage() {
10001000 }
10011001 } ) ;
10021002
1003+ // Export to PDF
1004+ document . getElementById ( 'exportPdfBtn' ) . addEventListener ( 'click' , async ( ) => {
1005+ try {
1006+ const startDate = document . getElementById ( 'startDate' ) . value ;
1007+ const endDate = document . getElementById ( 'endDate' ) . value ;
1008+
1009+ // Get instance name from server
1010+ const configResponse = await fetch ( joinPath ( 'api/config' ) , fetchConfig ) ;
1011+ await handleFetchResponse ( configResponse ) ;
1012+ const config = await configResponse . json ( ) ;
1013+ const instanceName = config . instanceName || 'DumbBudget' ;
1014+
1015+ // Get the current totals
1016+ const response = await fetch ( joinPath ( `api/totals/range?start=${ startDate } &end=${ endDate } ` ) , fetchConfig ) ;
1017+ await handleFetchResponse ( response ) ;
1018+ const totals = await response . json ( ) ;
1019+
1020+ // Get transactions
1021+ const transactionsResponse = await fetch ( joinPath ( `api/transactions/range?start=${ startDate } &end=${ endDate } ` ) , fetchConfig ) ;
1022+ await handleFetchResponse ( transactionsResponse ) ;
1023+ const transactions = await transactionsResponse . json ( ) ;
1024+
1025+ // Create PDF
1026+ const { jsPDF } = window . jspdf ;
1027+ const doc = new jsPDF ( ) ;
1028+
1029+ // Set font
1030+ doc . setFont ( 'helvetica' ) ;
1031+
1032+ // Add title
1033+ doc . setFontSize ( 20 ) ;
1034+ doc . text ( instanceName , 20 , 20 ) ;
1035+
1036+ // Add date range
1037+ doc . setFontSize ( 12 ) ;
1038+ doc . text ( `Date Range: ${ startDate } to ${ endDate } ` , 20 , 30 ) ;
1039+
1040+ // Add totals section
1041+ doc . setFontSize ( 14 ) ;
1042+ doc . text ( 'Summary' , 20 , 45 ) ;
1043+ doc . setFontSize ( 12 ) ;
1044+ doc . text ( `Total Income: ${ formatCurrency ( totals . income ) } ` , 20 , 55 ) ;
1045+ doc . text ( `Total Expenses: ${ formatCurrency ( totals . expenses ) } ` , 20 , 65 ) ;
1046+ doc . text ( `Balance: ${ formatCurrency ( totals . balance ) } ` , 20 , 75 ) ;
1047+
1048+ // Add transactions table
1049+ const tableData = transactions . map ( t => [
1050+ t . date ,
1051+ t . description ,
1052+ t . category || '-' ,
1053+ formatCurrency ( t . type === 'expense' ? - t . amount : t . amount ) ,
1054+ t . type
1055+ ] ) ;
1056+
1057+ doc . autoTable ( {
1058+ startY : 85 ,
1059+ head : [ [ 'Date' , 'Description' , 'Category' , 'Amount' , 'Type' ] ] ,
1060+ body : tableData ,
1061+ theme : 'grid' ,
1062+ headStyles : { fillColor : [ 66 , 66 , 66 ] } ,
1063+ styles : { fontSize : 10 } ,
1064+ columnStyles : {
1065+ 0 : { cellWidth : 30 } , // Date
1066+ 1 : { cellWidth : 60 } , // Description
1067+ 2 : { cellWidth : 30 } , // Category
1068+ 3 : { cellWidth : 30 } , // Amount
1069+ 4 : { cellWidth : 20 } // Type
1070+ }
1071+ } ) ;
1072+
1073+ // Save the PDF
1074+ doc . save ( `transactions-${ startDate } -to-${ endDate } .pdf` ) ;
1075+
1076+ } catch ( error ) {
1077+ console . error ( 'Error exporting to PDF:' , error ) ;
1078+ toastManager . show ( 'Failed to export to PDF. Please try again.' , 'error' ) ;
1079+ }
1080+ } ) ;
1081+
10031082 // Add filter button handlers
10041083 const filterButtons = document . querySelectorAll ( '.filter-btn' ) ;
10051084 filterButtons . forEach ( btn => {
0 commit comments