Value record page, improved history, link styling, etc.
Build and Deploy Web App / build-and-deploy (push) Successful in 19s
Details
Build and Deploy Web App / build-and-deploy (push) Successful in 19s
Details
This commit is contained in:
parent
41acb0dd51
commit
30764ba624
|
|
@ -3,6 +3,7 @@ import { ApiClient } from './base'
|
|||
import type { Currency } from './data'
|
||||
import type { Page, PageRequest } from './pagination'
|
||||
import { getSelectedProfile } from './profile'
|
||||
import type { Attachment } from './attachment'
|
||||
|
||||
export interface AccountType {
|
||||
id: string
|
||||
|
|
@ -87,6 +88,7 @@ export interface AccountValueRecord {
|
|||
type: AccountValueRecordType
|
||||
value: number
|
||||
currency: Currency
|
||||
attachments: Attachment[]
|
||||
}
|
||||
|
||||
export interface AccountValueRecordCreationPayload {
|
||||
|
|
@ -103,6 +105,14 @@ export enum AccountHistoryItemType {
|
|||
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 {
|
||||
timestamp: string
|
||||
type: AccountHistoryItemType
|
||||
|
|
|
|||
|
|
@ -2,6 +2,14 @@ import type { RouteLocation } from 'vue-router'
|
|||
import { ApiClient } from './base'
|
||||
import { getSelectedProfile } from './profile'
|
||||
|
||||
export interface Attachment {
|
||||
id: number
|
||||
uploadedAt: string
|
||||
filename: string
|
||||
contentType: string
|
||||
size: number
|
||||
}
|
||||
|
||||
export class AttachmentApiClient extends ApiClient {
|
||||
readonly profileName: string
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { Attachment } from './attachment'
|
||||
import { ApiClient } from './base'
|
||||
import type { Currency } from './data'
|
||||
import { type Page, type PageRequest } from './pagination'
|
||||
|
|
@ -86,7 +87,7 @@ export interface TransactionDetail {
|
|||
debitedAccount: TransactionDetailAccount | null
|
||||
tags: string[]
|
||||
lineItems: TransactionDetailLineItem[]
|
||||
attachments: TransactionDetailAttachment[]
|
||||
attachments: Attachment[]
|
||||
}
|
||||
|
||||
export interface TransactionDetailAccount {
|
||||
|
|
@ -104,14 +105,6 @@ export interface TransactionDetailLineItem {
|
|||
category: TransactionCategory | null
|
||||
}
|
||||
|
||||
export interface TransactionDetailAttachment {
|
||||
id: number
|
||||
uploadedAt: string
|
||||
filename: string
|
||||
contentType: string
|
||||
size: number
|
||||
}
|
||||
|
||||
export interface AddTransactionPayload {
|
||||
timestamp: string
|
||||
amount: number
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
@import url('@/assets/styles/fonts.css');
|
||||
@import url('@/assets/styles/text.css');
|
||||
@import url('@/assets/styles/spacing.css');
|
||||
|
||||
:root {
|
||||
--theme-primary: #113188;
|
||||
|
|
@ -12,6 +13,7 @@
|
|||
|
||||
--text: rgb(247, 247, 247);
|
||||
--text-muted: gray;
|
||||
--text-link: #6c8eff;
|
||||
--positive: rgb(59, 219, 44);
|
||||
--negative: rgb(253, 52, 52);
|
||||
--warning: rgb(255, 187, 0);
|
||||
|
|
@ -26,11 +28,11 @@ body {
|
|||
}
|
||||
|
||||
a {
|
||||
color: var(--theme-primary);
|
||||
color: var(--text-link);
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
color: var(--theme-tertiary);
|
||||
color: var(--text-tertiary);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
.p0 {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.m0 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.mt-1 {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
|
@ -41,3 +41,7 @@
|
|||
.align-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.align-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { AccountApiClient, AccountValueRecordType, type Account, type AccountVal
|
|||
import { datetimeLocalToISO, getDatetimeLocalValueForNow } from '@/util/time';
|
||||
import FileSelector from '@/components/common/FileSelector.vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { showConfirm } from '@/util/alert';
|
||||
|
||||
const route = useRoute()
|
||||
const props = defineProps<{ account: Account }>()
|
||||
|
|
@ -23,7 +24,11 @@ const attachments: Ref<File[]> = ref([])
|
|||
async function show(): Promise<AccountValueRecord | undefined> {
|
||||
if (!modal.value) return Promise.resolve(undefined)
|
||||
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
|
||||
const result = await modal.value.show()
|
||||
if (result === 'saved') {
|
||||
|
|
@ -38,6 +43,11 @@ async function addValueRecord() {
|
|||
type: AccountValueRecordType.BALANCE,
|
||||
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)
|
||||
try {
|
||||
savedValueRecord.value = await api.createValueRecord(props.account.id, payload, attachments.value)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -1,9 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { onMounted, ref, useTemplateRef, watch, type Ref } from 'vue';
|
||||
import AppButton from '@/components/common/AppButton.vue';
|
||||
import { AttachmentApiClient } from '@/api/attachment';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useAuthStore } from '@/stores/auth-store';
|
||||
import AttachmentRow from './AttachmentRow.vue';
|
||||
|
||||
interface ExistingFile {
|
||||
id: number
|
||||
|
|
@ -41,8 +39,6 @@ interface Props {
|
|||
initialFiles?: ExistingFile[]
|
||||
}
|
||||
|
||||
const route = useRoute()
|
||||
const authStore = useAuthStore()
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
disabled: false,
|
||||
initialFiles: () => []
|
||||
|
|
@ -105,28 +101,11 @@ function onFileDeleteClicked(idx: number) {
|
|||
}
|
||||
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>
|
||||
<template>
|
||||
<div class="file-selector">
|
||||
<div @click.prevent="">
|
||||
<div v-for="file, idx in files" :key="idx" class="file-selector-item">
|
||||
<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>
|
||||
<AttachmentRow v-for="file, idx in files" :key="idx" :attachment="file" @deleted="onFileDeleteClicked(idx)" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
|
@ -143,13 +122,4 @@ function downloadFile(attachmentId: number) {
|
|||
.file-selector {
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -5,27 +5,48 @@ import { onMounted, ref, type Ref } from 'vue';
|
|||
import ValueRecordHistoryItem from './ValueRecordHistoryItem.vue';
|
||||
import JournalEntryHistoryItem from './JournalEntryHistoryItem.vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import AppButton from '../common/AppButton.vue';
|
||||
import AppBadge from '../common/AppBadge.vue';
|
||||
|
||||
const route = useRoute()
|
||||
const props = defineProps<{ accountId: number }>()
|
||||
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 () => {
|
||||
const pageRequest: PageRequest = { page: 1, size: 10, sorts: [{ attribute: 'timestamp', dir: 'DESC' }] }
|
||||
await loadNextPage()
|
||||
})
|
||||
|
||||
async function loadNextPage() {
|
||||
const api = new AccountApiClient(route)
|
||||
while (true) {
|
||||
try {
|
||||
const page = await api.getHistory(props.accountId, pageRequest)
|
||||
const page = await api.getHistory(props.accountId, nextPage.value)
|
||||
historyItems.value.push(...page.items)
|
||||
if (page.isLast) return
|
||||
pageRequest.page++
|
||||
canLoadMore.value = !page.isLast
|
||||
if (canLoadMore.value) {
|
||||
nextPage.value.page++
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
historyItems.value = []
|
||||
return
|
||||
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 {
|
||||
return i as AccountHistoryValueRecordItem
|
||||
|
|
@ -34,19 +55,27 @@ function asVR(i: AccountHistoryItem): AccountHistoryValueRecordItem {
|
|||
function asJE(i: AccountHistoryItem): AccountHistoryJournalEntryItem {
|
||||
return i as AccountHistoryJournalEntryItem
|
||||
}
|
||||
|
||||
defineExpose({ reload })
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<div v-for="item in historyItems" :key="item.timestamp" class="history-item">
|
||||
<div class="history-item-header">
|
||||
<div class="font-mono font-size-xsmall">{{ new Date(item.timestamp).toLocaleString() }}</div>
|
||||
<div>{{ item.type }}</div>
|
||||
<div class="font-mono font-size-xsmall text-muted">{{ new Date(item.timestamp).toLocaleString() }}</div>
|
||||
</div>
|
||||
|
||||
<ValueRecordHistoryItem v-if="item.type === AccountHistoryItemType.VALUE_RECORD" :item="asVR(item)"
|
||||
:account-id="accountId" />
|
||||
|
||||
<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>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import type { AccountHistoryJournalEntryItem } from '@/api/account'
|
|||
import { formatMoney } from '@/api/data';
|
||||
import { getSelectedProfile } from '@/api/profile';
|
||||
import { useRoute } from 'vue-router';
|
||||
import AppBadge from '../common/AppBadge.vue';
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
|
|
@ -17,9 +18,11 @@ defineProps<{ item: AccountHistoryJournalEntryItem }>()
|
|||
entered as a
|
||||
<strong>{{ item.journalEntryType.toLowerCase() }}</strong>
|
||||
for this account with a value of
|
||||
{{ formatMoney(item.amount, item.currency) }}.
|
||||
<AppBadge class="font-mono">{{ formatMoney(item.amount, item.currency) }}</AppBadge>
|
||||
<br />
|
||||
<p class="font-size-small m0 mt-1">
|
||||
{{ item.transactionDescription }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,35 +1,26 @@
|
|||
<script setup lang="ts">
|
||||
import { AccountApiClient, type AccountHistoryValueRecordItem } from '@/api/account';
|
||||
import { type AccountHistoryValueRecordItem } from '@/api/account';
|
||||
import { formatMoney } from '@/api/data';
|
||||
import AppButton from '../common/AppButton.vue';
|
||||
import { showConfirm } from '@/util/alert';
|
||||
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 props = defineProps<{ item: AccountHistoryValueRecordItem, accountId: number }>()
|
||||
|
||||
async function deleteValueRecord(id: number) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
const valueRecordRoute = computed(() => `/profiles/${getSelectedProfile(route)}/accounts/${props.accountId}/value-records/${props.item.valueRecordId}`)
|
||||
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div class="history-item-content">
|
||||
<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>
|
||||
<ButtonBar>
|
||||
<AppButton type="button" icon="trash" size="sm" @click="deleteValueRecord(item.valueRecordId)">
|
||||
Delete this record
|
||||
</AppButton>
|
||||
</ButtonBar>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ const route = useRoute()
|
|||
const router = useRouter()
|
||||
|
||||
const addValueRecordModal = useTemplateRef("addValueRecordModal")
|
||||
const history = useTemplateRef('history')
|
||||
const account: Ref<Account | null> = ref(null)
|
||||
|
||||
onMounted(async () => {
|
||||
|
|
@ -45,7 +46,7 @@ async function deleteAccount() {
|
|||
async function addValueRecord() {
|
||||
const result = await addValueRecordModal.value?.show()
|
||||
if (result) {
|
||||
console.info('Value record added', result)
|
||||
history.value?.reload()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -97,7 +98,7 @@ async function addValueRecord() {
|
|||
<AppButton icon="trash" @click="deleteAccount()">Delete</AppButton>
|
||||
</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" />
|
||||
</AppPage>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import TagLabel from '@/components/TagLabel.vue';
|
|||
import { showAlert, showConfirm } from '@/util/alert';
|
||||
import { onMounted, ref, type Ref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import AttachmentRow from '@/components/common/AttachmentRow.vue';
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
|
@ -117,6 +118,11 @@ async function deleteTransaction() {
|
|||
</tbody>
|
||||
</table>
|
||||
</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>
|
||||
<AppButton icon="wrench"
|
||||
@click="router.push(`/profiles/${getSelectedProfile(route)}/transactions/${transaction.id}/edit`)">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -3,6 +3,7 @@ import { AccountApiClient, type Account, type CurrencyBalance } from '@/api/acco
|
|||
import { formatMoney } from '@/api/data'
|
||||
import { getSelectedProfile } from '@/api/profile'
|
||||
import AccountCard from '@/components/AccountCard.vue'
|
||||
import AppBadge from '@/components/common/AppBadge.vue'
|
||||
import AppButton from '@/components/common/AppButton.vue'
|
||||
import HomeModule from '@/components/HomeModule.vue'
|
||||
import { onMounted, ref, type Ref } from 'vue'
|
||||
|
|
@ -33,13 +34,9 @@ onMounted(async () => {
|
|||
</p>
|
||||
|
||||
<div>
|
||||
<ul>
|
||||
<li v-for="bal in totalBalances" :key="bal.currency.code">
|
||||
<span>Total in {{ bal.currency.code }}</span>
|
||||
=
|
||||
<span>{{ formatMoney(bal.balance, bal.currency) }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<AppBadge v-for="bal in totalBalances" :key="bal.currency.code">
|
||||
Total {{ bal.currency.code }}: {{ formatMoney(bal.balance, bal.currency) }}
|
||||
</AppBadge>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:actions>
|
||||
|
|
|
|||
|
|
@ -47,6 +47,11 @@ const router = createRouter({
|
|||
component: () => import('@/pages/forms/EditAccountPage.vue'),
|
||||
meta: { title: 'Edit Account' },
|
||||
},
|
||||
{
|
||||
path: 'accounts/:accountId/value-records/:valueRecordId',
|
||||
component: () => import('@/pages/ValueRecordPage.vue'),
|
||||
meta: { title: 'Value Record' },
|
||||
},
|
||||
{
|
||||
path: 'add-account',
|
||||
component: () => import('@/pages/forms/EditAccountPage.vue'),
|
||||
|
|
|
|||
Loading…
Reference in New Issue