generated from Flycro/laravel-nuxt
feat: BR Overview + Modal for New Recommendations
parent
923e41b396
commit
fffe1b4717
|
|
@ -19,6 +19,10 @@ const columns = [
|
||||||
key: 'author',
|
key: 'author',
|
||||||
label: 'Autor',
|
label: 'Autor',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'published_at',
|
||||||
|
label: 'Erstveröffentlichung',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'description',
|
key: 'description',
|
||||||
label: 'Beschreibung',
|
label: 'Beschreibung',
|
||||||
|
|
@ -42,27 +46,46 @@ const columns = [
|
||||||
{
|
{
|
||||||
key: 'votes',
|
key: 'votes',
|
||||||
label: 'Votes',
|
label: 'Votes',
|
||||||
|
sortable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'actions',
|
key: 'actions',
|
||||||
label: '',
|
label: '',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
const sort = ref({
|
||||||
|
column: 'votes',
|
||||||
|
direction: 'desc',
|
||||||
|
})
|
||||||
|
|
||||||
|
function resolveStatus(status: string) {
|
||||||
|
return bookRecommendationStore.statusOptions.find(option => option.value === status)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<NewBookRecommendation />
|
<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 }">
|
<template #created_at-data="{ row }">
|
||||||
<div>{{ dayjs(row.created_at).format('DD.MM.YYYY') }}</div>
|
<div>{{ dayjs(row.created_at).format('DD.MM.YYYY') }}</div>
|
||||||
</template>
|
</template>
|
||||||
|
<template #published_at-data="{ row }">
|
||||||
|
<div>{{ dayjs(row.published_at).format('DD.MM.YYYY') }}</div>
|
||||||
|
</template>
|
||||||
<template #description-data="{ row }">
|
<template #description-data="{ row }">
|
||||||
{{ `${row.description.substring(0, 50)}...` }}
|
<div v-if="row.description">
|
||||||
|
{{ `${row.description.substring(0, 50)}...` }}
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #votes-data="{ row }">
|
<template #votes-data="{ row }">
|
||||||
{{ row.votes.length }}
|
{{ row.votes.length }}
|
||||||
</template>
|
</template>
|
||||||
|
<template #status-data="{ row }">
|
||||||
|
<UBadge :color="resolveStatus(row.status)?.color">
|
||||||
|
{{ resolveStatus(row.status)?.name }}
|
||||||
|
</UBadge>
|
||||||
|
</template>
|
||||||
<template #actions-data="{ row }">
|
<template #actions-data="{ row }">
|
||||||
<div class="flex space-x-2">
|
<div class="flex space-x-2">
|
||||||
<CastVote :row="row" />
|
<CastVote :row="row" />
|
||||||
|
|
|
||||||
|
|
@ -2,50 +2,95 @@
|
||||||
import { useBookRecommendationStore } from '~/stores/book-recommendations'
|
import { useBookRecommendationStore } from '~/stores/book-recommendations'
|
||||||
|
|
||||||
const isOpen = ref(false)
|
const isOpen = ref(false)
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
const dayjs = useDayjs()
|
||||||
|
|
||||||
const form = ref()
|
const form = ref()
|
||||||
|
|
||||||
const state = reactive({
|
interface State {
|
||||||
book_name: null,
|
book_name: string
|
||||||
author: null,
|
author: string
|
||||||
description: null,
|
description: string
|
||||||
isbn: null,
|
isbn: string
|
||||||
pages: null,
|
pages: number
|
||||||
cover_image: null,
|
cover_image?: File | string
|
||||||
status: null,
|
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 bookRecommendationStore = useBookRecommendationStore()
|
||||||
|
|
||||||
const { refresh: onSubmit, status } = useFetch<any>(`book-recommendations`, {
|
function handleCoverImageInput(event: Event) {
|
||||||
method: 'POST',
|
const file = (event.target as HTMLInputElement).files?.[0]
|
||||||
body: state,
|
if (file) {
|
||||||
immediate: false,
|
// Update the state with the selected file
|
||||||
watch: false,
|
state.cover_image = file
|
||||||
async onResponse({ response }) {
|
}
|
||||||
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',
|
|
||||||
})
|
|
||||||
await bookRecommendationStore.fetchRecommendations()
|
|
||||||
|
|
||||||
state.book_name = null
|
async function onSubmit() {
|
||||||
state.author = null
|
loading.value = true
|
||||||
state.description = null
|
const formData = new FormData()
|
||||||
state.isbn = null
|
for (const key in state) {
|
||||||
state.pages = null
|
const item = state[key]
|
||||||
state.cover_image = null
|
if (item === undefined) {
|
||||||
state.status = null
|
continue
|
||||||
|
|
||||||
isOpen.value = false
|
|
||||||
}
|
}
|
||||||
},
|
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: 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 angelegt.',
|
||||||
|
color: 'emerald',
|
||||||
|
})
|
||||||
|
await bookRecommendationStore.fetchRecommendations()
|
||||||
|
|
||||||
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -72,9 +117,15 @@ const { refresh: onSubmit, status } = useFetch<any>(`book-recommendations`, {
|
||||||
<UFormGroup label="Autor" name="author">
|
<UFormGroup label="Autor" name="author">
|
||||||
<UInput v-model="state.author" />
|
<UInput v-model="state.author" />
|
||||||
</UFormGroup>
|
</UFormGroup>
|
||||||
|
<UFormGroup label="Erstveröffentlichung">
|
||||||
|
<UInput v-model="state.published_at" type="date" />
|
||||||
|
</UFormGroup>
|
||||||
<UFormGroup label="Beschreibung" name="description">
|
<UFormGroup label="Beschreibung" name="description">
|
||||||
<UTextarea v-model="state.description" />
|
<UTextarea v-model="state.description" />
|
||||||
</UFormGroup>
|
</UFormGroup>
|
||||||
|
<UFormGroup label="Cover" name="cover_image">
|
||||||
|
<UInput type="file" @change="handleCoverImageInput" />
|
||||||
|
</UFormGroup>
|
||||||
<UFormGroup label="ISBN" name="isbn">
|
<UFormGroup label="ISBN" name="isbn">
|
||||||
<UInput v-model="state.isbn" />
|
<UInput v-model="state.isbn" />
|
||||||
</UFormGroup>
|
</UFormGroup>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
enum BookRecommendationStatusEnum {
|
export enum BookRecommendationStatusEnum {
|
||||||
PENDING = 'PENDING',
|
PENDING = 'PENDING',
|
||||||
REJECTED = 'REJECTED',
|
REJECTED = 'REJECTED',
|
||||||
ACTIVE = 'ACTIVE',
|
ACTIVE = 'ACTIVE',
|
||||||
|
|
@ -23,6 +23,7 @@ export interface BookRecommendation {
|
||||||
}
|
}
|
||||||
status: BookRecommendationStatusEnum
|
status: BookRecommendationStatusEnum
|
||||||
cover_image?: string
|
cover_image?: string
|
||||||
|
published_at?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useBookRecommendationStore = defineStore('bookRecommendations', () => {
|
export const useBookRecommendationStore = defineStore('bookRecommendations', () => {
|
||||||
|
|
@ -30,20 +31,25 @@ export const useBookRecommendationStore = defineStore('bookRecommendations', ()
|
||||||
|
|
||||||
const statusOptions = [
|
const statusOptions = [
|
||||||
{
|
{
|
||||||
name: 'Pending',
|
name: 'Ausstehend',
|
||||||
value: BookRecommendationStatusEnum.PENDING,
|
value: BookRecommendationStatusEnum.PENDING,
|
||||||
|
color: 'orange',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Rejected',
|
name: 'Abgelehnt',
|
||||||
value: BookRecommendationStatusEnum.REJECTED,
|
value: BookRecommendationStatusEnum.REJECTED,
|
||||||
|
color: 'red',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Active',
|
name: 'Aktiv',
|
||||||
value: BookRecommendationStatusEnum.ACTIVE,
|
value: BookRecommendationStatusEnum.ACTIVE,
|
||||||
|
color: 'green',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Completed',
|
name: 'Abgeschlossen',
|
||||||
value: BookRecommendationStatusEnum.COMPLETED,
|
value: BookRecommendationStatusEnum.COMPLETED,
|
||||||
|
color: 'primary',
|
||||||
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -57,29 +63,30 @@ export const useBookRecommendationStore = defineStore('bookRecommendations', ()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const deleteRecommendation = async (id: number) => {
|
const { refresh: fetchActiveRecommendations, status: fetchActiveRecommendationsStatus } = useFetch<BookRecommendation[]>('book-recommendations?with=recommender,votes&status=ACTIVE', {
|
||||||
try {
|
immediate: false,
|
||||||
const { error } = await useFetch(`book-recommendations/${id}`, {
|
onResponse({ response }) {
|
||||||
method: 'DELETE',
|
if (response.status === 200) {
|
||||||
})
|
recommendations.value = response._data
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
if (error.value) {
|
function resetRecommendations() {
|
||||||
console.error('Failed to delete book recommendation:', error.value)
|
recommendations.value = []
|
||||||
}
|
|
||||||
else {
|
|
||||||
recommendations.value = recommendations.value.filter(rec => rec.id !== id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
console.error('An error occurred while deleting a book recommendation:', e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
recommendations,
|
recommendations,
|
||||||
|
resetRecommendations,
|
||||||
statusOptions,
|
statusOptions,
|
||||||
fetchRecommendations,
|
fetchRecommendations,
|
||||||
fetchRecommendationsStatus,
|
fetchRecommendationsStatus,
|
||||||
deleteRecommendation,
|
fetchActiveRecommendations,
|
||||||
|
fetchActiveRecommendationsStatus,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.accept(acceptHMRUpdate(useBookRecommendationStore, import.meta.hot))
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue