Improved tags editor styling.
This commit is contained in:
parent
7666c0f450
commit
110eb2c912
|
|
@ -8,12 +8,8 @@ defineEmits<{ deleted: void }>()
|
||||||
<AppBadge>
|
<AppBadge>
|
||||||
<span class="tag-label-hashtag">#</span>
|
<span class="tag-label-hashtag">#</span>
|
||||||
{{ tag }}
|
{{ tag }}
|
||||||
<font-awesome-icon
|
<font-awesome-icon v-if="deletable" icon="fa-x" class="tag-label-delete"
|
||||||
v-if="deletable"
|
@click="$emit('deleted')"></font-awesome-icon>
|
||||||
icon="fa-x"
|
|
||||||
class="tag-label-delete"
|
|
||||||
@click="$emit('deleted')"
|
|
||||||
></font-awesome-icon>
|
|
||||||
</AppBadge>
|
</AppBadge>
|
||||||
</template>
|
</template>
|
||||||
<style lang="css">
|
<style lang="css">
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { getSelectedProfile } from '@/api/profile';
|
||||||
|
import { TransactionApiClient } from '@/api/transaction';
|
||||||
|
import { computed, onMounted, ref, type Ref } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import TagLabel from './TagLabel.vue';
|
||||||
|
import AppButton from './common/AppButton.vue';
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const model = defineModel<string[]>({ required: true })
|
||||||
|
const existingTags: Ref<string[]> = ref([])
|
||||||
|
const availableTags = computed(() => {
|
||||||
|
return existingTags.value.filter(t => !model.value.includes(t))
|
||||||
|
})
|
||||||
|
|
||||||
|
const tagSelect: Ref<string | null> = ref(null)
|
||||||
|
const enteringCustomTag = computed(() => tagSelect.value == "--CUSTOM_TAG--")
|
||||||
|
const customTagInput: Ref<string> = ref('')
|
||||||
|
const customTagInputValid = computed(() => {
|
||||||
|
const result = customTagInput.value.match('^[a-z0-9-_]{3,32}$')
|
||||||
|
return result !== null && result.length > 0
|
||||||
|
})
|
||||||
|
const canAddTag = computed(() => {
|
||||||
|
if (tagSelect.value !== null && !enteringCustomTag.value) return true
|
||||||
|
return enteringCustomTag.value && customTagInputValid.value
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const api = new TransactionApiClient(getSelectedProfile(route))
|
||||||
|
existingTags.value = await api.getAllTags()
|
||||||
|
})
|
||||||
|
|
||||||
|
function addTag() {
|
||||||
|
if (customTagInputValid.value) {
|
||||||
|
model.value.push(customTagInput.value)
|
||||||
|
tagSelect.value = null
|
||||||
|
customTagInput.value = ''
|
||||||
|
} else if (tagSelect.value !== null) {
|
||||||
|
model.value.push(tagSelect.value)
|
||||||
|
tagSelect.value = null
|
||||||
|
customTagInput.value = ''
|
||||||
|
}
|
||||||
|
model.value.sort()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div style="margin-top: 0.5rem; margin-bottom: 0.5rem; font-weight: normal;">
|
||||||
|
<TagLabel v-for="t in model" :key="t" :tag="t" deletable @deleted="model = model.filter((tg) => tg !== t)" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<select v-model="tagSelect">
|
||||||
|
<option v-for="tag in availableTags" :key="tag" :value="tag">
|
||||||
|
{{ tag }}
|
||||||
|
</option>
|
||||||
|
<option value="--CUSTOM_TAG--">
|
||||||
|
Add a new tag
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<input style="margin-left: 0.25rem;" v-model="customTagInput" v-if="enteringCustomTag"
|
||||||
|
placeholder="Custom tag..." />
|
||||||
|
<AppButton size="sm" style="margin-left: 0.25rem;" @click="addTag()" :disabled="!canAddTag">
|
||||||
|
Add Tag
|
||||||
|
</AppButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -26,19 +26,19 @@ defineProps<{
|
||||||
|
|
||||||
/* Styles for different form controls under here: */
|
/* Styles for different form controls under here: */
|
||||||
|
|
||||||
.app-form-control > label > input {
|
.app-form-control>label input {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-family: 'OpenSans', sans-serif;
|
font-family: 'OpenSans', sans-serif;
|
||||||
padding: 0.25rem 0.5rem;
|
padding: 0.25rem 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-form-control > label > textarea {
|
.app-form-control>label textarea {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-family: 'OpenSans', sans-serif;
|
font-family: 'OpenSans', sans-serif;
|
||||||
padding: 0.25rem 0.5rem;
|
padding: 0.25rem 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-form-control > label > select {
|
.app-form-control>label select {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-family: 'OpenSans', sans-serif;
|
font-family: 'OpenSans', sans-serif;
|
||||||
padding: 0.25rem 0.5rem;
|
padding: 0.25rem 0.5rem;
|
||||||
|
|
|
||||||
|
|
@ -28,11 +28,11 @@ import FormActions from '@/components/common/form/FormActions.vue'
|
||||||
import FormControl from '@/components/common/form/FormControl.vue'
|
import FormControl from '@/components/common/form/FormControl.vue'
|
||||||
import FormGroup from '@/components/common/form/FormGroup.vue'
|
import FormGroup from '@/components/common/form/FormGroup.vue'
|
||||||
import LineItemsEditor from '@/components/LineItemsEditor.vue'
|
import LineItemsEditor from '@/components/LineItemsEditor.vue'
|
||||||
import TagLabel from '@/components/TagLabel.vue'
|
|
||||||
import { getDatetimeLocalValueForNow } from '@/util/time'
|
import { getDatetimeLocalValueForNow } from '@/util/time'
|
||||||
import { computed, onMounted, ref, watch, type Ref } from 'vue'
|
import { computed, onMounted, ref, watch, type Ref } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import VendorSelect from '@/components/VendorSelect.vue'
|
import VendorSelect from '@/components/VendorSelect.vue'
|
||||||
|
import TagsSelect from '@/components/TagsSelect.vue'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
@ -129,10 +129,6 @@ const allAccounts: Ref<Account[]> = ref([])
|
||||||
const availableAccounts = computed(() => {
|
const availableAccounts = computed(() => {
|
||||||
return allAccounts.value.filter((a) => a.currency.code === currency.value?.code)
|
return allAccounts.value.filter((a) => a.currency.code === currency.value?.code)
|
||||||
})
|
})
|
||||||
const allTags: Ref<string[]> = ref([])
|
|
||||||
const availableTags = computed(() => {
|
|
||||||
return allTags.value.filter((t) => !tags.value.includes(t))
|
|
||||||
})
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|
||||||
// Form data:
|
// Form data:
|
||||||
|
|
@ -146,7 +142,6 @@ const creditedAccountId: Ref<number | null> = ref(null)
|
||||||
const debitedAccountId: Ref<number | null> = ref(null)
|
const debitedAccountId: Ref<number | null> = ref(null)
|
||||||
const lineItems: Ref<TransactionDetailLineItem[]> = ref([])
|
const lineItems: Ref<TransactionDetailLineItem[]> = ref([])
|
||||||
const tags: Ref<string[]> = ref([])
|
const tags: Ref<string[]> = ref([])
|
||||||
const selectedTagToAdd: Ref<string | null> = ref(null)
|
|
||||||
const customTagInput = ref('')
|
const customTagInput = ref('')
|
||||||
const customTagInputValid = ref(false)
|
const customTagInputValid = ref(false)
|
||||||
const attachmentsToUpload: Ref<File[]> = ref([])
|
const attachmentsToUpload: Ref<File[]> = ref([])
|
||||||
|
|
@ -167,7 +162,6 @@ onMounted(async () => {
|
||||||
|
|
||||||
// Fetch various collections of data needed for different user choices.
|
// Fetch various collections of data needed for different user choices.
|
||||||
dataClient.getCurrencies().then((currencies) => (allCurrencies.value = currencies))
|
dataClient.getCurrencies().then((currencies) => (allCurrencies.value = currencies))
|
||||||
transactionApi.getAllTags().then((t) => (allTags.value = t))
|
|
||||||
accountApi.getAccounts().then((accounts) => (allAccounts.value = accounts))
|
accountApi.getAccounts().then((accounts) => (allAccounts.value = accounts))
|
||||||
|
|
||||||
const transactionIdStr = route.params.id
|
const transactionIdStr = route.params.id
|
||||||
|
|
@ -261,18 +255,6 @@ function doCancel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addTag() {
|
|
||||||
if (customTagInput.value.trim().length > 0) {
|
|
||||||
tags.value.push(customTagInput.value.trim())
|
|
||||||
tags.value.sort()
|
|
||||||
customTagInput.value = ''
|
|
||||||
} else if (selectedTagToAdd.value !== null) {
|
|
||||||
tags.value.push(selectedTagToAdd.value)
|
|
||||||
tags.value.sort()
|
|
||||||
selectedTagToAdd.value = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadValuesFromExistingTransaction(t: TransactionDetail) {
|
function loadValuesFromExistingTransaction(t: TransactionDetail) {
|
||||||
timestamp.value = getLocalDateTimeStringFromUTCTimestamp(t.timestamp)
|
timestamp.value = getLocalDateTimeStringFromUTCTimestamp(t.timestamp)
|
||||||
amount.value = t.amount / Math.pow(10, t.currency.fractionalDigits)
|
amount.value = t.amount / Math.pow(10, t.currency.fractionalDigits)
|
||||||
|
|
@ -352,20 +334,7 @@ function getLocalDateTimeStringFromUTCTimestamp(timestamp: string) {
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<!-- Tags -->
|
<!-- Tags -->
|
||||||
<FormControl label="Tags">
|
<FormControl label="Tags">
|
||||||
<div style="margin-top: 0.5rem; margin-bottom: 0.5rem">
|
<TagsSelect v-model="tags" />
|
||||||
<TagLabel v-for="t in tags" :key="t" :tag="t" deletable @deleted="tags = tags.filter((tg) => tg !== t)" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<select v-model="selectedTagToAdd">
|
|
||||||
<option v-for="tag in availableTags" :key="tag" :value="tag">
|
|
||||||
{{ tag }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
<input v-model="customTagInput" placeholder="Custom tag..." />
|
|
||||||
<button type="button" @click="addTag()" :disabled="selectedTagToAdd === null && !customTagInputValid">
|
|
||||||
Add Tag
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue