bookclub-manager/nuxt/components/modal/EditBookRecommendation.vue

180 lines
5.7 KiB
Vue

<script setup lang="ts">
import { useBookRecommendationStore } from '~/stores/book-recommendations'
import type { FormSubmitEvent } from '#ui/types'
const props = defineProps<{
row: {
id: number
book_name: string
author: string
description: string
isbn: string
pages: number
cover_image?: string | File
status: string
published_at: string
recommender: {
ulid: string
}
}
}>()
const dayjs = useDayjs()
const authStore = useAuthStore()
const { $storage } = useNuxtApp()
const isOpen = ref(false)
const loading = ref(false)
const form = ref()
interface State {
book_name: string
author: string
description: string
isbn: string
pages: number
cover_image?: File | string
status: string
published_at: string
// Index signature
[key: string]: string | number | File | Date | undefined
}
const state: State = reactive({
book_name: props.row.book_name,
author: props.row.author,
description: props.row.description,
isbn: props.row.isbn,
pages: props.row.pages,
cover_image: props.row.cover_image,
status: props.row.status,
published_at: dayjs(props.row.published_at).format('YYYY-MM-DD'),
})
watch(() => props.row, (newRow) => {
state.book_name = newRow.book_name
state.author = newRow.author
state.description = newRow.description
state.isbn = newRow.isbn
state.pages = newRow.pages
state.cover_image = newRow.cover_image
state.status = newRow.status
state.published_at = dayjs(newRow.published_at).format('YYYY-MM-DD')
}, { deep: true, immediate: true })
function handleCoverImageInput(event: Event) {
const file = (event.target as HTMLInputElement).files?.[0]
if (file) {
// Update the state with the selected file
state.cover_image = file
}
}
const bookRecommendationStore = useBookRecommendationStore()
async function onSubmit(event: FormSubmitEvent<any>) {
form.value.clear()
loading.value = true
const formData = new FormData()
for (const key in state) {
const item = state[key]
if (item === undefined) {
continue
}
if (key === 'cover_image' && state[key] instanceof File) {
formData.append(key, item as Blob, (state[key] as File).name)
}
else {
const value = typeof item === 'string' ? item : String(item)
if (key === 'cover_image') {
continue
}
formData.append(key, value)
}
}
const response = await $fetch<any>(`book-recommendations/${props.row.id}`, {
method: 'PUT',
body: formData,
async onResponse({ response }) {
loading.value = false
if (response?.status === 422) {
form.value.setErrors(response._data?.errors)
}
else if (response.ok) {
useToast().add({
icon: 'i-heroicons-check-circle-20-solid',
title: 'Buchempfehlung wurde erfolgreich aktualisiert.',
color: 'emerald',
})
isOpen.value = false
}
},
})
await bookRecommendationStore.fetchRecommendations()
}
function isFile(value: any): value is File {
return value instanceof File
}
function getFileUrl(file: File) {
return URL.createObjectURL(file)
}
</script>
<template>
<div>
<UButton v-if="authStore.user.ulid === props.row.recommender.ulid || authStore.user.roles.includes('admin')" class="transition-150 transform-gpu hover:scale-110" icon="i-heroicons-pencil-square" size="sm" variant="solid" square @click="isOpen = true" />
<UModal v-model="isOpen">
<UCard :ui="{ ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
<template #header>
<div class="flex items-center justify-between">
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
Bearbeiten
</h3>
<UButton color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @click="isOpen = false" />
</div>
</template>
<UForm ref="form" :state="state" class="space-y-4" enctype="multipart/form-data" @submit="onSubmit">
<UFormGroup label="Name" name="book_name">
<UInput v-model="state.book_name" />
</UFormGroup>
<UFormGroup label="Autor" name="author">
<UInput v-model="state.author" />
</UFormGroup>
<UFormGroup label="Erstveröffentlichung">
<UInput v-model="state.published_at" type="date" />
</UFormGroup>
<UFormGroup label="Beschreibung" name="description">
<UTextarea v-model="state.description" />
</UFormGroup>
<img v-if="state.cover_image && typeof state.cover_image === 'string'" :src="$storage(state.cover_image)" alt="Cover" class="size-1/3 content-center rounded-lg">
<ClientOnly>
<img v-if="state.cover_image && isFile(state.cover_image)" :src="getFileUrl(state.cover_image)" alt="Cover" class="size-1/3 content-center rounded-lg">
</ClientOnly>
<UFormGroup label="Cover" name="cover_image">
<UInput type="file" @change="handleCoverImageInput" />
</UFormGroup>
<UFormGroup label="ISBN" name="isbn">
<UInput v-model="state.isbn" />
</UFormGroup>
<UFormGroup label="Seiten" name="pages">
<UInput v-model="state.pages" type="number" />
</UFormGroup>
<UFormGroup label="Status" name="status">
<USelect v-model="state.status" :options="bookRecommendationStore.statusOptions" option-attribute="name" />
</UFormGroup>
<UButton size="md" type="submit">
Speichern
</UButton>
<UButton size="md" class="mx-4" color="white" label="Abbrechen" @click="isOpen = false" />
</UForm>
</UCard>
</UModal>
</div>
</template>