@@ -114,9 +114,14 @@ function getBudgetTrackerHTML() {
114114 <div class="budget-panel transactions-panel">
115115 <div class="transactions-header">
116116 <h3><i class="fas fa-list"></i> History</h3>
117- <button id="btnClearBudgetData" class="btn-clear-budget" type="button">
118- <i class="fas fa-trash-can"></i> Clear All
119- </button>
117+ <div class="transactions-header-actions">
118+ <button id="btnExportBudgetCsv" class="btn-export-budget" type="button" disabled>
119+ <i class="fas fa-file-csv"></i> Export CSV
120+ </button>
121+ <button id="btnClearBudgetData" class="btn-clear-budget" type="button">
122+ <i class="fas fa-trash-can"></i> Clear All
123+ </button>
124+ </div>
120125 </div>
121126
122127 <!-- Search and Filters -->
@@ -545,6 +550,50 @@ function getBudgetTrackerHTML() {
545550 padding-bottom: 0;
546551 }
547552
553+ .transactions-header-actions {
554+ display: flex;
555+ align-items: center;
556+ gap: 0.6rem;
557+ }
558+
559+ .btn-export-budget {
560+ background: transparent;
561+ border: 1px solid var(--success-color, #10b981);
562+ color: var(--success-color, #10b981);
563+ border-radius: 8px;
564+ padding: 0.4rem 0.8rem;
565+ font-size: 0.8rem;
566+ font-weight: 600;
567+ cursor: pointer;
568+ display: flex;
569+ align-items: center;
570+ gap: 0.3rem;
571+ transition: all 0.2s ease;
572+ }
573+
574+ .btn-export-budget:hover:not(:disabled) {
575+ background: var(--success-color, #10b981);
576+ color: white;
577+ box-shadow: 0 2px 8px rgba(16, 185, 129, 0.25);
578+ }
579+
580+ .btn-export-budget:active:not(:disabled) {
581+ transform: scale(0.96);
582+ }
583+
584+ .btn-export-budget:disabled {
585+ opacity: 0.45;
586+ cursor: not-allowed;
587+ border-color: var(--border-color);
588+ color: var(--text-secondary);
589+ }
590+
591+ .btn-export-budget.exported {
592+ background: var(--success-color, #10b981);
593+ color: white;
594+ border-color: var(--success-color, #10b981);
595+ }
596+
548597 .btn-clear-budget {
549598 background: transparent;
550599 border: 1px solid #ef4444;
@@ -864,6 +913,7 @@ function initBudgetTracker() {
864913 const searchInput = document . getElementById ( "budgetSearchInput" ) ;
865914 const filterType = document . getElementById ( "budgetFilterType" ) ;
866915 const clearBtn = document . getElementById ( "btnClearBudgetData" ) ;
916+ const exportBtn = document . getElementById ( "btnExportBudgetCsv" ) ;
867917
868918 // Exit early if elements not found
869919 if ( ! form || ! categorySelect || ! transactionsList ) return ;
@@ -983,7 +1033,64 @@ function initBudgetTracker() {
9831033 }
9841034 } ) ;
9851035
1036+ exportBtn . addEventListener ( "click" , exportTransactionsToCsv ) ;
1037+
9861038 // Helper functions
1039+ function csvEscape ( value ) {
1040+ const str = String ( value === undefined || value === null ? "" : value ) ;
1041+ if ( / [ " , \n ] / . test ( str ) ) {
1042+ return `"${ str . replace ( / " / g, '""' ) } "` ;
1043+ }
1044+ return str ;
1045+ }
1046+
1047+ function exportTransactionsToCsv ( ) {
1048+ if ( ! transactions . length ) return ;
1049+
1050+ const header = [ "Date" , "Type" , "Category" , "Description" , "Amount (INR)" ] ;
1051+ const rows = transactions . map ( t => [
1052+ t . date ,
1053+ t . type ,
1054+ t . category ,
1055+ t . description || "" ,
1056+ t . amount . toFixed ( 2 )
1057+ ] ) ;
1058+
1059+ const csvContent = [ header , ...rows ]
1060+ . map ( row => row . map ( csvEscape ) . join ( "," ) )
1061+ . join ( "\r\n" ) ;
1062+
1063+ const blob = new Blob ( [ csvContent ] , { type : "text/csv;charset=utf-8;" } ) ;
1064+ const url = URL . createObjectURL ( blob ) ;
1065+
1066+ const now = new Date ( ) ;
1067+ const dateStr = now . getFullYear ( ) + '-' +
1068+ String ( now . getMonth ( ) + 1 ) . padStart ( 2 , '0' ) + '-' +
1069+ String ( now . getDate ( ) ) . padStart ( 2 , '0' ) ;
1070+
1071+ const link = document . createElement ( "a" ) ;
1072+ link . href = url ;
1073+ link . download = `budget-tracker-${ dateStr } .csv` ;
1074+ document . body . appendChild ( link ) ;
1075+ link . click ( ) ;
1076+ document . body . removeChild ( link ) ;
1077+
1078+ // Free the blob URL once the download has been triggered
1079+ setTimeout ( ( ) => URL . revokeObjectURL ( url ) , 100 ) ;
1080+
1081+ // Brief visual confirmation
1082+ const originalHTML = exportBtn . innerHTML ;
1083+ exportBtn . classList . add ( "exported" ) ;
1084+ exportBtn . innerHTML = '<i class="fas fa-check"></i> Downloaded!' ;
1085+ exportBtn . disabled = true ;
1086+
1087+ setTimeout ( ( ) => {
1088+ exportBtn . classList . remove ( "exported" ) ;
1089+ exportBtn . innerHTML = originalHTML ;
1090+ exportBtn . disabled = transactions . length === 0 ;
1091+ } , 2000 ) ;
1092+ }
1093+
9871094 function onTypeChange ( ) {
9881095 populateCategories ( ) ;
9891096 }
@@ -1018,6 +1125,9 @@ function initBudgetTracker() {
10181125 updateSummary ( ) ;
10191126 renderBreakdown ( ) ;
10201127 renderTransactions ( ) ;
1128+ if ( exportBtn ) {
1129+ exportBtn . disabled = transactions . length === 0 ;
1130+ }
10211131 }
10221132
10231133 function updateSummary ( ) {
@@ -1152,4 +1262,4 @@ function initBudgetTracker() {
11521262 transactionsList . appendChild ( row ) ;
11531263 } ) ;
11541264 }
1155- }
1265+ }
0 commit comments