Add daily expense tracking with sort/split and CSV export

Standalone expenses (date, description, category, amount) with the same
equal/own/payer split machinery as entry costs, merged into the costs
panel and settle-up as a single expense bucket. Self-fetching card
with per-day grouping, client-side sorting, and quick-add. CSV export
interleaves expenses with priced entries, one share column per member
(UTF-8 BOM, RFC 4180, formula-injection guard on text cells).

Also fixes trip deletion, which hit a foreign-key violation and rolled
back for any trip with checklist items.
This commit is contained in:
2026-08-06 17:55:04 +07:00
parent e342cd9a91
commit f272e74b84
19 changed files with 2123 additions and 6 deletions
+22
View File
@@ -43,6 +43,28 @@ export function entryIcon(entry) {
return typeInfo(entry && entry.type).icon;
}
// Expense categories (daily spending log) — fixed enum, see docs/API.md.
// Display metadata lives here per the API contract ("Display metadata (icon +
// label + colour) lives in the frontend ... not the API").
export const EXPENSE_CATEGORIES = {
food: { label: 'Food', icon: '🍽️', color: '#059669' },
drinks: { label: 'Drinks', icon: '🍹', color: '#0891b2' },
transport: { label: 'Transport', icon: '🚕', color: '#d97706' },
activities: { label: 'Activities', icon: '🎟️', color: '#2563eb' },
shopping: { label: 'Shopping', icon: '🛍️', color: '#db2777' },
accommodation: { label: 'Accommodation', icon: '🏨', color: '#f59e0b' },
other: { label: 'Other', icon: '💸', color: '#64748b' },
};
export const EXPENSE_CATEGORY_LIST = Object.entries(EXPENSE_CATEGORIES).map(([value, meta]) => ({
value,
...meta,
}));
export function expenseCategoryInfo(category) {
return EXPENSE_CATEGORIES[category] || EXPENSE_CATEGORIES.other;
}
// Split modes with the human labels the day-editor select shows.
export const SPLIT_MODES = [
{ value: 'equal', label: 'Split equally' },