Skip to content

Commit f7eb792

Browse files
authored
Merge pull request #30 from DumbWareio/Add-PDF-export-and-reformat-download/export-buttons
Added PDF export
2 parents c091023 + f72f4f4 commit f7eb792

File tree

4 files changed

+192
-26
lines changed

4 files changed

+192
-26
lines changed

public/assets/styles.css

Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -614,36 +614,77 @@ header {
614614

615615
/* Footer */
616616
footer {
617-
text-align: center;
618-
padding-top: 0.75rem;
619-
border-top: 1px solid var(--border);
620-
margin-top: auto;
617+
display: flex;
618+
justify-content: center;
619+
align-items: center;
620+
margin-top: 2rem;
621+
padding: 1rem;
622+
width: 100%;
621623
}
622624

623-
#exportBtn {
624-
background: none;
625-
border: none;
626-
color: var(--text);
625+
.export-buttons {
626+
display: flex;
627+
gap: 0.5rem;
628+
align-items: center;
629+
justify-content: center;
630+
background: var(--container);
627631
padding: 0.5rem;
632+
border-radius: 6px;
633+
border: 1px solid var(--border);
634+
margin: 0 auto;
635+
width: fit-content;
636+
}
637+
638+
.export-btn {
639+
all: unset;
640+
background: transparent;
641+
border: 1px solid var(--border);
642+
padding: 0.5rem 1rem;
628643
cursor: pointer;
629-
border-radius: 50%;
630-
transition: all var(--transition);
644+
color: var(--text);
645+
border-radius: 4px;
646+
transition: all 0.2s;
647+
font-size: 0.9rem;
648+
min-width: 60px;
649+
text-align: center;
650+
display: inline-flex;
651+
align-items: center;
652+
justify-content: center;
653+
height: 32px;
654+
box-sizing: border-box;
655+
}
656+
657+
.export-btn:hover {
658+
background-color: var(--hover-color);
659+
border-color: var(--accent-color);
660+
}
661+
662+
.export-btn:active {
663+
transform: translateY(1px);
664+
}
665+
666+
/* Remove these specific styles */
667+
#exportBtn {
631668
display: flex;
632669
align-items: center;
633670
justify-content: center;
634-
margin: 0 auto;
671+
padding: 0.5rem;
672+
background: none;
673+
border: none;
674+
cursor: pointer;
675+
color: var(--text);
676+
border-radius: 4px;
677+
transition: background-color 0.2s;
635678
}
636679

637680
#exportBtn:hover {
638-
background: rgba(128, 128, 128, 0.1);
681+
background-color: rgba(128, 128, 128, 0.1);
639682
}
640683

641684
#exportBtn svg {
642-
width: 20px;
643-
height: 20px;
644-
stroke: var(--text);
645-
stroke-width: 2;
646-
transition: stroke var(--transition);
685+
width: 24px;
686+
height: 24px;
687+
stroke: currentColor;
647688
}
648689

649690
/* Responsive adjustments */

public/index.html

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -117,13 +117,10 @@ <h3>Transactions</h3>
117117
</section>
118118

119119
<footer>
120-
<button id="exportBtn" aria-label="Export CSV">
121-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
122-
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
123-
<polyline points="7 10 12 15 17 10"/>
124-
<line x1="12" y1="15" x2="12" y2="3"/>
125-
</svg>
126-
</button>
120+
<div class="export-buttons">
121+
<button id="exportBtn" class="export-btn">CSV</button>
122+
<button id="exportPdfBtn" class="export-btn">PDF</button>
123+
</div>
127124
</footer>
128125
</div>
129126
<div id="toast-container" class="toast-container"></div>
@@ -165,6 +162,8 @@ <h3>Transactions</h3>
165162
</div>
166163
</div>
167164

168-
<script src="script.js" type="module"></script>
165+
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
166+
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.5.31/jspdf.plugin.autotable.min.js"></script>
167+
<script type="module" src="script.js"></script>
169168
</body>
170169
</html>

public/script.js

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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 => {

public/styles.css

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
footer {
2+
display: flex;
3+
justify-content: center;
4+
margin-top: 2rem;
5+
padding: 1rem;
6+
text-align: center;
7+
}
8+
9+
.export-buttons {
10+
display: inline-flex;
11+
gap: 0.5rem;
12+
align-items: center;
13+
justify-content: center;
14+
background: var(--background);
15+
padding: 0.5rem;
16+
border-radius: 6px;
17+
border: 1px solid var(--border);
18+
margin: 0 auto;
19+
}
20+
21+
.export-btn {
22+
background: transparent;
23+
border: 1px solid var(--border);
24+
padding: 0.5rem 1rem;
25+
cursor: pointer;
26+
color: var(--text);
27+
border-radius: 4px;
28+
transition: all 0.2s;
29+
font-size: 0.9rem;
30+
min-width: 60px;
31+
text-align: center;
32+
display: inline-flex;
33+
align-items: center;
34+
justify-content: center;
35+
height: 32px;
36+
box-sizing: border-box;
37+
margin: 0;
38+
}
39+
40+
.export-btn:hover {
41+
background-color: var(--hover-color);
42+
border-color: var(--accent-color);
43+
}
44+
45+
.export-btn:active {
46+
transform: translateY(1px);
47+
}

0 commit comments

Comments
 (0)