229 lines
8.9 KiB
Vue
229 lines
8.9 KiB
Vue
<template>
|
|
<div>
|
|
<Toolbar :config="toolbarConfig" class="sticky top-16 z-10" />
|
|
|
|
<div class="container mx-auto px-4 py-6">
|
|
<!-- Back Navigation -->
|
|
<div class="mb-6">
|
|
<button
|
|
@click="goBack"
|
|
class="flex items-center space-x-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 transition-colors">
|
|
<ArrowLeftIcon class="w-5 h-5" />
|
|
<span>Retour aux configurations</span>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Loading State -->
|
|
<div v-if="loadingCurrentSource" class="flex justify-center py-12">
|
|
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500"></div>
|
|
</div>
|
|
|
|
<!-- Error State -->
|
|
<div v-else-if="currentSourceError" class="bg-red-50 dark:bg-red-900 border border-red-200 dark:border-red-700 rounded-lg p-4 mb-6">
|
|
<div class="flex items-center">
|
|
<ExclamationTriangleIcon class="w-5 h-5 text-red-400 mr-2" />
|
|
<p class="text-red-800 dark:text-red-200">{{ currentSourceError }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Form -->
|
|
<div v-else class="max-w-4xl mx-auto">
|
|
<ContentSourceForm
|
|
:source="currentSource"
|
|
:saving="saving"
|
|
:error="saveError"
|
|
@submit="handleSubmit"
|
|
@test="handleTest" />
|
|
</div>
|
|
|
|
<!-- Test Results Modal -->
|
|
<div v-if="showTestResults" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-2xl max-h-[80vh] overflow-hidden">
|
|
<div class="p-6 border-b border-gray-200 dark:border-gray-600">
|
|
<div class="flex justify-between items-center">
|
|
<h3 class="text-lg font-semibold">Résultats du test</h3>
|
|
<button
|
|
@click="showTestResults = false"
|
|
class="text-gray-400 hover:text-gray-600">
|
|
<XMarkIcon class="w-6 h-6" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="p-6 overflow-y-auto">
|
|
<div v-if="testResults.success" class="space-y-4">
|
|
<div class="flex items-center text-green-600 mb-4">
|
|
<CheckCircleIcon class="w-5 h-5 mr-2" />
|
|
<span class="font-medium">Test réussi !</span>
|
|
</div>
|
|
|
|
<div>
|
|
<h4 class="font-medium mb-2">URL testée:</h4>
|
|
<code class="block bg-gray-100 dark:bg-gray-700 p-2 rounded text-sm">
|
|
{{ testResults.testedUrl }}
|
|
</code>
|
|
</div>
|
|
|
|
<div v-if="testResults.images && testResults.images.length > 0">
|
|
<h4 class="font-medium mb-2">Images trouvées ({{ testResults.images.length }}):</h4>
|
|
<div class="grid grid-cols-2 gap-2 max-h-64 overflow-y-auto">
|
|
<img
|
|
v-for="(image, index) in testResults.images.slice(0, 6)"
|
|
:key="index"
|
|
:src="image"
|
|
:alt="`Image ${index + 1}`"
|
|
class="w-full h-32 object-cover rounded border"
|
|
@error="handleImageError" />
|
|
</div>
|
|
<p v-if="testResults.images.length > 6" class="text-sm text-gray-500 mt-2">
|
|
Et {{ testResults.images.length - 6 }} autres images...
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-else class="space-y-4">
|
|
<div class="flex items-center text-red-600 mb-4">
|
|
<XCircleIcon class="w-5 h-5 mr-2" />
|
|
<span class="font-medium">Test échoué</span>
|
|
</div>
|
|
|
|
<div>
|
|
<h4 class="font-medium mb-2">Erreur:</h4>
|
|
<div class="bg-red-50 dark:bg-red-900 border border-red-200 dark:border-red-700 rounded p-3">
|
|
<code class="text-sm text-red-800 dark:text-red-200">
|
|
{{ testResults.error }}
|
|
</code>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Success Message -->
|
|
<div v-if="showSuccessMessage" class="fixed bottom-4 right-4 bg-green-500 text-white px-4 py-2 rounded-lg shadow-lg">
|
|
Configuration {{ isEditing ? 'mise à jour' : 'créée' }} avec succès !
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import {
|
|
ArrowLeftIcon,
|
|
CheckCircleIcon,
|
|
ExclamationTriangleIcon,
|
|
XCircleIcon,
|
|
XMarkIcon
|
|
} from '@heroicons/vue/24/outline';
|
|
import { storeToRefs } from 'pinia';
|
|
import { computed, onMounted, ref } from 'vue';
|
|
import { useRoute, useRouter } from 'vue-router';
|
|
import Toolbar from '../../../../shared/components/ui/Toolbar.vue';
|
|
import { useContentSourceStore } from '../../application/store/contentSourceStore';
|
|
import ContentSourceForm from '../components/ContentSourceForm.vue';
|
|
|
|
const route = useRoute();
|
|
const router = useRouter();
|
|
const contentSourceStore = useContentSourceStore();
|
|
|
|
const {
|
|
currentSource,
|
|
loadingCurrentSource,
|
|
currentSourceError,
|
|
saving,
|
|
saveError
|
|
} = storeToRefs(contentSourceStore);
|
|
|
|
// Local state
|
|
const showTestResults = ref(false);
|
|
const showSuccessMessage = ref(false);
|
|
const testResults = ref({});
|
|
|
|
const isEditing = computed(() => !!route.params.id);
|
|
|
|
// Load source if editing, clear if creating new
|
|
onMounted(async () => {
|
|
if (isEditing.value) {
|
|
await contentSourceStore.loadSource(route.params.id);
|
|
} else {
|
|
// Clear current source immediately when creating new
|
|
contentSourceStore.clearCurrentSource();
|
|
}
|
|
});
|
|
|
|
// Toolbar configuration
|
|
const toolbarConfig = {
|
|
leftSection: [],
|
|
rightSection: []
|
|
};
|
|
|
|
// Actions
|
|
const goBack = () => {
|
|
router.push({ name: 'scrapper-configurations' });
|
|
};
|
|
|
|
const handleSubmit = async (formData) => {
|
|
try {
|
|
if (isEditing.value) {
|
|
await contentSourceStore.updateSource(route.params.id, formData);
|
|
} else {
|
|
await contentSourceStore.createSource(formData);
|
|
}
|
|
|
|
// Clear current source and errors before redirecting
|
|
contentSourceStore.clearCurrentSource();
|
|
contentSourceStore.clearErrors();
|
|
|
|
// Show success message briefly then redirect
|
|
showSuccessMessage.value = true;
|
|
|
|
// Use nextTick to ensure the DOM is updated before redirecting
|
|
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
|
|
// Navigate back to list
|
|
await router.push({ name: 'scrapper-configurations' });
|
|
|
|
// Hide success message after navigation
|
|
showSuccessMessage.value = false;
|
|
|
|
} catch (error) {
|
|
console.error('Erreur lors de la sauvegarde:', error);
|
|
// Don't redirect if there's an error
|
|
}
|
|
};
|
|
|
|
const handleTest = async ({ configuration, testData }) => {
|
|
try {
|
|
// Simulate test API call - You'll need to implement this endpoint
|
|
const testUrl = configuration.chapterUrlFormat
|
|
.replace('{slug}', testData.mangaSlug)
|
|
.replace('{chapterNumber}', testData.chapterNumber);
|
|
|
|
// Mock test results for now
|
|
testResults.value = {
|
|
success: true,
|
|
testedUrl: testUrl,
|
|
images: [
|
|
'https://via.placeholder.com/400x600/008000/FFFFFF?text=Page+1',
|
|
'https://via.placeholder.com/400x600/FF0000/FFFFFF?text=Page+2',
|
|
'https://via.placeholder.com/400x600/0000FF/FFFFFF?text=Page+3',
|
|
'https://via.placeholder.com/400x600/FFA500/FFFFFF?text=Page+4'
|
|
]
|
|
};
|
|
|
|
showTestResults.value = true;
|
|
} catch (error) {
|
|
testResults.value = {
|
|
success: false,
|
|
error: error.message
|
|
};
|
|
showTestResults.value = true;
|
|
}
|
|
};
|
|
|
|
const handleImageError = (event) => {
|
|
event.target.style.display = 'none';
|
|
};
|
|
</script>
|