feat: ajout des composants MangaChapter, MangaChapterList, MangaHeader, MangaVolume, MangaVolumeList et mise à jour de la page MangaDetails pour une meilleure gestion des chapitres et volumes de manga

This commit is contained in:
ext.jeremy.guillot@maxicoffee.domains
2025-03-26 21:41:59 +01:00
parent eeb8447d7a
commit 22cf4eb186
6 changed files with 364 additions and 271 deletions

View File

@@ -0,0 +1,75 @@
<template>
<tr class="border-t hover:bg-green-100">
<td class="px-4 py-2" :class="{ 'text-green-500': chapter.isDownloaded }">
{{ String(chapter.number).padStart(2, '0') }}
</td>
<td class="px-4 py-2 w-full text-left">
<router-link
v-if="chapter.isDownloaded"
:to="{
name: 'reader',
params: {
mangaSlug: mangaSlug,
chapterNumber: chapter.number,
pageNumber: 1
}
}"
class="hover:text-green-500">
{{ chapter.title || 'Sans titre' }}
</router-link>
<span v-else>{{ chapter.title || 'Sans titre' }}</span>
</td>
<td class="px-4 py-2 flex justify-end gap-2">
<button v-if="!chapter.isDownloaded" @click="handleSearch" class="text-gray-500 hover:text-green-500">
<MagnifyingGlassIcon class="h-5 w-5" />
</button>
<button v-else @click="handleDelete" class="text-gray-500 hover:text-green-500">
<XMarkIcon class="h-5 w-5" />
</button>
<button @click="handleDownload" class="text-gray-500 hover:text-green-500">
<ArrowDownTrayIcon class="h-5 w-5" />
</button>
<button @click="handleHide" class="text-gray-500 hover:text-green-500">
<TrashIcon class="h-5 w-5" />
</button>
</td>
</tr>
</template>
<script setup>
import { MagnifyingGlassIcon, ArrowDownTrayIcon, XMarkIcon, TrashIcon } from '@heroicons/vue/24/solid';
import { useMangaStore } from '../../application/store/mangaStore';
const props = defineProps({
chapter: {
type: Object,
required: true
},
mangaSlug: {
type: String,
required: true
}
});
const store = useMangaStore();
const handleSearch = async () => {
// TODO: Implémenter la recherche de chapitre
console.log('Recherche du chapitre:', props.chapter.id);
};
const handleDelete = async () => {
// TODO: Implémenter la suppression du chapitre
console.log('Suppression du chapitre:', props.chapter.id);
};
const handleDownload = async () => {
// TODO: Implémenter le téléchargement du chapitre
console.log('Téléchargement du chapitre:', props.chapter.id);
};
const handleHide = async () => {
// TODO: Implémenter le masquage du chapitre
console.log('Masquage du chapitre:', props.chapter.id);
};
</script>

View File

@@ -0,0 +1,35 @@
<template>
<div class="p-2 border-t">
<table class="min-w-full table-auto">
<thead>
<tr>
<th class="px-4 py-2 text-left">#</th>
<th class="px-4 py-2 text-left">Titre</th>
<th class="px-4 py-2 text-right">Actions</th>
</tr>
</thead>
<tbody>
<MangaChapter
v-for="chapter in chapters"
:key="chapter.id"
:chapter="chapter"
:manga-slug="mangaSlug" />
</tbody>
</table>
</div>
</template>
<script setup>
import MangaChapter from './MangaChapter.vue';
defineProps({
chapters: {
type: Array,
required: true
},
mangaSlug: {
type: String,
required: true
}
});
</script>

View File

@@ -0,0 +1,60 @@
<template>
<div class="shadow-lg text-white">
<div class="relative h-96 bg-cover bg-center" :style="{ backgroundImage: `url('${manga.imageUrl}')` }">
<div class="absolute inset-0 bg-black opacity-50"></div>
<div class="absolute inset-0 flex flex-row justify-center p-4">
<!-- Image de couverture -->
<div class="hidden mr-12 xl:block 2xl:block">
<img :src="manga.thumbnailUrl" :alt="manga.title" class="max-w-72 max-h-72 ml-4" />
</div>
<!-- Informations du manga -->
<div class="flex flex-col">
<div class="flex items-center mb-4">
<BookmarkIcon class="h-8 w-8 text-white" />
<h1 class="text-3xl font-bold ml-4">{{ manga.title }}</h1>
</div>
<div class="flex items-center mb-4">
<span class="mr-4">{{ manga.year }}</span>
<span>Chapitres: {{ manga.totalChapters }}</span>
</div>
<div class="flex items-center mb-4">
<FolderIcon class="h-6 w-6 text-gray-400 mr-2" />
<span class="truncate">/media/mangas/{{ manga.title }} ({{ manga.year }})</span>
<span class="ml-auto bg-green-600 py-1 px-2 rounded">{{ manga.status || 'Terminé' }}</span>
</div>
<div class="flex items-center mb-4">
<template v-if="manga.tags?.length">
<template v-for="(tag, index) in manga.tags.slice(0, 5)" :key="index">
<span class="bg-gray-700 py-1 px-2 rounded-sm mr-2">{{ tag }}</span>
</template>
<span v-if="manga.tags.length > 5" class="bg-gray-700 py-1 px-2 rounded-sm mr-2">...</span>
</template>
</div>
<div class="mb-4">
<div class="flex items-center mb-2">
<HeartIcon class="h-6 w-6 text-red-500 mr-2" />
<span>{{ manga.rating }}</span>
</div>
<p>{{ manga.description }}</p>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { BookmarkIcon, FolderIcon, HeartIcon } from '@heroicons/vue/24/outline';
defineProps({
manga: {
type: Object,
required: true
}
});
</script>

View File

@@ -0,0 +1,99 @@
<template>
<div class="bg-white rounded-sm shadow mb-2">
<!-- En-tête du volume -->
<div class="relative flex items-center justify-between bg-white p-4 rounded-t-sm">
<!-- Partie gauche -->
<div class="flex items-center space-x-4">
<BookmarkIcon class="h-8 w-8 text-gray-500" />
<h2 class="text-xl font-semibold w-28">Volume {{ String(volume.number).padStart(2, '0') }}</h2>
<div class="flex items-center w-16">
<span
:class="[
'px-2 py-1 text-sm rounded w-full text-center text-white',
{
'bg-red-500': volume.downloadedChapter === 0,
'bg-yellow-500':
volume.downloadedChapter < volume.totalChapter && volume.downloadedChapter > 0,
'bg-green-500': volume.downloadedChapter === volume.totalChapter
}
]">
{{ volume.downloadedChapter }}/{{ volume.totalChapter }}
</span>
</div>
</div>
<!-- Bouton toggle -->
<div class="absolute left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2">
<component
:is="isOpen ? ChevronUpIcon : ChevronDownIcon"
class="h-6 w-6 bg-gray-400 rounded-full p-1 text-white hover:bg-green-500 cursor-pointer"
@click="toggleVolume" />
</div>
<!-- Actions du volume -->
<div class="flex space-x-2 text-xl text-bold">
<button class="w-8 text-center" @click="handleSearch">
<MagnifyingGlassIcon class="h-6 w-6 text-gray-500 hover:text-green-500" />
</button>
<button class="w-8 text-center" @click="handleDownload">
<ArrowDownTrayIcon class="h-6 w-6 text-gray-500 hover:text-green-500" />
</button>
</div>
</div>
<!-- Liste des chapitres -->
<MangaChapterList v-show="isOpen" :chapters="volume.chapters" :manga-slug="mangaSlug" />
<!-- Chevron de fermeture -->
<div v-show="isOpen" class="flex justify-center p-2 py bg-white rounded-b-sm">
<ChevronUpIcon
class="h-6 w-6 bg-gray-400 rounded-full p-1 text-white hover:bg-green-500 cursor-pointer"
@click="toggleVolume" />
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import {
BookmarkIcon,
ChevronUpIcon,
ChevronDownIcon,
MagnifyingGlassIcon,
ArrowDownTrayIcon
} from '@heroicons/vue/24/outline';
import MangaChapterList from './MangaChapterList.vue';
import { useMangaStore } from '../../application/store/mangaStore';
const props = defineProps({
volume: {
type: Object,
required: true
},
mangaSlug: {
type: String,
required: true
},
isOpen: {
type: Boolean,
default: false
}
});
const store = useMangaStore();
const isOpen = ref(props.isOpen);
const toggleVolume = () => {
isOpen.value = !isOpen.value;
};
const handleSearch = async () => {
// TODO: Implémenter la recherche du volume
console.log('Recherche du volume:', props.volume.number);
};
const handleDownload = async () => {
// TODO: Implémenter le téléchargement du volume
console.log('Téléchargement du volume:', props.volume.number);
};
</script>

View File

@@ -0,0 +1,25 @@
<template>
<div class="p-4">
<MangaVolume
v-for="(volume, index) in volumes"
:key="volume.number"
:volume="volume"
:mangaSlug="mangaSlug"
:isOpen="index === 0" />
</div>
</template>
<script setup>
import MangaVolume from './MangaVolume.vue';
const props = defineProps({
volumes: {
type: Array,
required: true
},
mangaSlug: {
type: String,
required: true
}
});
</script>