feat: Dashboard Functionality

main
Flycro 2024-03-20 18:09:26 +01:00
parent cbc54210f0
commit 923e41b396
5 changed files with 290 additions and 6 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>