feat: reimport pdf
This commit is contained in:
@@ -18,6 +18,14 @@
|
||||
Accueil
|
||||
</router-link>
|
||||
|
||||
<router-link
|
||||
to="/documents"
|
||||
class="px-3 py-1.5 text-sm rounded transition-colors"
|
||||
:class="$route.path === '/documents' ? 'bg-gray-700 text-white' : 'text-gray-400 hover:text-white'"
|
||||
>
|
||||
Documents
|
||||
</router-link>
|
||||
|
||||
<router-link
|
||||
to="/revenus"
|
||||
class="px-3 py-1.5 text-sm rounded transition-colors"
|
||||
|
||||
@@ -327,13 +327,32 @@ function updateOperationsGroup(categorie, updatedGroupOperations) {
|
||||
if (!updated.data) updated.data = {}
|
||||
if (!updated.data.recapitulatif_operations) updated.data.recapitulatif_operations = []
|
||||
|
||||
// Reconstruire la liste complete des operations
|
||||
// 1. Filtrer toutes les operations de cette categorie
|
||||
const otherOperations = updated.data.recapitulatif_operations.filter(op => op.categorie !== categorie)
|
||||
// Reconstruire la liste en preservant l'ordre original
|
||||
const newOperations = []
|
||||
let updatedIndex = 0
|
||||
|
||||
// 2. Combiner avec les operations mises a jour
|
||||
updated.data.recapitulatif_operations = [...otherOperations, ...updatedGroupOperations]
|
||||
// Parcourir les operations originales et remplacer celles de la categorie modifiee
|
||||
for (const op of updated.data.recapitulatif_operations) {
|
||||
if (op.categorie === categorie) {
|
||||
// Remplacer par les operations mises a jour (dans l'ordre)
|
||||
if (updatedIndex < updatedGroupOperations.length) {
|
||||
newOperations.push(updatedGroupOperations[updatedIndex])
|
||||
updatedIndex++
|
||||
}
|
||||
// Si on a supprime des operations, on ne les ajoute pas
|
||||
} else {
|
||||
// Garder les operations des autres categories telles quelles
|
||||
newOperations.push(op)
|
||||
}
|
||||
}
|
||||
|
||||
// Ajouter les nouvelles operations si on en a ajoute
|
||||
while (updatedIndex < updatedGroupOperations.length) {
|
||||
newOperations.push(updatedGroupOperations[updatedIndex])
|
||||
updatedIndex++
|
||||
}
|
||||
|
||||
updated.data.recapitulatif_operations = newOperations
|
||||
emit('update:data', updated)
|
||||
}
|
||||
|
||||
|
||||
@@ -113,6 +113,10 @@ const props = defineProps({
|
||||
type: File,
|
||||
default: null
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
fileName: {
|
||||
type: String,
|
||||
default: 'document.pdf'
|
||||
@@ -131,14 +135,26 @@ const baseScale = ref(1)
|
||||
let pdfDoc = null
|
||||
let renderTask = null
|
||||
|
||||
async function loadPdf(file) {
|
||||
if (!file) return
|
||||
async function loadPdf(source) {
|
||||
if (!source) return
|
||||
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const arrayBuffer = await file.arrayBuffer()
|
||||
let arrayBuffer
|
||||
|
||||
// Support URL
|
||||
if (typeof source === 'string') {
|
||||
const response = await fetch(source)
|
||||
if (!response.ok) throw new Error('Erreur lors du chargement du PDF')
|
||||
arrayBuffer = await response.arrayBuffer()
|
||||
}
|
||||
// Support File
|
||||
else {
|
||||
arrayBuffer = await source.arrayBuffer()
|
||||
}
|
||||
|
||||
const loadingTask = pdfjsLib.getDocument({ data: arrayBuffer })
|
||||
pdfDoc = await loadingTask.promise
|
||||
|
||||
@@ -251,9 +267,10 @@ function onWheel(e) {
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => props.file, async (newFile) => {
|
||||
if (newFile) {
|
||||
await loadPdf(newFile)
|
||||
watch([() => props.file, () => props.url], async ([newFile, newUrl]) => {
|
||||
const source = newUrl || newFile
|
||||
if (source) {
|
||||
await loadPdf(source)
|
||||
} else {
|
||||
pdfDoc = null
|
||||
totalPages.value = 0
|
||||
|
||||
234
frontend/src/pages/DocumentsPage.vue
Normal file
234
frontend/src/pages/DocumentsPage.vue
Normal file
@@ -0,0 +1,234 @@
|
||||
<template>
|
||||
<div class="flex-1 overflow-auto p-6 bg-gray-800">
|
||||
<div class="max-w-6xl mx-auto space-y-6">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-xl font-semibold text-white">Documents importes</h1>
|
||||
<p class="text-sm text-gray-400 mt-1">
|
||||
{{ documents.length }} document{{ documents.length > 1 ? 's' : '' }} en base
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Import button -->
|
||||
<label class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors cursor-pointer flex items-center gap-2">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
|
||||
</svg>
|
||||
Importer un PDF
|
||||
<input
|
||||
type="file"
|
||||
accept="application/pdf,.pdf"
|
||||
class="hidden"
|
||||
@change="onFileSelect"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Loading state -->
|
||||
<div v-if="isLoading" class="bg-gray-900 rounded-lg p-12 text-center">
|
||||
<div class="inline-block animate-spin rounded-full h-8 w-8 border-2 border-gray-600 border-t-blue-400"></div>
|
||||
<p class="text-gray-400 mt-4">Chargement des documents...</p>
|
||||
</div>
|
||||
|
||||
<!-- Empty state -->
|
||||
<div v-else-if="documents.length === 0" class="bg-gray-900 rounded-lg p-12 text-center">
|
||||
<svg class="mx-auto h-16 w-16 text-gray-600 mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
<p class="text-gray-400 text-lg">Aucun document importe</p>
|
||||
<p class="text-gray-500 text-sm mt-2">Importez votre premier PDF pour commencer</p>
|
||||
</div>
|
||||
|
||||
<!-- Documents table -->
|
||||
<div v-else class="bg-gray-900 rounded-lg overflow-hidden border border-gray-700">
|
||||
<table class="w-full">
|
||||
<thead class="bg-gray-800 border-b border-gray-700">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">Document</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">Immeuble</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">Date</th>
|
||||
<th class="px-4 py-3 text-right text-xs font-medium text-gray-400 uppercase tracking-wider">Solde</th>
|
||||
<th class="px-4 py-3 text-center text-xs font-medium text-gray-400 uppercase tracking-wider">Fichiers</th>
|
||||
<th class="px-4 py-3 text-right text-xs font-medium text-gray-400 uppercase tracking-wider">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-800">
|
||||
<tr
|
||||
v-for="doc in documents"
|
||||
:key="doc.id"
|
||||
class="hover:bg-gray-800/50 transition-colors"
|
||||
>
|
||||
<!-- Document info -->
|
||||
<td class="px-4 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex-shrink-0 w-10 h-10 bg-red-500/20 rounded-lg flex items-center justify-center">
|
||||
<svg class="w-5 h-5 text-red-400" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6z"/>
|
||||
<path d="M14 2v6h6M16 13H8M16 17H8M10 9H8"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm font-medium text-white font-mono">{{ doc.reference }}</div>
|
||||
<div class="text-xs text-gray-500">{{ doc.source_file || 'Fichier non stocke' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<!-- Immeuble -->
|
||||
<td class="px-4 py-4">
|
||||
<div class="text-sm text-white">{{ doc.immeuble_adresse || '-' }}</div>
|
||||
<div class="text-xs text-gray-500 font-mono">{{ doc.immeuble_code }}</div>
|
||||
</td>
|
||||
|
||||
<!-- Date -->
|
||||
<td class="px-4 py-4">
|
||||
<div class="text-sm text-white">{{ formatDate(doc.date) }}</div>
|
||||
<div class="text-xs text-gray-500">Importe le {{ formatDateTime(doc.created_at) }}</div>
|
||||
</td>
|
||||
|
||||
<!-- Solde -->
|
||||
<td class="px-4 py-4 text-right">
|
||||
<span
|
||||
:class="[
|
||||
'text-sm font-medium',
|
||||
doc.solde_type === 'crediteur' ? 'text-green-400' : 'text-red-400'
|
||||
]"
|
||||
>
|
||||
{{ formatAmount(doc.solde_montant) }}
|
||||
</span>
|
||||
<div class="text-xs text-gray-500">{{ doc.solde_type }}</div>
|
||||
</td>
|
||||
|
||||
<!-- Files status -->
|
||||
<td class="px-4 py-4">
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<!-- PDF download -->
|
||||
<a
|
||||
v-if="doc.has_pdf"
|
||||
:href="`/api/documents/${doc.id}/pdf`"
|
||||
target="_blank"
|
||||
class="p-1.5 rounded bg-red-500/20 text-red-400 hover:bg-red-500/30 transition-colors"
|
||||
title="Telecharger le PDF"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
</a>
|
||||
<span v-else class="p-1.5 rounded bg-gray-700 text-gray-500" title="PDF non stocke">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</span>
|
||||
|
||||
<!-- JSON download -->
|
||||
<a
|
||||
v-if="doc.has_json"
|
||||
:href="`/api/documents/${doc.id}/json`"
|
||||
target="_blank"
|
||||
class="p-1.5 rounded bg-yellow-500/20 text-yellow-400 hover:bg-yellow-500/30 transition-colors"
|
||||
title="Telecharger le JSON"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</a>
|
||||
<span v-else class="p-1.5 rounded bg-gray-700 text-gray-500" title="JSON non stocke">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<!-- Actions -->
|
||||
<td class="px-4 py-4 text-right">
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<!-- Edit & reimport button -->
|
||||
<button
|
||||
@click="router.push(`/documents/${doc.id}/edit`)"
|
||||
class="px-3 py-1.5 text-xs rounded bg-blue-600/20 text-blue-400 hover:bg-blue-600 hover:text-white transition-colors flex items-center gap-1"
|
||||
title="Editer et reimporter"
|
||||
>
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 1 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||
</svg>
|
||||
Editer
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { pendingFile } from '../store'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
// State
|
||||
const isLoading = ref(true)
|
||||
const documents = ref([])
|
||||
|
||||
// Formatting helpers
|
||||
function formatDate(dateStr) {
|
||||
if (!dateStr) return '-'
|
||||
const [year, month, day] = dateStr.split('-')
|
||||
return `${day}/${month}/${year}`
|
||||
}
|
||||
|
||||
function formatDateTime(isoStr) {
|
||||
if (!isoStr) return '-'
|
||||
const date = new Date(isoStr)
|
||||
return date.toLocaleDateString('fr-FR', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})
|
||||
}
|
||||
|
||||
function formatAmount(amount) {
|
||||
if (amount == null) return '-'
|
||||
return new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR' }).format(amount)
|
||||
}
|
||||
|
||||
// Load documents
|
||||
async function loadDocuments() {
|
||||
isLoading.value = true
|
||||
try {
|
||||
const response = await fetch('/api/documents?limit=100')
|
||||
if (response.ok) {
|
||||
documents.value = await response.json()
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to load documents:', err)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// File selection for new import
|
||||
function onFileSelect(e) {
|
||||
const file = e.target.files[0]
|
||||
if (file && file.name.toLowerCase().endsWith('.pdf')) {
|
||||
pendingFile.value = file
|
||||
router.push('/extract')
|
||||
}
|
||||
e.target.value = ''
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadDocuments()
|
||||
})
|
||||
</script>
|
||||
217
frontend/src/pages/EditDocumentPage.vue
Normal file
217
frontend/src/pages/EditDocumentPage.vue
Normal file
@@ -0,0 +1,217 @@
|
||||
<template>
|
||||
<div class="flex-1 flex flex-col overflow-hidden">
|
||||
<!-- Header avec breadcrumb et actions -->
|
||||
<div class="flex-shrink-0 px-6 py-4 bg-gray-800 border-b border-gray-700">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<div class="flex items-center gap-2 text-sm text-gray-400 mb-1">
|
||||
<router-link to="/documents" class="hover:text-white transition-colors">Documents</router-link>
|
||||
<span>›</span>
|
||||
<span class="text-white">{{ documentData?.reference || 'Chargement...' }}</span>
|
||||
</div>
|
||||
<h1 class="text-xl font-semibold text-white">
|
||||
Édition du document {{ documentData?.reference }}
|
||||
</h1>
|
||||
<p v-if="documentData" class="text-sm text-gray-400 mt-1">
|
||||
{{ documentData.immeuble?.adresse || '-' }} - {{ formatDate(documentData.date) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<button
|
||||
@click="cancel"
|
||||
:disabled="isSaving"
|
||||
class="px-4 py-2 text-sm text-gray-400 hover:text-white transition-colors disabled:opacity-50"
|
||||
>
|
||||
Annuler
|
||||
</button>
|
||||
<button
|
||||
v-if="!showTagging && extractedData"
|
||||
@click="goToTagging"
|
||||
:disabled="isSaving"
|
||||
class="px-4 py-2 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50 flex items-center gap-2"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
|
||||
</svg>
|
||||
Continuer vers le tagging
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading state -->
|
||||
<div v-if="isLoading" class="flex-1 flex items-center justify-center bg-gray-800">
|
||||
<div class="text-center">
|
||||
<div class="inline-block animate-spin rounded-full h-12 w-12 border-4 border-gray-600 border-t-blue-400 mb-4"></div>
|
||||
<p class="text-gray-400">Chargement du document...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Corps : Split view PDF + Données -->
|
||||
<div v-else class="flex-1 flex overflow-hidden">
|
||||
<!-- Gauche : PDF Preview -->
|
||||
<div class="w-1/2 border-r border-gray-700 flex flex-col">
|
||||
<div class="flex-shrink-0 px-4 py-2 bg-gray-700/50 border-b border-gray-700">
|
||||
<span class="text-sm text-gray-300">Aperçu PDF</span>
|
||||
</div>
|
||||
<div class="flex-1 overflow-hidden">
|
||||
<PdfPreview
|
||||
v-if="documentData?.has_pdf"
|
||||
:url="`/api/documents/${documentId}/pdf`"
|
||||
:file-name="documentData?.source_file || 'document.pdf'"
|
||||
class="h-full"
|
||||
/>
|
||||
<div v-else class="h-full flex items-center justify-center bg-gray-800 text-gray-500">
|
||||
<div class="text-center">
|
||||
<svg class="mx-auto h-16 w-16 text-gray-600 mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
<p class="text-lg font-medium">PDF non disponible</p>
|
||||
<p class="text-sm text-gray-600 mt-1">Ce document a été importé sans stockage du PDF</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Droite : Édition ou Tagging -->
|
||||
<div class="w-1/2 flex flex-col bg-gray-50">
|
||||
<!-- Vue 1 : JsonViewer (mode édition) -->
|
||||
<JsonViewer
|
||||
v-if="!showTagging && extractedData"
|
||||
:data="extractedData"
|
||||
@update:data="extractedData = $event"
|
||||
:is-loading="false"
|
||||
class="flex-1"
|
||||
/>
|
||||
|
||||
<!-- Vue 2 : TaggingStep -->
|
||||
<TaggingStep
|
||||
v-if="showTagging && extractedData"
|
||||
:depenses="extractedData.data.recapitulatif_operations"
|
||||
:is-duplicate="true"
|
||||
@save="handleSave"
|
||||
@cancel="showTagging = false"
|
||||
class="flex-1"
|
||||
/>
|
||||
|
||||
<!-- Error message -->
|
||||
<div v-if="saveError" class="flex-shrink-0 px-4 py-3 bg-red-500/10 border-t border-red-500/30">
|
||||
<div class="flex items-center gap-2 text-sm text-red-400">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span>{{ saveError }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import PdfPreview from '../components/PdfPreview.vue'
|
||||
import JsonViewer from '../components/JsonViewer.vue'
|
||||
import TaggingStep from '../components/TaggingStep.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const documentId = route.params.id
|
||||
|
||||
const documentData = ref(null)
|
||||
const extractedData = ref(null)
|
||||
const isLoading = ref(true)
|
||||
const showTagging = ref(false)
|
||||
const isSaving = ref(false)
|
||||
const saveError = ref(null)
|
||||
|
||||
onMounted(async () => {
|
||||
await loadDocument()
|
||||
})
|
||||
|
||||
async function loadDocument() {
|
||||
isLoading.value = true
|
||||
try {
|
||||
const response = await fetch(`/api/documents/${documentId}`)
|
||||
if (!response.ok) {
|
||||
throw new Error('Document non trouvé')
|
||||
}
|
||||
|
||||
documentData.value = await response.json()
|
||||
|
||||
// Formatter les données pour JsonViewer (même structure que ExtractPage)
|
||||
extractedData.value = {
|
||||
source_file: documentData.value.source_file,
|
||||
data: documentData.value.json_data
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error loading document:', err)
|
||||
alert('Erreur lors du chargement du document: ' + err.message)
|
||||
router.push('/documents')
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function formatDate(dateStr) {
|
||||
if (!dateStr) return '-'
|
||||
const [year, month, day] = dateStr.split('-')
|
||||
return `${day}/${month}/${year}`
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
const hasChanges = extractedData.value !== null
|
||||
if (hasChanges) {
|
||||
const confirmed = confirm('Abandonner les modifications ?')
|
||||
if (!confirmed) return
|
||||
}
|
||||
router.push('/documents')
|
||||
}
|
||||
|
||||
function goToTagging() {
|
||||
showTagging.value = true
|
||||
saveError.value = null
|
||||
}
|
||||
|
||||
async function handleSave(depensesTags, shouldOverwrite) {
|
||||
isSaving.value = true
|
||||
saveError.value = null
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/save', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
source_file: extractedData.value.source_file,
|
||||
data: extractedData.value.data,
|
||||
depenses_tags: depensesTags,
|
||||
overwrite: true // Toujours true en mode édition
|
||||
})
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(result.detail || 'Erreur lors de la sauvegarde')
|
||||
}
|
||||
|
||||
if (result.success) {
|
||||
// Succès - redirection vers la liste des documents
|
||||
router.push('/documents')
|
||||
} else {
|
||||
throw new Error(result.message || 'Échec de la sauvegarde')
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Save error:', err)
|
||||
saveError.value = err.message || 'Une erreur est survenue lors de la sauvegarde'
|
||||
// Scroll to top to show error
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||
} finally {
|
||||
isSaving.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -3,6 +3,8 @@ import HomePage from './pages/HomePage.vue'
|
||||
import ExtractPage from './pages/ExtractPage.vue'
|
||||
import AnalyticsPage from './pages/AnalyticsPage.vue'
|
||||
import RevenusPage from './pages/RevenusPage.vue'
|
||||
import DocumentsPage from './pages/DocumentsPage.vue'
|
||||
import EditDocumentPage from './pages/EditDocumentPage.vue'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
@@ -24,6 +26,16 @@ const routes = [
|
||||
path: '/revenus',
|
||||
name: 'revenus',
|
||||
component: RevenusPage
|
||||
},
|
||||
{
|
||||
path: '/documents',
|
||||
name: 'documents',
|
||||
component: DocumentsPage
|
||||
},
|
||||
{
|
||||
path: '/documents/:id/edit',
|
||||
name: 'edit-document',
|
||||
component: EditDocumentPage
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user