Value record page, improved history, link styling, etc.
Build and Deploy Web App / build-and-deploy (push) Successful in 19s Details

This commit is contained in:
andrewlalis 2025-09-11 12:48:59 -04:00
parent 41acb0dd51
commit 30764ba624
17 changed files with 249 additions and 91 deletions

View File

@ -3,6 +3,7 @@ import { ApiClient } from './base'
import type { Currency } from './data' import type { Currency } from './data'
import type { Page, PageRequest } from './pagination' import type { Page, PageRequest } from './pagination'
import { getSelectedProfile } from './profile' import { getSelectedProfile } from './profile'
import type { Attachment } from './attachment'
export interface AccountType { export interface AccountType {
id: string id: string
@ -87,6 +88,7 @@ export interface AccountValueRecord {
type: AccountValueRecordType type: AccountValueRecordType
value: number value: number
currency: Currency currency: Currency
attachments: Attachment[]
} }
export interface AccountValueRecordCreationPayload { export interface AccountValueRecordCreationPayload {
@ -103,6 +105,14 @@ export enum AccountHistoryItemType {
JOURNAL_ENTRY = 'JOURNAL_ENTRY', JOURNAL_ENTRY = 'JOURNAL_ENTRY',
} }
export function accountHistoryItemTypeToDisplayName(type: AccountHistoryItemType) {
if (type === AccountHistoryItemType.TEXT) return 'Text'
if (type === AccountHistoryItemType.PROPERTY_CHANGE) return 'Property Change'
if (type === AccountHistoryItemType.VALUE_RECORD) return 'Value Record'
if (type === AccountHistoryItemType.JOURNAL_ENTRY) return 'Journal Entry'
return 'Unknown'
}
export interface AccountHistoryItem { export interface AccountHistoryItem {
timestamp: string timestamp: string
type: AccountHistoryItemType type: AccountHistoryItemType

View File

@ -2,6 +2,14 @@ import type { RouteLocation } from 'vue-router'
import { ApiClient } from './base' import { ApiClient } from './base'
import { getSelectedProfile } from './profile' import { getSelectedProfile } from './profile'
export interface Attachment {
id: number
uploadedAt: string
filename: string
contentType: string
size: number
}
export class AttachmentApiClient extends ApiClient { export class AttachmentApiClient extends ApiClient {
readonly profileName: string readonly profileName: string

View File

@ -1,3 +1,4 @@
import type { Attachment } from './attachment'
import { ApiClient } from './base' import { ApiClient } from './base'
import type { Currency } from './data' import type { Currency } from './data'
import { type Page, type PageRequest } from './pagination' import { type Page, type PageRequest } from './pagination'
@ -86,7 +87,7 @@ export interface TransactionDetail {
debitedAccount: TransactionDetailAccount | null debitedAccount: TransactionDetailAccount | null
tags: string[] tags: string[]
lineItems: TransactionDetailLineItem[] lineItems: TransactionDetailLineItem[]
attachments: TransactionDetailAttachment[] attachments: Attachment[]
} }
export interface TransactionDetailAccount { export interface TransactionDetailAccount {
@ -104,14 +105,6 @@ export interface TransactionDetailLineItem {
category: TransactionCategory | null category: TransactionCategory | null
} }
export interface TransactionDetailAttachment {
id: number
uploadedAt: string
filename: string
contentType: string
size: number
}
export interface AddTransactionPayload { export interface AddTransactionPayload {
timestamp: string timestamp: string
amount: number amount: number

View File

@ -1,5 +1,6 @@
@import url('@/assets/styles/fonts.css'); @import url('@/assets/styles/fonts.css');
@import url('@/assets/styles/text.css'); @import url('@/assets/styles/text.css');
@import url('@/assets/styles/spacing.css');
:root { :root {
--theme-primary: #113188; --theme-primary: #113188;
@ -12,6 +13,7 @@
--text: rgb(247, 247, 247); --text: rgb(247, 247, 247);
--text-muted: gray; --text-muted: gray;
--text-link: #6c8eff;
--positive: rgb(59, 219, 44); --positive: rgb(59, 219, 44);
--negative: rgb(253, 52, 52); --negative: rgb(253, 52, 52);
--warning: rgb(255, 187, 0); --warning: rgb(255, 187, 0);
@ -26,11 +28,11 @@ body {
} }
a { a {
color: var(--theme-primary); color: var(--text-link);
text-decoration: none; text-decoration: none;
} }
a:hover { a:hover {
color: var(--theme-tertiary); color: var(--text-tertiary);
text-decoration: underline; text-decoration: underline;
} }

View File

@ -0,0 +1,11 @@
.p0 {
padding: 0;
}
.m0 {
margin: 0;
}
.mt-1 {
margin-top: 0.5rem;
}

View File

@ -41,3 +41,7 @@
.align-left { .align-left {
text-align: left; text-align: left;
} }
.align-center {
text-align: center;
}

View File

@ -9,6 +9,7 @@ import { AccountApiClient, AccountValueRecordType, type Account, type AccountVal
import { datetimeLocalToISO, getDatetimeLocalValueForNow } from '@/util/time'; import { datetimeLocalToISO, getDatetimeLocalValueForNow } from '@/util/time';
import FileSelector from '@/components/common/FileSelector.vue'; import FileSelector from '@/components/common/FileSelector.vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { showConfirm } from '@/util/alert';
const route = useRoute() const route = useRoute()
const props = defineProps<{ account: Account }>() const props = defineProps<{ account: Account }>()
@ -23,7 +24,11 @@ const attachments: Ref<File[]> = ref([])
async function show(): Promise<AccountValueRecord | undefined> { async function show(): Promise<AccountValueRecord | undefined> {
if (!modal.value) return Promise.resolve(undefined) if (!modal.value) return Promise.resolve(undefined)
timestamp.value = getDatetimeLocalValueForNow() timestamp.value = getDatetimeLocalValueForNow()
amount.value = props.account.currentBalance ?? 0 if (props.account.currentBalance !== null) {
amount.value = props.account.currentBalance / Math.pow(10, props.account.currency.fractionalDigits)
} else {
amount.value = 0
}
savedValueRecord.value = undefined savedValueRecord.value = undefined
const result = await modal.value.show() const result = await modal.value.show()
if (result === 'saved') { if (result === 'saved') {
@ -38,6 +43,11 @@ async function addValueRecord() {
type: AccountValueRecordType.BALANCE, type: AccountValueRecordType.BALANCE,
value: Math.round(amount.value * Math.pow(10, props.account.currency.fractionalDigits)) value: Math.round(amount.value * Math.pow(10, props.account.currency.fractionalDigits))
} }
// Check and confirm with the user if the value they entered doesn't match the expected balance of the account.
if (props.account.currentBalance !== null && payload.value !== props.account.currentBalance) {
const result = await showConfirm("The balance you entered doesn't match the expected current balance for this account. Proceeding to add this value record will result in an incomplete account history and possible reconciliation errors. Are you sure you want to proceed?")
if (!result) return
}
const api = new AccountApiClient(route) const api = new AccountApiClient(route)
try { try {
savedValueRecord.value = await api.createValueRecord(props.account.id, payload, attachments.value) savedValueRecord.value = await api.createValueRecord(props.account.id, payload, attachments.value)

View File

@ -0,0 +1,49 @@
<script setup lang="ts">
import { AttachmentApiClient } from '@/api/attachment';
import { useAuthStore } from '@/stores/auth-store';
import { useRoute } from 'vue-router';
import AppButton from './AppButton.vue';
interface AttachmentInfo {
filename: string
contentType: string
size: number
id?: number
}
const route = useRoute()
const authStore = useAuthStore()
const props = defineProps<{ attachment: AttachmentInfo, disabled?: boolean }>()
defineEmits<{ "deleted": void }>()
function downloadFile() {
const api = new AttachmentApiClient(route)
if (!authStore.state) return
api.downloadAttachment(props.attachment?.id ?? 0, authStore.state.token)
}
</script>
<template>
<div class="attachment-row">
<div style="display: flex; align-items: center; margin-left: 1rem;">
<div>
<div>{{ attachment.filename }}</div>
<div style="font-size: 0.75rem;">{{ attachment.contentType }}</div>
</div>
</div>
<div>
<AppButton icon="download" type="button" @click="downloadFile()" v-if="attachment.id !== undefined" />
<AppButton v-if="!disabled" type="button" icon="trash" @click="$emit('deleted')" />
</div>
</div>
</template>
<style lang="css">
.attachment-row {
background-color: var(--bg);
display: flex;
justify-content: space-between;
padding: 0.25rem;
border-radius: 0.5rem;
margin: 0.5rem 0;
}
</style>

View File

@ -1,9 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref, useTemplateRef, watch, type Ref } from 'vue'; import { onMounted, ref, useTemplateRef, watch, type Ref } from 'vue';
import AppButton from '@/components/common/AppButton.vue'; import AppButton from '@/components/common/AppButton.vue';
import { AttachmentApiClient } from '@/api/attachment'; import AttachmentRow from './AttachmentRow.vue';
import { useRoute } from 'vue-router';
import { useAuthStore } from '@/stores/auth-store';
interface ExistingFile { interface ExistingFile {
id: number id: number
@ -41,8 +39,6 @@ interface Props {
initialFiles?: ExistingFile[] initialFiles?: ExistingFile[]
} }
const route = useRoute()
const authStore = useAuthStore()
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
disabled: false, disabled: false,
initialFiles: () => [] initialFiles: () => []
@ -105,28 +101,11 @@ function onFileDeleteClicked(idx: number) {
} }
files.value.splice(idx, 1) files.value.splice(idx, 1)
} }
function downloadFile(attachmentId: number) {
const api = new AttachmentApiClient(route)
if (!authStore.state) return
api.downloadAttachment(attachmentId, authStore.state.token)
}
</script> </script>
<template> <template>
<div class="file-selector"> <div class="file-selector">
<div @click.prevent=""> <div @click.prevent="">
<div v-for="file, idx in files" :key="idx" class="file-selector-item"> <AttachmentRow v-for="file, idx in files" :key="idx" :attachment="file" @deleted="onFileDeleteClicked(idx)" />
<div style="display: flex; align-items: center; margin-left: 1rem;">
<div>
<div>{{ file.filename }}</div>
<div style="font-size: 0.75rem;">{{ file.contentType }}</div>
</div>
</div>
<div>
<AppButton icon="download" @click="downloadFile(file.id)" v-if="(file instanceof ExistingFileListItem)" />
<AppButton v-if="!disabled" icon="trash" @click="onFileDeleteClicked(idx)" />
</div>
</div>
</div> </div>
<div> <div>
@ -143,13 +122,4 @@ function downloadFile(attachmentId: number) {
.file-selector { .file-selector {
width: 100%; width: 100%;
} }
.file-selector-item {
background-color: var(--bg-secondary);
display: flex;
justify-content: space-between;
padding: 0.25rem;
border-radius: 0.5rem;
margin: 0.5rem 0;
}
</style> </style>

View File

@ -5,28 +5,49 @@ import { onMounted, ref, type Ref } from 'vue';
import ValueRecordHistoryItem from './ValueRecordHistoryItem.vue'; import ValueRecordHistoryItem from './ValueRecordHistoryItem.vue';
import JournalEntryHistoryItem from './JournalEntryHistoryItem.vue'; import JournalEntryHistoryItem from './JournalEntryHistoryItem.vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import AppButton from '../common/AppButton.vue';
import AppBadge from '../common/AppBadge.vue';
const route = useRoute() const route = useRoute()
const props = defineProps<{ accountId: number }>() const props = defineProps<{ accountId: number }>()
const historyItems: Ref<AccountHistoryItem[]> = ref([]) const historyItems: Ref<AccountHistoryItem[]> = ref([])
const canLoadMore = ref(true)
const nextPage: Ref<PageRequest> = ref({ page: 1, size: 10, sorts: [{ attribute: 'timestamp', dir: 'DESC' }] })
onMounted(async () => { onMounted(async () => {
const pageRequest: PageRequest = { page: 1, size: 10, sorts: [{ attribute: 'timestamp', dir: 'DESC' }] } await loadNextPage()
const api = new AccountApiClient(route)
while (true) {
try {
const page = await api.getHistory(props.accountId, pageRequest)
historyItems.value.push(...page.items)
if (page.isLast) return
pageRequest.page++
} catch (err) {
console.error(err)
historyItems.value = []
return
}
}
}) })
async function loadNextPage() {
const api = new AccountApiClient(route)
try {
const page = await api.getHistory(props.accountId, nextPage.value)
historyItems.value.push(...page.items)
canLoadMore.value = !page.isLast
if (canLoadMore.value) {
nextPage.value.page++
}
} catch (err) {
console.error(err)
historyItems.value = []
canLoadMore.value = false
nextPage.value.page = 1
}
}
async function loadAll() {
while (canLoadMore.value) {
await loadNextPage()
}
}
function reload() {
nextPage.value.page = 1
canLoadMore.value = true
historyItems.value = []
loadNextPage()
}
function asVR(i: AccountHistoryItem): AccountHistoryValueRecordItem { function asVR(i: AccountHistoryItem): AccountHistoryValueRecordItem {
return i as AccountHistoryValueRecordItem return i as AccountHistoryValueRecordItem
} }
@ -34,19 +55,27 @@ function asVR(i: AccountHistoryItem): AccountHistoryValueRecordItem {
function asJE(i: AccountHistoryItem): AccountHistoryJournalEntryItem { function asJE(i: AccountHistoryItem): AccountHistoryJournalEntryItem {
return i as AccountHistoryJournalEntryItem return i as AccountHistoryJournalEntryItem
} }
defineExpose({ reload })
</script> </script>
<template> <template>
<div> <div>
<div v-for="item in historyItems" :key="item.timestamp" class="history-item"> <div v-for="item in historyItems" :key="item.timestamp" class="history-item">
<div class="history-item-header"> <div class="history-item-header">
<div class="font-mono font-size-xsmall">{{ new Date(item.timestamp).toLocaleString() }}</div> <div class="font-mono font-size-xsmall text-muted">{{ new Date(item.timestamp).toLocaleString() }}</div>
<div>{{ item.type }}</div>
</div> </div>
<ValueRecordHistoryItem v-if="item.type === AccountHistoryItemType.VALUE_RECORD" :item="asVR(item)" <ValueRecordHistoryItem v-if="item.type === AccountHistoryItemType.VALUE_RECORD" :item="asVR(item)"
:account-id="accountId" /> :account-id="accountId" />
<JournalEntryHistoryItem v-if="item.type === AccountHistoryItemType.JOURNAL_ENTRY" :item="asJE(item)" /> <JournalEntryHistoryItem v-if="item.type === AccountHistoryItemType.JOURNAL_ENTRY" :item="asJE(item)" />
<hr class="m0" />
</div>
<div class="align-center">
<AppButton size="md" @click="loadNextPage()" v-if="canLoadMore">Load more history...</AppButton>
<AppButton size="sm" @click="loadAll()" theme="secondary" v-if="canLoadMore">Load all</AppButton>
<AppBadge v-if="!canLoadMore">This is the start of this account's history.</AppBadge>
</div> </div>
</div> </div>
</template> </template>

View File

@ -3,6 +3,7 @@ import type { AccountHistoryJournalEntryItem } from '@/api/account'
import { formatMoney } from '@/api/data'; import { formatMoney } from '@/api/data';
import { getSelectedProfile } from '@/api/profile'; import { getSelectedProfile } from '@/api/profile';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import AppBadge from '../common/AppBadge.vue';
const route = useRoute() const route = useRoute()
@ -17,9 +18,11 @@ defineProps<{ item: AccountHistoryJournalEntryItem }>()
entered as a entered as a
<strong>{{ item.journalEntryType.toLowerCase() }}</strong> <strong>{{ item.journalEntryType.toLowerCase() }}</strong>
for this account with a value of for this account with a value of
{{ formatMoney(item.amount, item.currency) }}. <AppBadge class="font-mono">{{ formatMoney(item.amount, item.currency) }}</AppBadge>
<br /> <br />
{{ item.transactionDescription }} <p class="font-size-small m0 mt-1">
{{ item.transactionDescription }}
</p>
</div> </div>
</div> </div>
</template> </template>

View File

@ -1,35 +1,26 @@
<script setup lang="ts"> <script setup lang="ts">
import { AccountApiClient, type AccountHistoryValueRecordItem } from '@/api/account'; import { type AccountHistoryValueRecordItem } from '@/api/account';
import { formatMoney } from '@/api/data'; import { formatMoney } from '@/api/data';
import AppButton from '../common/AppButton.vue';
import { showConfirm } from '@/util/alert';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import ButtonBar from '../common/ButtonBar.vue'; import AppBadge from '../common/AppBadge.vue';
import { computed } from 'vue';
import { getSelectedProfile } from '@/api/profile';
const route = useRoute() const route = useRoute()
const props = defineProps<{ item: AccountHistoryValueRecordItem, accountId: number }>() const props = defineProps<{ item: AccountHistoryValueRecordItem, accountId: number }>()
async function deleteValueRecord(id: number) { const valueRecordRoute = computed(() => `/profiles/${getSelectedProfile(route)}/accounts/${props.accountId}/value-records/${props.item.valueRecordId}`)
const confirm = await showConfirm("Are you sure you want to delete this value record? This may affect how your account's balance is calculated.")
if (!confirm) return
const api = new AccountApiClient(route)
try {
await api.deleteValueRecord(props.accountId, id)
} catch (err) {
console.error(err)
}
}
</script> </script>
<template> <template>
<div class="history-item-content"> <div class="history-item-content">
<div> <div>
Value recorded for this account at {{ formatMoney(item.value, item.currency) }} <RouterLink :to="valueRecordRoute">Value recorded</RouterLink> as <AppBadge class="font-mono">{{
formatMoney(item.value,
item.currency) }}
</AppBadge>
</div> </div>
<ButtonBar>
<AppButton type="button" icon="trash" size="sm" @click="deleteValueRecord(item.valueRecordId)">
Delete this record
</AppButton>
</ButtonBar>
</div> </div>
</template> </template>

View File

@ -16,6 +16,7 @@ const route = useRoute()
const router = useRouter() const router = useRouter()
const addValueRecordModal = useTemplateRef("addValueRecordModal") const addValueRecordModal = useTemplateRef("addValueRecordModal")
const history = useTemplateRef('history')
const account: Ref<Account | null> = ref(null) const account: Ref<Account | null> = ref(null)
onMounted(async () => { onMounted(async () => {
@ -45,7 +46,7 @@ async function deleteAccount() {
async function addValueRecord() { async function addValueRecord() {
const result = await addValueRecordModal.value?.show() const result = await addValueRecordModal.value?.show()
if (result) { if (result) {
console.info('Value record added', result) history.value?.reload()
} }
} }
</script> </script>
@ -97,7 +98,7 @@ async function addValueRecord() {
<AppButton icon="trash" @click="deleteAccount()">Delete</AppButton> <AppButton icon="trash" @click="deleteAccount()">Delete</AppButton>
</ButtonBar> </ButtonBar>
<AccountHistory :account-id="account.id" v-if="account" /> <AccountHistory :account-id="account.id" v-if="account" ref="history" />
<AddValueRecordModal v-if="account" :account="account" ref="addValueRecordModal" /> <AddValueRecordModal v-if="account" :account="account" ref="addValueRecordModal" />
</AppPage> </AppPage>

View File

@ -11,6 +11,7 @@ import TagLabel from '@/components/TagLabel.vue';
import { showAlert, showConfirm } from '@/util/alert'; import { showAlert, showConfirm } from '@/util/alert';
import { onMounted, ref, type Ref } from 'vue'; import { onMounted, ref, type Ref } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import AttachmentRow from '@/components/common/AttachmentRow.vue';
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
@ -117,6 +118,11 @@ async function deleteTransaction() {
</tbody> </tbody>
</table> </table>
</div> </div>
<div v-if="transaction.attachments.length > 0">
<h3>Attachments</h3>
<AttachmentRow v-for="a in transaction.attachments" :attachment="a" :key="a.id" disabled />
</div>
<div> <div>
<AppButton icon="wrench" <AppButton icon="wrench"
@click="router.push(`/profiles/${getSelectedProfile(route)}/transactions/${transaction.id}/edit`)"> @click="router.push(`/profiles/${getSelectedProfile(route)}/transactions/${transaction.id}/edit`)">

View File

@ -0,0 +1,69 @@
<script setup lang="ts">
import { AccountApiClient, type AccountValueRecord } from '@/api/account';
import { formatMoney } from '@/api/data';
import { getSelectedProfile } from '@/api/profile';
import AppBadge from '@/components/common/AppBadge.vue';
import AppButton from '@/components/common/AppButton.vue';
import AppPage from '@/components/common/AppPage.vue';
import AttachmentRow from '@/components/common/AttachmentRow.vue';
import ButtonBar from '@/components/common/ButtonBar.vue';
import PropertiesTable from '@/components/PropertiesTable.vue';
import { showConfirm } from '@/util/alert';
import { onMounted, ref, type Ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
const route = useRoute()
const router = useRouter()
const valueRecord: Ref<AccountValueRecord | null> = ref(null)
onMounted(async () => {
const accountId = parseInt(route.params.accountId as string)
const id = parseInt(route.params.valueRecordId as string)
try {
const api = new AccountApiClient(route)
valueRecord.value = await api.getValueRecord(accountId, id)
} catch (err) {
console.error(err)
await router.replace('/')
}
})
async function deleteValueRecord() {
if (!valueRecord.value) return
const confirm = await showConfirm("Are you sure you want to delete this value record? This may affect how your account's balance is calculated.")
if (!confirm) return
const api = new AccountApiClient(route)
try {
await api.deleteValueRecord(valueRecord.value.accountId, valueRecord.value.id)
await router.replace(`/profiles/${getSelectedProfile(route)}/accounts/${valueRecord.value.accountId}`)
} catch (err) {
console.error(err)
}
}
</script>
<template>
<AppPage title="Value Record" v-if="valueRecord">
<PropertiesTable>
<tr>
<th>ID</th>
<td>{{ valueRecord.id }}</td>
</tr>
<tr>
<th>Value</th>
<td>
<AppBadge>{{ formatMoney(valueRecord.value, valueRecord.currency) }}</AppBadge>
</td>
</tr>
</PropertiesTable>
<div v-if="valueRecord.attachments.length > 0">
<h3>Attachments</h3>
<AttachmentRow v-for="a in valueRecord.attachments" :attachment="a" :key="a.id" disabled />
</div>
<ButtonBar>
<AppButton type="button" icon="trash" size="sm" @click="deleteValueRecord()">
Delete this record
</AppButton>
</ButtonBar>
</AppPage>
</template>

View File

@ -3,6 +3,7 @@ import { AccountApiClient, type Account, type CurrencyBalance } from '@/api/acco
import { formatMoney } from '@/api/data' import { formatMoney } from '@/api/data'
import { getSelectedProfile } from '@/api/profile' import { getSelectedProfile } from '@/api/profile'
import AccountCard from '@/components/AccountCard.vue' import AccountCard from '@/components/AccountCard.vue'
import AppBadge from '@/components/common/AppBadge.vue'
import AppButton from '@/components/common/AppButton.vue' import AppButton from '@/components/common/AppButton.vue'
import HomeModule from '@/components/HomeModule.vue' import HomeModule from '@/components/HomeModule.vue'
import { onMounted, ref, type Ref } from 'vue' import { onMounted, ref, type Ref } from 'vue'
@ -33,13 +34,9 @@ onMounted(async () => {
</p> </p>
<div> <div>
<ul> <AppBadge v-for="bal in totalBalances" :key="bal.currency.code">
<li v-for="bal in totalBalances" :key="bal.currency.code"> Total {{ bal.currency.code }}: {{ formatMoney(bal.balance, bal.currency) }}
<span>Total in {{ bal.currency.code }}</span> </AppBadge>
=
<span>{{ formatMoney(bal.balance, bal.currency) }}</span>
</li>
</ul>
</div> </div>
</template> </template>
<template v-slot:actions> <template v-slot:actions>

View File

@ -47,6 +47,11 @@ const router = createRouter({
component: () => import('@/pages/forms/EditAccountPage.vue'), component: () => import('@/pages/forms/EditAccountPage.vue'),
meta: { title: 'Edit Account' }, meta: { title: 'Edit Account' },
}, },
{
path: 'accounts/:accountId/value-records/:valueRecordId',
component: () => import('@/pages/ValueRecordPage.vue'),
meta: { title: 'Value Record' },
},
{ {
path: 'add-account', path: 'add-account',
component: () => import('@/pages/forms/EditAccountPage.vue'), component: () => import('@/pages/forms/EditAccountPage.vue'),