166 lines
4.9 KiB
Vue
166 lines
4.9 KiB
Vue
<!--
|
|
Part of the EditTransactionPage which controls the list of line items for the
|
|
transaction. This editor shows a table of current line items, and includes a
|
|
modal for adding a new one.
|
|
-->
|
|
<script setup lang="ts">
|
|
import { type TransactionCategoryTree, type TransactionLineItemResponse } from '@/api/transaction'
|
|
import AppButton from '@/components/common/AppButton.vue'
|
|
import FormGroup from '@/components/common/form/FormGroup.vue'
|
|
import { floatMoneyToInteger, formatMoney, type Currency } from '@/api/data'
|
|
import ModalWrapper from '@/components/common/ModalWrapper.vue'
|
|
import FormControl from '@/components/common/form/FormControl.vue'
|
|
import { computed, ref, type Ref, useTemplateRef } from 'vue'
|
|
import CategorySelect from './CategorySelect.vue'
|
|
import LineItemCard from './LineItemCard.vue'
|
|
import AppBadge from './common/AppBadge.vue'
|
|
|
|
const model = defineModel<TransactionLineItemResponse[]>({ required: true })
|
|
const props = defineProps<{
|
|
transactionAmount: number
|
|
currency: Currency
|
|
}>()
|
|
|
|
const computedTotal = computed(() => {
|
|
let sum = 0
|
|
for (const item of model.value) {
|
|
sum += item.quantity * item.valuePerItem
|
|
}
|
|
return sum
|
|
})
|
|
|
|
const addLineItemDescription = ref('')
|
|
const addLineItemValuePerItem = ref(0)
|
|
const addLineItemQuantity = ref(0)
|
|
const addLineItemCategoryId: Ref<number | null> = ref(null)
|
|
const selectedCategory: Ref<TransactionCategoryTree | null> = ref(null)
|
|
const addLineItemModal = useTemplateRef('addLineItemModal')
|
|
|
|
function canAddLineItem() {
|
|
return addLineItemDescription.value.length > 0 && addLineItemQuantity.value > 0
|
|
}
|
|
|
|
function showAddLineItemModal() {
|
|
addLineItemDescription.value = ''
|
|
addLineItemValuePerItem.value = 1.0
|
|
addLineItemQuantity.value = 1
|
|
addLineItemCategoryId.value = null
|
|
addLineItemModal.value?.show()
|
|
}
|
|
|
|
async function addLineItem() {
|
|
const newIdx = model.value.length === 0 ? 0 : Math.max(...model.value.map((i) => i.idx)) + 1
|
|
model.value.push({
|
|
idx: newIdx,
|
|
description: addLineItemDescription.value,
|
|
quantity: addLineItemQuantity.value,
|
|
valuePerItem: floatMoneyToInteger(addLineItemValuePerItem.value, props.currency),
|
|
category: selectedCategory.value,
|
|
})
|
|
addLineItemModal.value?.close()
|
|
}
|
|
|
|
function removeLineItem(idx: number) {
|
|
model.value = model.value.filter((i) => i.idx !== idx)
|
|
for (let i = 0; i < model.value.length; i++) {
|
|
model.value[i].idx = i
|
|
}
|
|
}
|
|
|
|
function moveItemUp(idx: number) {
|
|
if (idx <= 0) return
|
|
const item = model.value[idx]
|
|
model.value[idx] = model.value[idx - 1]
|
|
model.value[idx - 1] = item
|
|
for (let i = 0; i < model.value.length; i++) {
|
|
model.value[i].idx = i
|
|
}
|
|
}
|
|
|
|
function moveItemDown(idx: number) {
|
|
if (idx >= model.value.length - 1) return
|
|
const item = model.value[idx]
|
|
model.value[idx] = model.value[idx + 1]
|
|
model.value[idx + 1] = item
|
|
for (let i = 0; i < model.value.length; i++) {
|
|
model.value[i].idx = i
|
|
}
|
|
}
|
|
</script>
|
|
<template>
|
|
<div>
|
|
<LineItemCard
|
|
v-for="item in model"
|
|
:key="item.idx"
|
|
:line-item="item"
|
|
:currency="currency"
|
|
:total-count="model.length"
|
|
editable
|
|
@deleted="removeLineItem(item.idx)"
|
|
@moved-up="moveItemUp(item.idx)"
|
|
@moved-down="moveItemDown(item.idx)"
|
|
/>
|
|
<div>
|
|
<AppButton
|
|
icon="plus"
|
|
@click="showAddLineItemModal()"
|
|
>Add Line Item</AppButton
|
|
>
|
|
<AppBadge
|
|
v-if="model.length > 0"
|
|
:class="{
|
|
'text-positive': computedTotal === transactionAmount,
|
|
'text-negative': computedTotal !== transactionAmount,
|
|
}"
|
|
>
|
|
Items Total: {{ formatMoney(computedTotal, currency) }}
|
|
</AppBadge>
|
|
</div>
|
|
|
|
<!-- Modal for adding a new line item. -->
|
|
<ModalWrapper ref="addLineItemModal">
|
|
<template v-slot:default>
|
|
<h3>Add Line Item</h3>
|
|
<FormGroup>
|
|
<FormControl label="Description">
|
|
<textarea v-model="addLineItemDescription"></textarea>
|
|
</FormControl>
|
|
<FormControl label="Value Per Item">
|
|
<input
|
|
type="number"
|
|
step="0.01"
|
|
v-model="addLineItemValuePerItem"
|
|
/>
|
|
</FormControl>
|
|
<FormControl label="Quantity">
|
|
<input
|
|
type="number"
|
|
step="1"
|
|
min="1"
|
|
v-model="addLineItemQuantity"
|
|
/>
|
|
</FormControl>
|
|
<FormControl label="Category">
|
|
<CategorySelect
|
|
v-model="addLineItemCategoryId"
|
|
@category-selected="(c) => (selectedCategory = c)"
|
|
/>
|
|
</FormControl>
|
|
</FormGroup>
|
|
</template>
|
|
<template v-slot:buttons>
|
|
<AppButton
|
|
@click="addLineItem()"
|
|
:disabled="!canAddLineItem()"
|
|
>Add</AppButton
|
|
>
|
|
<AppButton
|
|
button-style="secondary"
|
|
@click="addLineItemModal?.close()"
|
|
>Cancel</AppButton
|
|
>
|
|
</template>
|
|
</ModalWrapper>
|
|
</div>
|
|
</template>
|