style(conversion): aligner l'UI de conversion sur le design system import

Ajout du Toolbar avec titre et bouton d'action, restructuration en sections
avec bordures et titres typographiques, harmonisation des espacements et
classes Tailwind avec NewImportPage.vue.
This commit is contained in:
ext.jeremy.guillot@maxicoffee.domains
2026-03-15 20:24:05 +01:00
parent 2243716800
commit 9f83f9c137

View File

@@ -1,84 +1,77 @@
<template> <template>
<div class="flex flex-col h-full bg-gray-50 dark:bg-gray-900"> <div class="flex flex-col h-full">
<div class="overflow-y-auto flex-1"> <Toolbar :config="toolbarConfig" />
<div class="container mx-auto px-4 sm:px-6 lg:px-8 py-8">
<FileUploadArea <div class="overflow-y-auto flex-1">
:selected-file="conversionStore.currentFile" <div class="px-6 py-8">
:disabled="conversionStore.isProcessing"
@file-selected="handleFileSelected"
@file-cleared="handleFileClear"
/>
<div v-if="conversionStore.hasSelectedFile && !conversionStore.hasSucceeded" class="mt-6 flex justify-center"> <!-- Zone d'upload -->
<button <section class="border-t border-gray-200 dark:border-gray-700 pt-6">
@click="handleConvert" <h2 class="text-xs font-semibold text-gray-400 dark:text-gray-500 uppercase tracking-wider mb-4">Fichier</h2>
:disabled="conversionStore.isProcessing" <FileUploadArea
:class="[ :selected-file="conversionStore.currentFile"
'flex items-center gap-2 px-6 py-3 text-white font-medium rounded-lg transition-colors', :disabled="conversionStore.isProcessing"
conversionStore.isProcessing @file-selected="handleFileSelected"
? 'bg-gray-400 cursor-not-allowed' @file-cleared="handleFileClear"
: 'bg-green-600 hover:bg-green-700' />
]" </section>
>
<ArrowPathIcon :class="['w-5 h-5', conversionStore.isProcessing && 'animate-spin']" />
{{ conversionStore.isProcessing ? 'Conversion en cours...' : 'Convertir en CBZ' }}
</button>
</div>
<ConversionProgress <!-- Progression -->
v-if="showProgress" <section v-if="showProgress" class="border-t border-gray-200 dark:border-gray-700 pt-6 mt-6">
class="mt-6" <ConversionProgress
:is-converting="conversionStore.isProcessing" :is-converting="conversionStore.isProcessing"
:progress="conversionStore.conversionProgress" :progress="conversionStore.conversionProgress"
:is-success="conversionStore.hasSucceeded" :is-success="conversionStore.hasSucceeded"
:has-error="conversionStore.hasError" :has-error="conversionStore.hasError"
:error-message="conversionStore.conversionError" :error-message="conversionStore.conversionError"
:file-name="conversionStore.currentFileName" :file-name="conversionStore.currentFileName"
:original-size="conversionStore.currentFile?.size || 0" :original-size="conversionStore.currentFile?.size || 0"
:converted-size="conversionStore.convertedFile?.size || 0" :converted-size="conversionStore.convertedFile?.size || 0"
@download="handleDownload" @download="handleDownload"
@reset="handleReset" @reset="handleReset"
/> />
</section>
<!-- Historique -->
<section v-if="conversionStore.conversionCount > 0" class="border-t border-gray-200 dark:border-gray-700 pt-6 mt-6">
<div class="flex items-center justify-between mb-3">
<h2 class="text-xs font-semibold text-gray-400 dark:text-gray-500 uppercase tracking-wider">Historique</h2>
<button
@click="conversionStore.clearHistory()"
class="text-sm text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 transition-colors"
>
Effacer
</button>
</div>
<div class="divide-y divide-gray-100 dark:divide-gray-700/50">
<div
v-for="(conversion, index) in conversionStore.conversionHistory"
:key="index"
class="flex items-center justify-between py-3"
>
<div>
<p class="text-sm text-gray-900 dark:text-gray-100">{{ conversion.originalName }}</p>
<p class="text-xs text-gray-500 dark:text-gray-400">{{ formatDate(conversion.timestamp) }}</p>
</div>
<div class="text-right text-sm">
<p class="text-gray-600 dark:text-gray-300">
{{ formatFileSize(conversion.originalSize) }} → {{ formatFileSize(conversion.convertedSize) }}
</p>
<p class="text-xs text-green-600">{{ calculateSaving(conversion.originalSize, conversion.convertedSize) }}</p>
</div>
</div>
</div>
</section>
<div v-if="conversionStore.conversionCount > 0" class="mt-8">
<div class="flex items-center justify-between mb-3">
<h3 class="text-sm font-medium text-gray-700 dark:text-gray-300">Historique</h3>
<button
@click="conversionStore.clearHistory()"
class="text-sm text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 transition-colors"
>
Effacer
</button>
</div>
<div class="divide-y divide-gray-200 dark:divide-gray-700">
<div
v-for="(conversion, index) in conversionStore.conversionHistory"
:key="index"
class="flex items-center justify-between py-3"
>
<div>
<p class="text-sm text-gray-900 dark:text-gray-100">{{ conversion.originalName }}</p>
<p class="text-xs text-gray-500 dark:text-gray-400">{{ formatDate(conversion.timestamp) }}</p>
</div>
<div class="text-right text-sm">
<p class="text-gray-600 dark:text-gray-300">
{{ formatFileSize(conversion.originalSize) }} {{ formatFileSize(conversion.convertedSize) }}
</p>
<p class="text-xs text-green-600">{{ calculateSaving(conversion.originalSize, conversion.convertedSize) }}</p>
</div>
</div> </div>
</div>
</div> </div>
</div>
</div> </div>
</div>
</template> </template>
<script setup> <script setup>
import { ArrowPathIcon } from '@heroicons/vue/24/outline'; import { ArrowPathIcon } from '@heroicons/vue/24/outline';
import { computed, onMounted } from 'vue'; import { computed, onMounted } from 'vue';
import Toolbar from '../../../../shared/components/ui/Toolbar.vue';
import { useConversionStore } from '../../application/store/conversionStore'; import { useConversionStore } from '../../application/store/conversionStore';
import { useNotifications } from '../../../../shared/composables/useNotifications'; import { useNotifications } from '../../../../shared/composables/useNotifications';
import ConversionProgress from '../components/ConversionProgress.vue'; import ConversionProgress from '../components/ConversionProgress.vue';
@@ -88,53 +81,68 @@ const conversionStore = useConversionStore();
const { showSuccess, showError } = useNotifications(); const { showSuccess, showError } = useNotifications();
const showProgress = computed(() => const showProgress = computed(() =>
conversionStore.hasSelectedFile && conversionStore.hasSelectedFile &&
(conversionStore.isProcessing || conversionStore.hasSucceeded || conversionStore.hasError) (conversionStore.isProcessing || conversionStore.hasSucceeded || conversionStore.hasError)
); );
const toolbarConfig = computed(() => ({
leftSection: [
{ type: 'label', text: 'Conversion CBR CBZ', class: 'text-sm font-medium' },
],
rightSection: [
...(conversionStore.hasSelectedFile && !conversionStore.hasSucceeded ? [{
type: 'button',
icon: ArrowPathIcon,
label: conversionStore.isProcessing ? 'Conversion en cours...' : 'Convertir en CBZ',
onClick: handleConvert,
disabled: conversionStore.isProcessing,
}] : []),
],
}));
const handleFileSelected = (file) => { const handleFileSelected = (file) => {
conversionStore.selectFile(file); conversionStore.selectFile(file);
}; };
const handleFileClear = () => { const handleFileClear = () => {
conversionStore.resetConversion(); conversionStore.resetConversion();
}; };
const handleConvert = async () => { const handleConvert = async () => {
if (!conversionStore.currentFile) return; if (!conversionStore.currentFile) return;
const success = await conversionStore.convertCurrentFile(); const success = await conversionStore.convertCurrentFile();
if (success) { if (success) {
showSuccess('Conversion réussie !'); showSuccess('Conversion réussie !');
} else { } else {
showError(conversionStore.conversionError ?? 'Échec de la conversion'); showError(conversionStore.conversionError ?? 'Échec de la conversion');
} }
}; };
const handleDownload = () => conversionStore.downloadConvertedFile(); const handleDownload = () => conversionStore.downloadConvertedFile();
const handleReset = () => conversionStore.resetConversion(); const handleReset = () => conversionStore.resetConversion();
const formatFileSize = (bytes) => { const formatFileSize = (bytes) => {
if (bytes === 0) return '0 octets'; if (bytes === 0) return '0 octets';
const k = 1024; const k = 1024;
const sizes = ['octets', 'Ko', 'Mo', 'Go']; const sizes = ['octets', 'Ko', 'Mo', 'Go'];
const i = Math.floor(Math.log(bytes) / Math.log(k)); const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`; return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
}; };
const formatDate = (isoString) => const formatDate = (isoString) =>
new Intl.DateTimeFormat('fr-FR', { new Intl.DateTimeFormat('fr-FR', {
day: 'numeric', day: 'numeric',
month: 'short', month: 'short',
hour: '2-digit', hour: '2-digit',
minute: '2-digit', minute: '2-digit',
}).format(new Date(isoString)); }).format(new Date(isoString));
const calculateSaving = (originalSize, convertedSize) => { const calculateSaving = (originalSize, convertedSize) => {
if (!originalSize || !convertedSize) return ''; if (!originalSize || !convertedSize) return '';
const saving = ((originalSize - convertedSize) / originalSize) * 100; const saving = ((originalSize - convertedSize) / originalSize) * 100;
if (saving > 0) return `-${saving.toFixed(1)}%`; if (saving > 0) return `-${saving.toFixed(1)}%`;
if (saving < 0) return `+${Math.abs(saving).toFixed(1)}%`; if (saving < 0) return `+${Math.abs(saving).toFixed(1)}%`;
return '0%'; return '0%';
}; };
onMounted(() => conversionStore.resetConversion()); onMounted(() => conversionStore.resetConversion());