generated from Flycro/laravel-nuxt
feat: Dashboard Functionality
parent
cbc54210f0
commit
923e41b396
|
|
@ -0,0 +1,89 @@
|
|||
<script setup lang="ts">
|
||||
import DeadlineTable from '~/components/deadline/DeadlineTable.vue'
|
||||
import NewDeadline from '~/components/modal/NewDeadline.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
book: BookRecommendation
|
||||
}>()
|
||||
|
||||
const auth = useAuthStore()
|
||||
const { $storage } = useNuxtApp()
|
||||
const dayjs = useDayjs()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCard class="my-4">
|
||||
<div class="flex w-full justify-between md:space-x-8 lg:space-x-4">
|
||||
<div class="hidden w-1/5 md:block">
|
||||
<img :src="$storage(props.book.cover_image)" :alt="props.book.book_name" class="rounded-lg">
|
||||
</div>
|
||||
<div class="w-4/5 space-y-4">
|
||||
<div class="space-y-2">
|
||||
<h1 class="font-sans text-3xl font-bold">
|
||||
{{ props.book.book_name }}
|
||||
</h1>
|
||||
<div class="flex justify-between">
|
||||
<div>
|
||||
<UBadge>
|
||||
{{ props.book.author }}
|
||||
</UBadge>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<NewDeadline v-if="auth.user?.roles.includes('admin')" :book-recommendation-id="props.book.id" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<UDivider />
|
||||
<div class="space-y-2">
|
||||
<p class="font-sans text-xl font-bold ">
|
||||
Beschreibung
|
||||
</p>
|
||||
<p class="w-full text-lg md:w-3/5">
|
||||
{{ props.book.description }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col flex-wrap justify-start gap-x-32 gap-y-4 pt-4 md:flex-row">
|
||||
<div>
|
||||
<p class="font-sans text-lg font-bold ">
|
||||
Seiten
|
||||
</p>
|
||||
<p class="text-lg">
|
||||
{{ props.book.pages }}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-sans text-lg font-bold">
|
||||
Erstveröffentlichung
|
||||
</p>
|
||||
<p class="text-lg">
|
||||
{{ dayjs(props.book.published_at).format('MMM YYYY') }}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-sans text-lg font-bold">
|
||||
Empfohlen von
|
||||
</p>
|
||||
<p class="text-lg">
|
||||
{{ props.book.recommender.name }}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-sans text-lg font-bold">
|
||||
Votes
|
||||
</p>
|
||||
<p class="text-lg">
|
||||
{{ props.book.votes.length }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<DeadlineTable :book-recommendation-id="props.book.id" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
<script setup lang="ts">
|
||||
import ConfirmUserDeadline from '~/components/modal/ConfirmUserDeadline.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
bookRecommendationId: number
|
||||
}>()
|
||||
|
||||
const { refresh: deadlineRefresh, status: deadlineStatus, data: deadlines } = useFetch(() => `book-recommendations/${props.bookRecommendationId}/deadlines`)
|
||||
|
||||
const columns = [
|
||||
{
|
||||
label: 'Deadline',
|
||||
key: 'deadline',
|
||||
},
|
||||
{
|
||||
label: 'Ziel Seite',
|
||||
key: 'target_page',
|
||||
},
|
||||
{
|
||||
label: 'Ziel Kapitel',
|
||||
key: 'target_chapter',
|
||||
},
|
||||
{
|
||||
label: '',
|
||||
key: 'actions',
|
||||
},
|
||||
]
|
||||
|
||||
const dayjs = useDayjs()
|
||||
|
||||
const rows = computed(() => {
|
||||
return deadlines.value?.map((deadline: any) => {
|
||||
return {
|
||||
id: deadline.id,
|
||||
deadline: dayjs(deadline.deadline).format('DD.MM.YYYY'),
|
||||
target_page: deadline.target_page,
|
||||
target_chapter: deadline.target_chapter,
|
||||
user_deadline: deadline.user_deadlines[0],
|
||||
actions: null,
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable :columns="columns" :rows="rows" :empty-state="{ icon: 'i-heroicons-calendar-solid', label: 'Keine Deadlines.' }">
|
||||
<template #actions-data="{ row }">
|
||||
<UBadge v-if="row.user_deadline.completed_at !== null">
|
||||
Abgeschlossen - {{ dayjs(row.user_deadline.completed_at).format('DD.MM.YYYY') }}
|
||||
</UBadge>
|
||||
<ConfirmUserDeadline v-if="row.user_deadline.completed_at === null" :user-deadline-id="row.id" @update="deadlineRefresh" />
|
||||
</template>
|
||||
</UTable>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
userDeadlineId: number
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['update'])
|
||||
|
||||
const isOpen = ref(false)
|
||||
|
||||
const { refresh: onConfirmDeadline, status } = useFetch<any>(() => `user-deadlines/${props.userDeadlineId}`, {
|
||||
method: 'PUT',
|
||||
immediate: false,
|
||||
watch: false,
|
||||
async onResponse({ response }) {
|
||||
if (response.ok) {
|
||||
useToast().add({
|
||||
icon: 'i-heroicons-check-circle-20-solid',
|
||||
title: 'Deadline erfolgreich Abgeschlossen.',
|
||||
color: 'emerald',
|
||||
})
|
||||
emit('update')
|
||||
isOpen.value = false
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UButton class="transition-150 transform-gpu hover:scale-110" icon="i-heroicons-book-open" size="sm" variant="solid" color="primary" square @click="isOpen = true" />
|
||||
<UDashboardModal
|
||||
v-model="isOpen"
|
||||
title="Deadline abschließen"
|
||||
description="Bist du dir sicher das du die Deadline abschließen möchtest?"
|
||||
icon="i-heroicons-book-open"
|
||||
:ui="{
|
||||
icon: { base: 'text-primary dark:text-primary-400' } as any,
|
||||
footer: { base: 'ml-16' } as any,
|
||||
}"
|
||||
>
|
||||
<template #footer>
|
||||
<UButton color="primary" label="Abschließen" :loading="status === 'pending'" @click="onConfirmDeadline" />
|
||||
<UButton color="white" label="Abbrechen" @click="isOpen = false" />
|
||||
</template>
|
||||
</UDashboardModal>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
bookRecommendationId: number
|
||||
}>()
|
||||
const isOpen = ref(false)
|
||||
const loading = ref(false)
|
||||
|
||||
const dayjs = useDayjs()
|
||||
const form = ref()
|
||||
|
||||
interface NewDeadlineState {
|
||||
book_recommendation_id: number | null
|
||||
deadline: string
|
||||
target_page?: number
|
||||
target_chapter?: string
|
||||
}
|
||||
|
||||
const state: NewDeadlineState = reactive({
|
||||
book_recommendation_id: props.bookRecommendationId,
|
||||
deadline: '',
|
||||
target_page: undefined,
|
||||
target_chapter: undefined,
|
||||
})
|
||||
|
||||
async function onSubmit() {
|
||||
loading.value = true
|
||||
|
||||
await $fetch('deadlines', {
|
||||
method: 'POST',
|
||||
body: state,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
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: 'Deadline erfolgreich erstellt.',
|
||||
color: 'emerald',
|
||||
})
|
||||
loading.value = false
|
||||
|
||||
isOpen.value = false
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton icon="i-heroicons-plus" label="Neue Deadline" size="sm" variant="solid" color="green" 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">
|
||||
Neue Deadline
|
||||
</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" @submit="onSubmit">
|
||||
<UFormGroup label="Deadline" name="deadline">
|
||||
<UInput v-model="state.deadline" type="date" />
|
||||
</UFormGroup>
|
||||
<UFormGroup label="Zielseite" name="target_page">
|
||||
<UInput v-model="state.target_page" type="number" />
|
||||
</UFormGroup>
|
||||
<UFormGroup label="Zielkapitel" name="target_chapter">
|
||||
<UInput v-model="state.target_chapter" />
|
||||
</UFormGroup>
|
||||
<UButton size="md" type="submit" :loading="loading">
|
||||
Erstellen
|
||||
</UButton>
|
||||
<UButton size="md" class="mx-4" color="white" label="Abbrechen" @click="isOpen = false" />
|
||||
</UForm>
|
||||
</UCard>
|
||||
</UModal>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,13 +1,19 @@
|
|||
<script setup lang="ts">
|
||||
definePageMeta({ middleware: ['auth'] })
|
||||
import BookInfoCard from '~/components/dashboard/BookInfoCard.vue'
|
||||
|
||||
const modal = useModal();
|
||||
const router = useRouter();
|
||||
const auth = useAuthStore();
|
||||
definePageMeta({ middleware: ['auth'] })
|
||||
const { $storage } = useNuxtApp()
|
||||
|
||||
const modal = useModal()
|
||||
const router = useRouter()
|
||||
const auth = useAuthStore()
|
||||
const bookRecommendationStore = useBookRecommendationStore()
|
||||
|
||||
bookRecommendationStore.fetchActiveRecommendations()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
This is the Page Content
|
||||
<div v-if="bookRecommendationStore.fetchActiveRecommendationsStatus !== 'pending'">
|
||||
<BookInfoCard v-for="book in bookRecommendationStore.recommendations" :key="book.id" :book="book" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
Loading…
Reference in New Issue