A household bill-splitting app. Two owners split monthly expenses and the app calculates who owes whom. Stack: Spring Boot API + MySQL backend, PrimeVue/Vue 3 frontend.
bill-manager/
├── primevue-ui/ # Vue 3 + PrimeVue frontend (active development)
├── springboot-api/ # Spring Boot REST API
├── mysql/ # MySQL init scripts
├── client/ # Legacy client (not in active use)
├── docker-compose.yaml # Full stack local dev
├── docker-up.sh / docker-down.sh
- Vue 3 with
<script setup lang="ts"> - PrimeVue 4.5.x styled mode with
@primeuix/themesAura preset - Sakai theme conventions — follow patterns from sakai.primevue.org
- No Tailwind — utility classes are hand-rolled in
src/assets/main.cssto mirror Tailwind names
- Aura preset with custom cyan primary palette (Sakai's cyan configuration)
darkModeSelector: '.app-dark'- No global
inputSize— per-componentsize="small"on all form elements
All form elements use size="small": InputText, InputNumber, Select, Button, DataTable, DatePicker.
size="small"works via theBaseInputmixin forwarding to the innerInputText(addsp-inputtext-smclass)- Always use
icon-display="input"withshow-icon— the defaulticonDisplay="button"renders a separate<button>whose height doesn't match the small input - For month/year-only pickers:
view="month"+date-format="MM yy"
Defined utilities (Tailwind-named):
flex, flex-col, items-center, gap-2, gap-4, pt-2,
mb-2, mb-4, mb-6, mt-2, mt-4,
font-medium, font-semibold,
text-sm, text-base, text-lg, text-xl, text-2xl, text-muted-color
Also defined: .p-select-label { line-height: normal; } — fixes Select height inflation caused by body { line-height: 1.5 } inheritance.
Before using a Tailwind class in a template, verify it is in main.css. If it isn't, add it.
Every page uses a <Toolbar> component for its header, with title above it as a separate element:
<div class="font-semibold text-xl mb-4">Page Title</div>
<Toolbar>
<template #start>
<!-- outlined action buttons (Add X, Add Y, etc.) -->
</template>
<template #end>
<!-- primary/save/back actions -->
</template>
</Toolbar>- Action buttons on the left use
outlinedprop - Primary actions (Save, Back) go on the right
- Danger actions (Erase All, Delete) go on the right
- Page layout: one or more
.carddivs stacked vertically — no tabs on any page - Section cards: title only in header (no per-card add buttons — those live in the page Toolbar)
- DataTable row counts go in
#footerslot:<template #footer><span class="text-muted-color text-sm">{{ items.length }} total</span></template> - Paginator counts (Bills page) go in
#paginatorstartwith a hidden mirror in#paginatorendto keep pagination centered - Empty states:
.empty-statewith.piicon,.empty-state-title, and description text referencing the toolbar - Dialogs:
modal,:draggable="false",style="width: 22rem"(or wider as needed), all inner form elementssize="small" - Row hover + click-to-edit on DataTables:
row-hover,style="cursor: pointer",@row-click="handler($event.data)". Delete button uses@click.stopto prevent row-click firing.
When creating a new bill (BillsView), useDefaultsStore().load() is called and the returned defaults.incomes / defaults.expenses are mapped into the bill's incomes / expenses arrays before createBill() is POSTed. Field mapping: DefaultIncome → Income (id=0, owner, description, amount, incomeDate=billDate, month, year, lastUpdated=billDate); DefaultExpense → Expense (id=0, name, amount, paid, hasDetails=false, expenseDate=billDate, month, year, lastUpdated=billDate, details=[]).
Calculation in BillEditView is imperative, not reactive. The computeSummary() function runs only when the Calculate button is clicked. It pushes totals into bill.value and opens a Summary Dialog. There is no watch on summary — the old reactive pattern was intentionally removed.
| Path | Name | View |
|---|---|---|
/ |
bills |
BillsView |
/bills/:id |
bill-edit |
BillEditView |
/chart |
chart |
ChartView |
/search |
search |
SearchView |
/defaults |
defaults |
SystemView |
/data |
data |
DataView |
Sidebar nav items defined in App.vue navItems array.
- BillsView — New Bill modal: DatePicker (
view="month",icon-display="input",size="small") replaces auto-create. - SystemView — Tab → Cards: Removed Tabs. Each section is its own
.card. main.cssutilities: Addedflex-col,pt-2,.p-select-labelline-height fix.- DatePicker
iconDisplay: Standardizedicon-display="input"for all in-form date pickers. - SearchView: Renamed facets → filters in UI and view-layer code (backend
api.tsfield names unchanged). - ChartView: Side-by-side chart cards via CSS grid (
1fr 1fr), responsive breakpoint at 768px. - DataView: Extracted Data Management (export/import/erase) from SystemView into its own page/route.
- Route rename:
/system→/defaults, sidebar label "System" → "Defaults".
- Charts year filter: Added "Last 4 years" as default top option (sentinel value
'last4'); filters bills whereparseInt(year) >= currentYear - 3. Removed "All" option. - New bill pre-population:
confirmAddBillnow callsstore.load()and maps defaults into the new bill's incomes/expenses before POST. - BillEditView — Tab → Cards: Removed Tabs. Summary, Income, Expense are now stacked cards. Then Summary card removed and moved to a Dialog triggered by Calculate button.
- BillEditView — Toolbar header: Title above Toolbar; left: Add Income, Add Expense, Calculate (outlined); right: Back, Save.
- BillEditView — Imperative calculate: Removed reactive
computed(summary)+watch.computeSummary()runs on Calculate click, pushes totals into bill, opens Summary Dialog. - BillEditView — DataTable footers: Income and Expense counts moved from card headers to
#footerslot of each DataTable. - SystemView — Toolbar header: Title above Toolbar; left: Add Owner, Add Income, Add Expense (outlined); right: Save. Per-card add buttons removed.
- DataView — Toolbar: Title above Toolbar; left: Export, Import (outlined); right: Erase All (danger).
- SearchView — Toolbar: Title above Toolbar; left: operator Select + InputText; right: Search button. Placeholder changed to "Search terms…".
- BillsView — Toolbar: Title above Toolbar; right: New button. Row hover + click-to-edit. Delete column header removed. Paginator count in
#paginatorstart/#paginatorend. - ChartView — Toolbar: Title above Toolbar; left: year Select.