132 lines
4.0 KiB
Vue
132 lines
4.0 KiB
Vue
<script lang="ts" setup>
|
|
import { reactive, ref, computed } from 'vue'
|
|
import axios from 'axios'
|
|
import type { SettingField } from "@/Types/types"
|
|
|
|
const props = defineProps<{
|
|
fields: SettingField[],
|
|
categories: Record<string, string>
|
|
}>()
|
|
|
|
const values = reactive(
|
|
Object.fromEntries(props.fields.map(f => [f.key, f.value]))
|
|
)
|
|
const saving = ref(false)
|
|
const saved = ref(false)
|
|
const error = ref(false)
|
|
|
|
const groupedFields = computed(() =>
|
|
props.fields.reduce((groups: Record<string, SettingField[]>, field) => {
|
|
const cat = field.category ?? 'General'
|
|
;(groups[cat] ??= []).push(field)
|
|
return groups
|
|
}, {} as Record<string, SettingField[]>)
|
|
)
|
|
|
|
async function save() {
|
|
saving.value = true
|
|
error.value = false
|
|
try {
|
|
await axios.patch('/settings', { settings: values })
|
|
saved.value = true
|
|
setTimeout(() => saved.value = false, 3000)
|
|
} catch {
|
|
error.value = true
|
|
} finally {
|
|
saving.value = false
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<v-form @submit.prevent="save">
|
|
<template v-for="(groupFields, category) in groupedFields" :key="category">
|
|
<p class="text-overline text-medium-emphasis mb-1 mt-4">{{ category }}</p>
|
|
<small v-if="categories[category]" class="text-body-2 text-medium-emphasis mb-3">
|
|
{{ categories[category] }}
|
|
</small>
|
|
<v-divider class="mb-4" />
|
|
|
|
<template v-for="field in groupFields" :key="field.key">
|
|
|
|
<v-select
|
|
v-if="field.type === 'select'"
|
|
v-model="values[field.key]"
|
|
:label="field.label"
|
|
:items="field.options"
|
|
item-title="label"
|
|
item-value="value"
|
|
variant="outlined"
|
|
density="comfortable"
|
|
class="mb-2"
|
|
/>
|
|
|
|
<v-text-field
|
|
v-else-if="field.type === 'text'"
|
|
v-model="values[field.key]"
|
|
:label="field.label"
|
|
variant="outlined"
|
|
density="comfortable"
|
|
class="mb-2"
|
|
/>
|
|
|
|
<v-checkbox
|
|
v-else-if="field.type === 'checkbox'"
|
|
v-model="values[field.key]"
|
|
:label="field.label"
|
|
color="primary"
|
|
density="comfortable"
|
|
hide-details
|
|
class="mb-2"
|
|
/>
|
|
|
|
<v-select
|
|
v-else-if="field.type === 'multiselect'"
|
|
v-model="values[field.key]"
|
|
:label="field.label"
|
|
:items="field.options"
|
|
item-title="label"
|
|
item-value="value"
|
|
variant="outlined"
|
|
density="comfortable"
|
|
chips
|
|
multiple
|
|
closable-chips
|
|
clearable
|
|
class="mb-2"
|
|
/>
|
|
|
|
|
|
</template>
|
|
</template>
|
|
|
|
<v-divider class="my-4" />
|
|
|
|
<div class="d-flex align-center gap-3">
|
|
<v-btn
|
|
type="submit"
|
|
color="primary"
|
|
variant="elevated"
|
|
:loading="saving"
|
|
min-width="140"
|
|
>
|
|
Save settings
|
|
</v-btn>
|
|
|
|
<v-fade-transition>
|
|
<div v-if="saved" class="d-flex align-center gap-1 text-success">
|
|
<v-icon size="18">mdi-check-circle</v-icon>
|
|
<span class="text-body-2">Saved</span>
|
|
</div>
|
|
</v-fade-transition>
|
|
|
|
<v-fade-transition>
|
|
<div v-if="error" class="d-flex align-center gap-1 text-error">
|
|
<v-icon size="18">mdi-alert-circle</v-icon>
|
|
<span class="text-body-2">Something went wrong</span>
|
|
</div>
|
|
</v-fade-transition>
|
|
</div>
|
|
</v-form>
|
|
</template>
|