diff --git a/mft/static/app.js b/mft/static/app.js index 24e2964..ddbda53 100644 --- a/mft/static/app.js +++ b/mft/static/app.js @@ -243,3 +243,170 @@ function addExpenseToList(expense) { const card = createExpenseCard(expense); listContainer.insertAdjacentElement('afterbegin', card); } + +// Tab switching +document.querySelectorAll('.tab-button').forEach(button => { + button.addEventListener('click', () => { + const targetTab = button.dataset.tab; + + // Update button states + document.querySelectorAll('.tab-button').forEach(btn => { + btn.classList.remove('active'); + }); + button.classList.add('active'); + + // Update content visibility + document.querySelectorAll('.tab-content').forEach(content => { + content.classList.remove('active'); + }); + document.getElementById(`${targetTab}-tab`).classList.add('active'); + + // Load statistics when switching to statistics tab + if (targetTab === 'statistics') { + loadWeeklyTotals(); + } + }); +}); + +// Weekly totals functionality +async function loadWeeklyTotals() { + const token = localStorage.getItem(TOKEN_KEY); + const container = document.getElementById('weekly-totals'); + + try { + const response = await fetch('/api/statistics/totals?granularity=weekly', { + method: 'GET', + headers: { + 'Authorization': `Bearer ${token}` + } + }); + + if (!response.ok) { + throw new Error('Failed to load weekly totals'); + } + + const weeklyData = await response.json(); + renderWeeklyTotals(weeklyData); + } catch (error) { + console.error('Error loading weekly totals:', error); + container.innerHTML = '
Error loading weekly totals
'; + } +} + +function formatDateRange(fromDate, toDate) { + const from = new Date(fromDate); + const to = new Date(toDate); + + const options = { month: 'short', day: 'numeric' }; + const fromStr = from.toLocaleDateString('en-US', options); + const toStr = to.toLocaleDateString('en-US', options); + + return `${fromStr} - ${toStr}`; +} + +function renderWeeklyTotals(weeklyData) { + const container = document.getElementById('weekly-totals'); + + if (weeklyData.length === 0) { + container.innerHTML = 'No expense data available
'; + return; + } + + const table = document.createElement('table'); + table.className = 'totals-table'; + + const tbody = document.createElement('tbody'); + + // Reverse to show newest weeks first + weeklyData.slice().reverse().forEach(week => { + // Main week row + const weekRow = document.createElement('tr'); + weekRow.className = 'week-row'; + weekRow.dataset.week = week.week; + + const dateCell = document.createElement('td'); + dateCell.className = 'week-date'; + dateCell.innerHTML = `${formatDateRange(week.from_date, week.to_date)}`; + + const totalCell = document.createElement('td'); + totalCell.className = 'week-total'; + totalCell.textContent = `€${(week.total / 100).toFixed(2)}`; + + weekRow.appendChild(dateCell); + weekRow.appendChild(totalCell); + + // Categories row (hidden by default) + const categoriesRow = document.createElement('tr'); + categoriesRow.className = 'categories-row'; + categoriesRow.dataset.week = week.week; + + const categoriesCell = document.createElement('td'); + categoriesCell.colSpan = 2; + + const categoriesContent = document.createElement('div'); + categoriesContent.className = 'categories-content'; + + if (week.by_category.length === 0) { + categoriesContent.innerHTML = 'No expenses this week
'; + } else { + week.by_category.forEach(category => { + const categoryItem = document.createElement('div'); + categoryItem.className = 'category-item'; + + const nameSpan = document.createElement('span'); + nameSpan.className = 'category-name'; + nameSpan.textContent = category.name; + + const amountSpan = document.createElement('span'); + amountSpan.className = 'category-amount'; + amountSpan.textContent = `€${(category.total / 100).toFixed(2)}`; + + categoryItem.appendChild(nameSpan); + categoryItem.appendChild(amountSpan); + categoriesContent.appendChild(categoryItem); + }); + } + + categoriesCell.appendChild(categoriesContent); + categoriesRow.appendChild(categoriesCell); + + // Add click handler for expansion + weekRow.addEventListener('click', () => { + toggleWeekExpansion(week.week); + }); + + tbody.appendChild(weekRow); + tbody.appendChild(categoriesRow); + }); + + table.appendChild(tbody); + container.innerHTML = ''; + container.appendChild(table); +} + +function toggleWeekExpansion(weekId) { + const weekRow = document.querySelector(`.week-row[data-week="${weekId}"]`); + const categoriesRow = document.querySelector(`.categories-row[data-week="${weekId}"]`); + + weekRow.classList.toggle('expanded'); + categoriesRow.classList.toggle('visible'); +} + +// Expand/collapse all buttons +document.getElementById('expand-all-btn').addEventListener('click', () => { + document.querySelectorAll('.week-row').forEach(row => { + row.classList.add('expanded'); + }); + document.querySelectorAll('.categories-row').forEach(row => { + row.classList.add('visible'); + }); +}); + +document.getElementById('collapse-all-btn').addEventListener('click', () => { + document.querySelectorAll('.week-row').forEach(row => { + row.classList.remove('expanded'); + }); + document.querySelectorAll('.categories-row').forEach(row => { + row.classList.remove('visible'); + }); +}); diff --git a/mft/static/index.html b/mft/static/index.html index a985ee2..9174bcd 100644 --- a/mft/static/index.html +++ b/mft/static/index.html @@ -23,31 +23,50 @@ diff --git a/mft/static/style.css b/mft/static/style.css index 8a44590..8789bd3 100644 --- a/mft/static/style.css +++ b/mft/static/style.css @@ -146,3 +146,143 @@ select:focus { font-style: italic; padding: 20px; } + +.tabs { + display: flex; + gap: 10px; + margin-bottom: 30px; + border-bottom: 2px solid #e0e0e0; +} + +.tab-button { + background: transparent; + color: #666; + border: none; + padding: 10px 20px; + font-size: 16px; + cursor: pointer; + border-bottom: 3px solid transparent; + margin-bottom: -2px; + transition: all 0.2s; +} + +.tab-button:hover { + background: transparent; + color: #3498db; + transform: none; +} + +.tab-button.active { + color: #3498db; + border-bottom-color: #3498db; + font-weight: 600; +} + +.tab-content { + display: none; +} + +.tab-content.active { + display: block; +} + +.stats-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; +} + +.stats-controls { + display: flex; + gap: 10px; +} + +.secondary-btn { + background: #95a5a6; + padding: 8px 16px; + font-size: 14px; +} + +.secondary-btn:hover { + background: #7f8c8d; +} + +.totals-table { + width: 100%; + border-collapse: collapse; +} + +.week-row { + cursor: pointer; + user-select: none; +} + +.week-row td { + padding: 15px; + border-bottom: 1px solid #e0e0e0; +} + +.week-row:hover { + background: #f8f9fa; +} + +.week-date { + font-weight: 500; + color: #2c3e50; +} + +.week-total { + text-align: right; + font-size: 1.1em; + font-weight: 600; + color: #e74c3c; +} + +.expand-icon { + display: inline-block; + margin-right: 8px; + transition: transform 0.2s; + font-size: 0.8em; +} + +.week-row.expanded .expand-icon { + transform: rotate(90deg); +} + +.categories-row { + display: none; + background: #f8f9fa; +} + +.categories-row.visible { + display: table-row; +} + +.categories-row td { + padding: 0; +} + +.categories-content { + padding: 10px 15px 15px 40px; +} + +.category-item { + display: flex; + justify-content: space-between; + padding: 8px 0; + border-bottom: 1px solid #e8e8e8; +} + +.category-item:last-child { + border-bottom: none; +} + +.category-name { + color: #555; +} + +.category-amount { + font-weight: 500; + color: #666; +}