- Add mangaTitle to ScrapingJob context at creation time - Fix job.js constructor to map failureReason, attempts, maxAttempts, context from API - JobItem: show readable type label, manga name, chapter number, source and attempts counter Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
134 lines
5.3 KiB
Vue
134 lines
5.3 KiB
Vue
<template>
|
|
<tr
|
|
class="border-b border-gray-200 hover:bg-gray-50 transition duration-150 ease-in-out"
|
|
:class="{
|
|
'bg-yellow-50': job.status === 'pending',
|
|
'bg-blue-50': job.status === 'in_progress',
|
|
'bg-green-50': job.status === 'completed',
|
|
'bg-red-50': 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 text-yellow-800': job.status === 'pending',
|
|
'bg-blue-100 text-blue-800': job.status === 'in_progress',
|
|
'bg-green-100 text-green-800': job.status === 'completed',
|
|
'bg-red-100 text-red-800': job.status === 'failed'
|
|
}">
|
|
{{ job.status }}
|
|
</span>
|
|
</td>
|
|
<td class="py-4 px-4">
|
|
<div v-if="job.error" class="text-sm text-red-600">
|
|
{{ job.error }}
|
|
</div>
|
|
<div v-else-if="job.context?.mangaTitle || job.context?.chapterNumber !== undefined || job.context?.sourceId"
|
|
class="text-sm text-gray-700 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">
|
|
Chapitre {{ job.context.chapterNumber }}
|
|
</div>
|
|
<div v-if="job.context.sourceId" class="text-xs text-gray-400">
|
|
Source : {{ job.context.sourceId }}
|
|
</div>
|
|
</div>
|
|
<div v-else class="text-sm text-gray-600">
|
|
{{ 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 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 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 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 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">
|
|
En attente
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="job.maxAttempts > 1 || job.attempts > 0"
|
|
class="text-xs text-gray-400 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>
|