- Ajout du store userPreferencesStore (thème, vue, tri, pagination, lecteur) - Page UserPreferencesPage pour configurer toutes les préférences - Câblage des prefs dans HomePage (viewMode, sortBy, itemsPerPage), readerStore (fallback prefs), ChapterReader (autoHide, autoFullscreen, sync), useNotifications (toastDuration) - Thème sombre (dark: Tailwind) sur tous les composants Vue : Layout, Pagination, NotificationToast, MangaCard, MangaVolume, MangaDetails, AddManga, HomePage, ActivityPage, JobItem, MangaDeleteModal, MangaEditModal, MangaPreferredSourcesModal, ManageChaptersModal, MangaChapterList, MangaChapter, ConversionPage, FileUploadArea, ConversionProgress, NewImportPage, FileImportCard, MangaMatchCard, StatusBadge, ImportResults - i18n partiellement initialisé Jeremy Guillot
134 lines
5.8 KiB
Vue
134 lines
5.8 KiB
Vue
<template>
|
|
<tr
|
|
class="border-b border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700/50 transition duration-150 ease-in-out"
|
|
:class="{
|
|
'bg-yellow-50 dark:bg-yellow-900/20': job.status === 'pending',
|
|
'bg-blue-50 dark:bg-blue-900/20': job.status === 'in_progress',
|
|
'bg-green-50 dark:bg-green-900/20': job.status === 'completed',
|
|
'bg-red-50 dark:bg-red-900/20': job.status === 'failed'
|
|
}">
|
|
<td class="py-4 px-4 text-center">
|
|
<input type="checkbox" class="form-checkbox h-5 w-5 text-green-600" />
|
|
</td>
|
|
<td class="py-4 px-4 font-medium">
|
|
<div>{{ jobTypeLabel }}</div>
|
|
<div v-if="job.context?.mangaTitle" class="text-xs text-gray-500 mt-0.5">
|
|
{{ job.context.mangaTitle }}
|
|
</div>
|
|
</td>
|
|
<td class="py-4 px-4">
|
|
<span
|
|
class="px-2 py-1 text-xs rounded-full"
|
|
:class="{
|
|
'bg-yellow-100 dark:bg-yellow-900/40 text-yellow-800 dark:text-yellow-300': job.status === 'pending',
|
|
'bg-blue-100 dark:bg-blue-900/40 text-blue-800 dark:text-blue-300': job.status === 'in_progress',
|
|
'bg-green-100 dark:bg-green-900/40 text-green-800 dark:text-green-300': job.status === 'completed',
|
|
'bg-red-100 dark:bg-red-900/40 text-red-800 dark:text-red-300': job.status === 'failed'
|
|
}">
|
|
{{ job.status }}
|
|
</span>
|
|
</td>
|
|
<td class="py-4 px-4">
|
|
<div v-if="job.error" class="text-sm text-red-600 dark:text-red-400">
|
|
{{ job.error }}
|
|
</div>
|
|
<div v-else-if="job.context?.mangaTitle || job.context?.chapterNumber !== undefined || job.context?.sourceId"
|
|
class="text-sm text-gray-700 dark:text-gray-300 space-y-0.5">
|
|
<div v-if="job.context.mangaTitle" class="font-medium">
|
|
{{ job.context.mangaTitle }}
|
|
</div>
|
|
<div v-if="job.context.chapterNumber !== undefined" class="text-gray-500 dark:text-gray-400">
|
|
Chapitre {{ job.context.chapterNumber }}
|
|
</div>
|
|
<div v-if="job.context.sourceId" class="text-xs text-gray-400 dark:text-gray-500">
|
|
Source : {{ job.context.sourceId }}
|
|
</div>
|
|
</div>
|
|
<div v-else class="text-sm text-gray-600 dark:text-gray-400">
|
|
{{ formatDate(job.createdAt) }}
|
|
</div>
|
|
</td>
|
|
<td class="py-4 px-4">
|
|
<div v-if="job.status === 'in_progress'" class="mt-2">
|
|
<div class="relative bg-gray-200 dark:bg-gray-700 rounded-full h-6 overflow-hidden">
|
|
<div
|
|
class="absolute top-0 left-0 h-full bg-green-400 transition-all duration-300 ease-out"
|
|
:style="{ width: `${job.progress}%` }"></div>
|
|
<div class="absolute inset-0 flex items-center justify-center text-xs font-semibold text-white">
|
|
{{ job.progress }}%
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-else-if="job.status === 'completed'" class="relative bg-gray-200 dark:bg-gray-700 rounded-full h-6 overflow-hidden">
|
|
<div
|
|
class="absolute top-0 left-0 h-full bg-green-400 transition-all duration-300 ease-out"
|
|
style="width: 100%"></div>
|
|
<div class="absolute inset-0 flex items-center justify-center text-xs font-semibold text-white">
|
|
100%
|
|
</div>
|
|
</div>
|
|
<div v-else-if="job.status === 'failed'" class="relative bg-gray-200 dark:bg-gray-700 rounded-full h-6 overflow-hidden">
|
|
<div
|
|
class="absolute top-0 left-0 h-full bg-red-400 transition-all duration-300 ease-out"
|
|
style="width: 100%"></div>
|
|
<div class="absolute inset-0 flex items-center justify-center text-xs font-semibold text-white">
|
|
Erreur
|
|
</div>
|
|
</div>
|
|
<div v-else class="relative bg-gray-200 dark:bg-gray-700 rounded-full h-6 overflow-hidden">
|
|
<div
|
|
class="absolute top-0 left-0 h-full bg-yellow-400 transition-all duration-300 ease-out"
|
|
style="width: 0%"></div>
|
|
<div class="absolute inset-0 flex items-center justify-center text-xs font-semibold text-gray-600 dark:text-gray-300">
|
|
En attente
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="job.maxAttempts > 1 || job.attempts > 0"
|
|
class="text-xs text-gray-400 dark:text-gray-500 mt-1 text-center">
|
|
{{ job.attempts }} / {{ job.maxAttempts }} tentative{{ job.maxAttempts > 1 ? 's' : '' }}
|
|
</div>
|
|
</td>
|
|
<td class="py-4 px-4">
|
|
<button
|
|
@click="onDelete"
|
|
class="text-red-500 hover:text-red-700 transition duration-150 ease-in-out"
|
|
title="Supprimer">
|
|
<TrashIcon class="h-5 w-5" />
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { TrashIcon } from '@heroicons/vue/24/outline';
|
|
import { computed, defineEmits, defineProps } from 'vue';
|
|
|
|
const props = defineProps({
|
|
job: {
|
|
type: Object,
|
|
required: true
|
|
}
|
|
});
|
|
|
|
const emit = defineEmits(['delete']);
|
|
|
|
const JOB_TYPE_LABELS = {
|
|
scraping_job: 'Scraping',
|
|
conversion_job: 'Conversion',
|
|
};
|
|
|
|
const jobTypeLabel = computed(() =>
|
|
JOB_TYPE_LABELS[props.job.type] ?? props.job.type
|
|
);
|
|
|
|
function formatDate(dateString) {
|
|
const date = new Date(dateString);
|
|
return date.toLocaleString();
|
|
}
|
|
|
|
function onDelete() {
|
|
emit('delete', props.job.id);
|
|
}
|
|
</script>
|