feat: add inline editing for extracted data and autocomplete for tagging

- Add EditableField component for inline editing of any field
- Update DataRow, LocataireCard, OperationCard to support inline editing
- Add CRUD operations for locataires and operations (add/remove)
- Update JsonViewer to propagate changes via v-model
- Add TagAutocomplete component with search/filter functionality
- Replace select dropdown with autocomplete in TaggingStep
This commit is contained in:
2026-01-19 22:02:28 +01:00
parent aac4d194e3
commit cae8d5b963
8 changed files with 1041 additions and 129 deletions

View File

@@ -66,38 +66,38 @@
<div class="grid grid-cols-1 gap-3">
<!-- Editeur -->
<DataCard title="Editeur" icon="building">
<DataRow label="Nom" :value="data.data?.metadata?.editeur?.nom" />
<DataRow label="Adresse" :value="data.data?.metadata?.editeur?.adresse" />
<DataRow label="Telephone" :value="data.data?.metadata?.editeur?.telephone" />
<DataRow label="SIRET" :value="data.data?.metadata?.editeur?.siret" />
<DataRow label="Nom" :value="data.data?.metadata?.editeur?.nom" @update:value="updateMetadata('editeur.nom', $event)" />
<DataRow label="Adresse" :value="data.data?.metadata?.editeur?.adresse" @update:value="updateMetadata('editeur.adresse', $event)" />
<DataRow label="Telephone" :value="data.data?.metadata?.editeur?.telephone" @update:value="updateMetadata('editeur.telephone', $event)" />
<DataRow label="SIRET" :value="data.data?.metadata?.editeur?.siret" @update:value="updateMetadata('editeur.siret', $event)" />
</DataCard>
<!-- Destinataire -->
<DataCard title="Destinataire" icon="user">
<DataRow label="Nom" :value="data.data?.metadata?.destinataire?.nom" />
<DataRow label="Adresse" :value="data.data?.metadata?.destinataire?.adresse" />
<DataRow label="Nom" :value="data.data?.metadata?.destinataire?.nom" @update:value="updateMetadata('destinataire.nom', $event)" />
<DataRow label="Adresse" :value="data.data?.metadata?.destinataire?.adresse" @update:value="updateMetadata('destinataire.adresse', $event)" />
</DataCard>
<!-- Document -->
<DataCard title="Document" icon="document">
<DataRow label="Reference" :value="data.data?.metadata?.document?.reference" />
<DataRow label="Date" :value="data.data?.metadata?.document?.date" />
<DataRow label="Type" :value="data.data?.metadata?.document?.type" />
<DataRow label="Reference" :value="data.data?.metadata?.document?.reference" @update:value="updateMetadata('document.reference', $event)" />
<DataRow label="Date" :value="data.data?.metadata?.document?.date" type="date" @update:value="updateMetadata('document.date', $event)" />
<DataRow label="Type" :value="data.data?.metadata?.document?.type" @update:value="updateMetadata('document.type', $event)" />
</DataCard>
<!-- Immeuble -->
<DataCard title="Immeuble" icon="home">
<DataRow label="Code" :value="data.data?.metadata?.immeuble?.code" />
<DataRow label="Adresse" :value="data.data?.metadata?.immeuble?.adresse" />
<DataRow label="Ville" :value="data.data?.metadata?.immeuble?.ville" />
<DataRow label="Code postal" :value="data.data?.metadata?.immeuble?.code_postal" />
<DataRow label="Code" :value="data.data?.metadata?.immeuble?.code" @update:value="updateMetadata('immeuble.code', $event)" />
<DataRow label="Adresse" :value="data.data?.metadata?.immeuble?.adresse" @update:value="updateMetadata('immeuble.adresse', $event)" />
<DataRow label="Ville" :value="data.data?.metadata?.immeuble?.ville" @update:value="updateMetadata('immeuble.ville', $event)" />
<DataRow label="Code postal" :value="data.data?.metadata?.immeuble?.code_postal" @update:value="updateMetadata('immeuble.code_postal', $event)" />
</DataCard>
<!-- Solde -->
<DataCard title="Solde" icon="currency">
<DataRow label="Montant" :value="formatCurrency(data.data?.metadata?.solde?.montant)" highlight />
<DataRow label="Type" :value="data.data?.metadata?.solde?.type" />
<DataRow label="Date arrete" :value="data.data?.metadata?.solde?.date_arrete" />
<DataRow label="Montant" :value="data.data?.metadata?.solde?.montant" type="currency" highlight @update:value="updateMetadata('solde.montant', $event)" />
<DataRow label="Type" :value="data.data?.metadata?.solde?.type" @update:value="updateMetadata('solde.type', $event)" />
<DataRow label="Date arrete" :value="data.data?.metadata?.solde?.date_arrete" type="date" @update:value="updateMetadata('solde.date_arrete', $event)" />
</DataCard>
</div>
</DataSection>
@@ -115,7 +115,20 @@
:key="idx"
:locataire="loc"
:index="idx"
@update:locataire="updateLocataire(idx, $event)"
@remove="removeLocataire(idx)"
/>
<!-- Bouton ajouter locataire -->
<button
@click="addLocataire"
class="w-full flex items-center justify-center gap-2 px-3 py-2 text-sm text-blue-600 hover:bg-blue-50 border border-dashed border-blue-300 rounded-lg transition-colors"
>
<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 4v16m8-8H4" />
</svg>
Ajouter un locataire
</button>
</div>
</DataSection>
@@ -132,7 +145,19 @@
:key="idx"
:categorie="group.categorie"
:operations="group.operations"
@update:operations="updateOperationsGroup(group.categorie, $event)"
/>
<!-- Bouton ajouter operation -->
<button
@click="addOperation"
class="w-full flex items-center justify-center gap-2 px-3 py-2 text-sm text-blue-600 hover:bg-blue-50 border border-dashed border-blue-300 rounded-lg transition-colors"
>
<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 4v16m8-8H4" />
</svg>
Ajouter une operation
</button>
</div>
</DataSection>
</div>
@@ -159,6 +184,8 @@ const props = defineProps({
}
})
const emit = defineEmits(['update:data'])
// Grouper les operations par categorie pour l'affichage
const operationsGroupedByCategory = computed(() => {
const operations = props.data?.data?.recapitulatif_operations
@@ -203,11 +230,6 @@ function collapseAll() {
expandedSections.operations = false
}
function formatCurrency(value) {
if (value === null || value === undefined) return '-'
return new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR' }).format(value)
}
async function copyJson() {
if (!props.data) return
@@ -221,4 +243,122 @@ async function copyJson() {
console.error('Failed to copy:', error)
}
}
// ===== FONCTIONS DE MISE A JOUR =====
// Helper pour creer une copie profonde des donnees
function cloneData() {
return JSON.parse(JSON.stringify(props.data))
}
// Helper pour setter une valeur nested
function setNestedValue(obj, path, value) {
const parts = path.split('.')
let current = obj
for (let i = 0; i < parts.length - 1; i++) {
if (!current[parts[i]]) {
current[parts[i]] = {}
}
current = current[parts[i]]
}
current[parts[parts.length - 1]] = value
}
// Mettre a jour les metadonnees
function updateMetadata(path, value) {
const updated = cloneData()
if (!updated.data) updated.data = {}
if (!updated.data.metadata) updated.data.metadata = {}
setNestedValue(updated.data.metadata, path, value)
emit('update:data', updated)
}
// Mettre a jour un locataire
function updateLocataire(index, locataire) {
const updated = cloneData()
if (!updated.data) updated.data = {}
if (!updated.data.situation_locataires) updated.data.situation_locataires = []
updated.data.situation_locataires[index] = locataire
emit('update:data', updated)
}
// Ajouter un locataire
function addLocataire() {
const updated = cloneData()
if (!updated.data) updated.data = {}
if (!updated.data.situation_locataires) updated.data.situation_locataires = []
updated.data.situation_locataires.push({
lot: {
numero: '',
type: ''
},
locataire: {
nom: 'Nouveau locataire'
},
lignes: [],
totaux: {
solde_anterieur: 0,
loyers: 0,
taxes: 0,
provisions: 0,
divers: 0,
total: 0,
regles: 0,
impayes: 0
}
})
emit('update:data', updated)
}
// Supprimer un locataire
function removeLocataire(index) {
const updated = cloneData()
updated.data.situation_locataires.splice(index, 1)
emit('update:data', updated)
}
// Mettre a jour un groupe d'operations (par categorie)
function updateOperationsGroup(categorie, updatedGroupOperations) {
const updated = cloneData()
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)
// 2. Combiner avec les operations mises a jour
updated.data.recapitulatif_operations = [...otherOperations, ...updatedGroupOperations]
emit('update:data', updated)
}
// Ajouter une operation
function addOperation() {
const updated = cloneData()
if (!updated.data) updated.data = {}
if (!updated.data.recapitulatif_operations) updated.data.recapitulatif_operations = []
updated.data.recapitulatif_operations.push({
categorie: 'AUTRES',
sous_categorie: null,
fournisseur: 'Nouveau fournisseur',
description: null,
lot_concerne: null,
lot_numero: null,
montants: {
debit: 0,
credit: 0,
tva: 0,
locatif: 0,
deductible: 0
}
})
emit('update:data', updated)
}
</script>