feat: BR Overview + Modal for New Recommendations

main
Flycro 2024-03-20 18:10:02 +01:00
parent 923e41b396
commit fffe1b4717
3 changed files with 139 additions and 58 deletions

View File

@ -19,6 +19,10 @@ const columns = [
key: 'author',
label: 'Autor',
},
{
key: 'published_at',
label: 'Erstveröffentlichung',
},
{
key: 'description',
label: 'Beschreibung',
@ -42,27 +46,46 @@ const columns = [
{
key: 'votes',
label: 'Votes',
sortable: true,
},
{
key: 'actions',
label: '',
},
]
const sort = ref({
column: 'votes',
direction: 'desc',
})
function resolveStatus(status: string) {
return bookRecommendationStore.statusOptions.find(option => option.value === status)
}
</script>
<template>
<div>
<NewBookRecommendation />
<UTable :loading="bookRecommendationStore.fetchRecommendationsStatus === 'pending'" :columns="columns" :rows="bookRecommendationStore.recommendations">
<UTable :sort="sort" :loading="bookRecommendationStore.fetchRecommendationsStatus === 'pending'" :columns="columns" :rows="bookRecommendationStore.recommendations">
<template #created_at-data="{ row }">
<div>{{ dayjs(row.created_at).format('DD.MM.YYYY') }}</div>
</template>
<template #published_at-data="{ row }">
<div>{{ dayjs(row.published_at).format('DD.MM.YYYY') }}</div>
</template>
<template #description-data="{ row }">
<div v-if="row.description">
{{ `${row.description.substring(0, 50)}...` }}
</div>
</template>
<template #votes-data="{ row }">
{{ row.votes.length }}
</template>
<template #status-data="{ row }">
<UBadge :color="resolveStatus(row.status)?.color">
{{ resolveStatus(row.status)?.name }}
</UBadge>
</template>
<template #actions-data="{ row }">
<div class="flex space-x-2">
<CastVote :row="row" />

View File

@ -2,50 +2,95 @@
import { useBookRecommendationStore } from '~/stores/book-recommendations'
const isOpen = ref(false)
const loading = ref(false)
const dayjs = useDayjs()
const form = ref()
const state = reactive({
book_name: null,
author: null,
description: null,
isbn: null,
pages: null,
cover_image: null,
status: null,
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 | undefined
}
const state: State = reactive({
book_name: '',
author: '',
description: '',
isbn: '',
pages: 0,
cover_image: '',
status: 'PENDING',
published_at: dayjs().format('YYYY-MM-DD'),
})
const bookRecommendationStore = useBookRecommendationStore()
const { refresh: onSubmit, status } = useFetch<any>(`book-recommendations`, {
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
}
}
async function onSubmit() {
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 {
if (key === 'cover_image') {
continue
}
const value = typeof item === 'string' ? item : String(item)
formData.append(key, value)
}
}
await $fetch<any>(`book-recommendations`, {
method: 'POST',
body: state,
immediate: false,
watch: false,
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.',
title: 'Buchempfehlung wurde erfolgreich angelegt.',
color: 'emerald',
})
await bookRecommendationStore.fetchRecommendations()
state.book_name = null
state.author = null
state.description = null
state.isbn = null
state.pages = null
state.cover_image = null
state.status = null
state.book_name = ''
state.author = ''
state.description = ''
state.isbn = ''
state.pages = 0
state.cover_image = ''
state.status = 'PENDING'
state.published_at = dayjs().format('YYYY-MM-DD')
isOpen.value = false
}
},
})
})
}
</script>
<template>
@ -72,9 +117,15 @@ const { refresh: onSubmit, status } = useFetch<any>(`book-recommendations`, {
<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>
<UFormGroup label="Cover" name="cover_image">
<UInput type="file" @change="handleCoverImageInput" />
</UFormGroup>
<UFormGroup label="ISBN" name="isbn">
<UInput v-model="state.isbn" />
</UFormGroup>

View File

@ -1,6 +1,6 @@
import { defineStore } from 'pinia'
enum BookRecommendationStatusEnum {
export enum BookRecommendationStatusEnum {
PENDING = 'PENDING',
REJECTED = 'REJECTED',
ACTIVE = 'ACTIVE',
@ -23,6 +23,7 @@ export interface BookRecommendation {
}
status: BookRecommendationStatusEnum
cover_image?: string
published_at?: string
}
export const useBookRecommendationStore = defineStore('bookRecommendations', () => {
@ -30,20 +31,25 @@ export const useBookRecommendationStore = defineStore('bookRecommendations', ()
const statusOptions = [
{
name: 'Pending',
name: 'Ausstehend',
value: BookRecommendationStatusEnum.PENDING,
color: 'orange',
},
{
name: 'Rejected',
name: 'Abgelehnt',
value: BookRecommendationStatusEnum.REJECTED,
color: 'red',
},
{
name: 'Active',
name: 'Aktiv',
value: BookRecommendationStatusEnum.ACTIVE,
color: 'green',
},
{
name: 'Completed',
name: 'Abgeschlossen',
value: BookRecommendationStatusEnum.COMPLETED,
color: 'primary',
},
]
@ -57,29 +63,30 @@ export const useBookRecommendationStore = defineStore('bookRecommendations', ()
},
})
const deleteRecommendation = async (id: number) => {
try {
const { error } = await useFetch(`book-recommendations/${id}`, {
method: 'DELETE',
const { refresh: fetchActiveRecommendations, status: fetchActiveRecommendationsStatus } = useFetch<BookRecommendation[]>('book-recommendations?with=recommender,votes&status=ACTIVE', {
immediate: false,
onResponse({ response }) {
if (response.status === 200) {
recommendations.value = response._data
}
},
})
if (error.value) {
console.error('Failed to delete book recommendation:', error.value)
}
else {
recommendations.value = recommendations.value.filter(rec => rec.id !== id)
}
}
catch (e) {
console.error('An error occurred while deleting a book recommendation:', e)
}
function resetRecommendations() {
recommendations.value = []
}
return {
recommendations,
resetRecommendations,
statusOptions,
fetchRecommendations,
fetchRecommendationsStatus,
deleteRecommendation,
fetchActiveRecommendations,
fetchActiveRecommendationsStatus,
}
})
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useBookRecommendationStore, import.meta.hot))
}