Wire admin add-account endpoint into the UI #46

Merged
padreug merged 12 commits from feat/add-account-ui into main 2026-06-18 10:03:10 +00:00
2 changed files with 44 additions and 11 deletions
Showing only changes of commit 051c9f0c22 - Show all commits

feat(ui): constrain add-account to a root-type dropdown + sub-path

Free-typing the full hierarchical name let admins fat-finger the parent
(wrong/invalid root). Replace the single name field with a required
Account Type select (the 5 valid roots, mirroring _VALID_ACCOUNT_PREFIXES)
plus a sub-account input, a live 'Will create: ...' preview, and
per-segment validation (each part must be a capitalized Beancount
account component). The root prefix is now structurally guaranteed valid.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Padreug 2026-06-16 01:15:06 +02:00

View file

@ -71,7 +71,8 @@ window.app = Vue.createApp({
}, },
addAccountDialog: { addAccountDialog: {
show: false, show: false,
name: '', rootType: 'Expenses',
subPath: '',
description: '', description: '',
loading: false loading: false
}, },
@ -292,6 +293,16 @@ window.app = Vue.createApp({
}) })
return options return options
}, },
accountRootTypes() {
// The five Beancount root account types — the only valid parents.
// Mirrors the server's _VALID_ACCOUNT_PREFIXES.
return ['Assets', 'Liabilities', 'Equity', 'Income', 'Expenses']
},
addAccountFullName() {
const sub = (this.addAccountDialog.subPath || '').trim().replace(/^:+|:+$/g, '')
if (!this.addAccountDialog.rootType || !sub) return ''
return `${this.addAccountDialog.rootType}:${sub}`
},
userOptions() { userOptions() {
const options = [] const options = []
this.users.forEach(user => { this.users.forEach(user => {
@ -573,17 +584,26 @@ window.app = Vue.createApp({
} }
}, },
showAddAccountDialog() { showAddAccountDialog() {
this.addAccountDialog.name = '' this.addAccountDialog.rootType = 'Expenses'
this.addAccountDialog.subPath = ''
this.addAccountDialog.description = '' this.addAccountDialog.description = ''
this.addAccountDialog.show = true this.addAccountDialog.show = true
}, },
async submitAddAccount() { async submitAddAccount() {
const name = (this.addAccountDialog.name || '').trim() const name = this.addAccountFullName
const validPrefixes = ['Assets:', 'Liabilities:', 'Equity:', 'Income:', 'Expenses:'] if (!name) {
if (!validPrefixes.some(p => name.startsWith(p))) { this.$q.notify({type: 'warning', message: 'Enter a sub-account name'})
return
}
// Each segment under the root must be a valid Beancount account
// component: start with an uppercase letter, then letters/digits/hyphens.
const badSegment = name.split(':').slice(1).find(
seg => !/^[A-Z][A-Za-z0-9-]*$/.test(seg)
)
if (badSegment !== undefined) {
this.$q.notify({ this.$q.notify({
type: 'warning', type: 'warning',
message: `Account name must start with one of: ${validPrefixes.join(', ')}` message: `Invalid segment "${badSegment}" — each part must start with a capital letter (letters, digits, hyphens only)`
}) })
return return
} }

View file

@ -1251,15 +1251,28 @@
<q-form @submit="submitAddAccount" class="q-gutter-md"> <q-form @submit="submitAddAccount" class="q-gutter-md">
<div class="text-h6 q-mb-md">Add Account</div> <div class="text-h6 q-mb-md">Add Account</div>
<q-select
filled
dense
v-model="addAccountDialog.rootType"
:options="accountRootTypes"
label="Account Type *"
hint="Top-level category — the only valid parents"
></q-select>
<q-input <q-input
filled filled
dense dense
v-model.trim="addAccountDialog.name" v-model.trim="addAccountDialog.subPath"
label="Account Name *" label="Sub-account *"
placeholder="e.g., Expenses:Services:Domain" placeholder="e.g., Vehicle:Gas"
hint="Full hierarchical name. Must start with Assets:, Liabilities:, Equity:, Income: or Expenses:" hint="Path under the type. Use ':' to nest; capitalize each part."
></q-input> ></q-input>
<div v-if="addAccountFullName" class="text-caption text-grey">
Will create: <span class="text-weight-medium">{% raw %}{{ addAccountFullName }}{% endraw %}</span>
</div>
<q-input <q-input
filled filled
dense dense
@ -1279,7 +1292,7 @@
color="primary" color="primary"
type="submit" type="submit"
:loading="addAccountDialog.loading" :loading="addAccountDialog.loading"
:disable="!addAccountDialog.name" :disable="!addAccountFullName"
> >
Create Account Create Account
</q-btn> </q-btn>