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>
This commit is contained in:
parent
caef3cf5e8
commit
051c9f0c22
2 changed files with 44 additions and 11 deletions
|
|
@ -71,7 +71,8 @@ window.app = Vue.createApp({
|
|||
},
|
||||
addAccountDialog: {
|
||||
show: false,
|
||||
name: '',
|
||||
rootType: 'Expenses',
|
||||
subPath: '',
|
||||
description: '',
|
||||
loading: false
|
||||
},
|
||||
|
|
@ -292,6 +293,16 @@ window.app = Vue.createApp({
|
|||
})
|
||||
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() {
|
||||
const options = []
|
||||
this.users.forEach(user => {
|
||||
|
|
@ -573,17 +584,26 @@ window.app = Vue.createApp({
|
|||
}
|
||||
},
|
||||
showAddAccountDialog() {
|
||||
this.addAccountDialog.name = ''
|
||||
this.addAccountDialog.rootType = 'Expenses'
|
||||
this.addAccountDialog.subPath = ''
|
||||
this.addAccountDialog.description = ''
|
||||
this.addAccountDialog.show = true
|
||||
},
|
||||
async submitAddAccount() {
|
||||
const name = (this.addAccountDialog.name || '').trim()
|
||||
const validPrefixes = ['Assets:', 'Liabilities:', 'Equity:', 'Income:', 'Expenses:']
|
||||
if (!validPrefixes.some(p => name.startsWith(p))) {
|
||||
const name = this.addAccountFullName
|
||||
if (!name) {
|
||||
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({
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1251,15 +1251,28 @@
|
|||
<q-form @submit="submitAddAccount" class="q-gutter-md">
|
||||
<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
|
||||
filled
|
||||
dense
|
||||
v-model.trim="addAccountDialog.name"
|
||||
label="Account Name *"
|
||||
placeholder="e.g., Expenses:Services:Domain"
|
||||
hint="Full hierarchical name. Must start with Assets:, Liabilities:, Equity:, Income: or Expenses:"
|
||||
v-model.trim="addAccountDialog.subPath"
|
||||
label="Sub-account *"
|
||||
placeholder="e.g., Vehicle:Gas"
|
||||
hint="Path under the type. Use ':' to nest; capitalize each part."
|
||||
></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
|
||||
filled
|
||||
dense
|
||||
|
|
@ -1279,7 +1292,7 @@
|
|||
color="primary"
|
||||
type="submit"
|
||||
:loading="addAccountDialog.loading"
|
||||
:disable="!addAccountDialog.name"
|
||||
:disable="!addAccountFullName"
|
||||
>
|
||||
Create Account
|
||||
</q-btn>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue