Add basic expense list rendering to frontend

This commit is contained in:
2026-01-01 02:23:50 +01:00
parent 17a1c29d76
commit be1544d5d5
3 changed files with 157 additions and 2 deletions

View File

@@ -1,5 +1,8 @@
const TOKEN_KEY = 'mft_token'; const TOKEN_KEY = 'mft_token';
// Store categories for mapping cid to name
let categories = [];
// Validate token with the API // Validate token with the API
async function validateToken(token) { async function validateToken(token) {
try { try {
@@ -95,7 +98,8 @@ document.getElementById('expense-form').addEventListener('submit', async (e) =>
// Reset form // Reset form
document.getElementById('expense-form').reset(); document.getElementById('expense-form').reset();
// TODO: Update local expense view with the new expense // Add to expense list
addExpenseToList(expense);
} catch (error) { } catch (error) {
console.error('Error adding expense:', error); console.error('Error adding expense:', error);
alert(`Failed to add expense: ${error.message}`); alert(`Failed to add expense: ${error.message}`);
@@ -111,6 +115,7 @@ async function showApp() {
document.getElementById('login-section').style.display = 'none'; document.getElementById('login-section').style.display = 'none';
document.getElementById('app-section').style.display = 'block'; document.getElementById('app-section').style.display = 'block';
await loadCategories(); await loadCategories();
await loadExpenses();
} }
async function loadCategories() { async function loadCategories() {
@@ -129,7 +134,7 @@ async function loadCategories() {
throw new Error('Failed to load categories'); throw new Error('Failed to load categories');
} }
const categories = await response.json(); categories = await response.json();
// Clear loading option // Clear loading option
categorySelect.innerHTML = ''; categorySelect.innerHTML = '';
@@ -152,3 +157,89 @@ async function loadCategories() {
categorySelect.innerHTML = '<option value="">Error loading categories</option>'; categorySelect.innerHTML = '<option value="">Error loading categories</option>';
} }
} }
async function loadExpenses() {
const token = localStorage.getItem(TOKEN_KEY);
const listContainer = document.getElementById('expense-list');
try {
const response = await fetch('/api/expenses', {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`
}
});
if (!response.ok) {
throw new Error('Failed to load expenses');
}
const expenses = await response.json();
renderExpenses(expenses);
} catch (error) {
console.error('Error loading expenses:', error);
listContainer.innerHTML = '<p>Error loading expenses</p>';
}
}
function createExpenseCard(expense) {
const category = categories.find(c => c.id === expense.cid);
const categoryName = category ? category.name : 'Unknown';
const amount = (expense.value / 100).toFixed(2);
const timestamp = new Date(expense.ts).toLocaleString();
// Create card structure
const item = document.createElement('div');
item.className = 'expense-item';
item.innerHTML = `
<div class="expense-header">
<span class="expense-category"></span>
<span class="expense-amount"></span>
</div>
<div class="expense-details">
<span class="expense-time"></span>
<span class="expense-note"></span>
</div>
`;
// Populate with textContent
item.querySelector('.expense-category').textContent = categoryName;
item.querySelector('.expense-amount').textContent = `${amount}`;
item.querySelector('.expense-time').textContent = timestamp;
if (expense.note) {
item.querySelector('.expense-note').textContent = expense.note;
} else {
item.querySelector('.expense-note').remove();
}
return item;
}
function renderExpenses(expenses) {
const listContainer = document.getElementById('expense-list');
if (expenses.length === 0) {
listContainer.innerHTML = '<p class="empty-state">No expenses recorded</p>';
return;
}
listContainer.innerHTML = '';
expenses.forEach(expense => {
const card = createExpenseCard(expense);
listContainer.appendChild(card);
});
}
function addExpenseToList(expense) {
const listContainer = document.getElementById('expense-list');
// Remove empty state if it exists
const emptyState = listContainer.querySelector('.empty-state');
if (emptyState) {
listContainer.innerHTML = '';
}
const card = createExpenseCard(expense);
listContainer.insertAdjacentElement('afterbegin', card);
}

View File

@@ -44,6 +44,11 @@
</div> </div>
<button type="submit">Add Expense</button> <button type="submit">Add Expense</button>
</form> </form>
<div id="expense-list-section">
<h2>Recent Expenses</h2>
<div id="expense-list"></div>
</div>
</div> </div>
</div> </div>

View File

@@ -87,3 +87,62 @@ select:focus {
outline: none; outline: none;
border-color: #3498db; border-color: #3498db;
} }
.header-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
#expense-list-section {
margin-top: 40px;
}
.expense-item {
padding: 15px;
margin-bottom: 10px;
border: 1px solid #e0e0e0;
border-radius: 4px;
background: #fafafa;
}
.expense-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.expense-category {
font-weight: 600;
color: #2c3e50;
}
.expense-amount {
font-size: 1.1em;
font-weight: 600;
color: #e74c3c;
}
.expense-details {
display: flex;
gap: 15px;
font-size: 0.9em;
color: #666;
}
.expense-time {
font-style: italic;
}
.expense-note {
color: #555;
}
.empty-state {
text-align: center;
color: #999;
font-style: italic;
padding: 20px;
}