refactor(ui): bascule le flux extraction/edition en theme sombre
Tout ce flux (visionneuse de donnees, fiches locataire et operation, tagging, autocompletion, panneau de differences) etait en theme clair, au milieu d'une application sombre : le contraste sautait a chaque passage d'une page a l'edition d'un document. Les surfaces, bordures, textes et etats colores suivent desormais la palette commune. Les champs natifs (date, nombre, cases a cocher) recoivent color-scheme: dark pour que le calendrier et les widgets systeme s'affichent en sombre eux aussi. Les fiches de metadonnees se repartissent sur 2 puis 3 colonnes selon la largeur disponible, au lieu d'une seule colonne. Le bouton « Annuler » du bandeau de tagging devient « Retour a l'edition », qui decrit son action reelle. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="bg-gray-50 rounded-lg p-3">
|
||||
<h4 class="text-sm font-medium text-gray-700 mb-2 flex items-center gap-2">
|
||||
<div class="subcard p-3">
|
||||
<h4 class="text-sm font-medium text-gray-300 mb-2 flex items-center gap-2">
|
||||
<!-- Icons -->
|
||||
<svg v-if="icon === 'building'" class="w-4 h-4 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
@update:modelValue="onUpdate"
|
||||
:type="type"
|
||||
:placeholder="'-'"
|
||||
:displayClass="highlight ? 'text-blue-600 text-base font-medium' : 'text-gray-800 font-medium'"
|
||||
:displayClass="highlight ? 'text-blue-400 text-base font-medium' : 'text-gray-200 font-medium'"
|
||||
:fullWidth="false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div class="border border-gray-200 rounded-lg overflow-hidden">
|
||||
<div class="card">
|
||||
<!-- Header -->
|
||||
<button
|
||||
@click="$emit('toggle')"
|
||||
class="w-full flex items-center justify-between px-4 py-3 bg-gray-50 hover:bg-gray-100 transition-colors"
|
||||
class="w-full flex items-center justify-between px-4 py-3 bg-gray-900 hover:bg-gray-800 transition-colors"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<svg
|
||||
@@ -15,15 +15,15 @@
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
<span class="font-semibold text-gray-800">{{ title }}</span>
|
||||
<span v-if="count !== undefined" class="text-xs bg-blue-100 text-blue-700 px-2 py-0.5 rounded-full">
|
||||
<span class="font-semibold text-white">{{ title }}</span>
|
||||
<span v-if="count !== undefined" class="badge badge-info rounded-full">
|
||||
{{ count }}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<!-- Content -->
|
||||
<div v-show="expanded" class="p-4 bg-white">
|
||||
<div v-show="expanded" class="p-4 border-t border-gray-700">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
<span
|
||||
v-if="!isEditing"
|
||||
@click="startEditing"
|
||||
class="cursor-pointer border-b border-dashed border-transparent hover:border-gray-400 hover:bg-yellow-50 px-1 py-0.5 rounded transition-colors min-w-[2rem]"
|
||||
class="cursor-pointer border-b border-dashed border-transparent hover:border-gray-500 hover:bg-gray-700/60 px-1 py-0.5 rounded transition-colors min-w-[2rem]"
|
||||
:class="[
|
||||
displayClass,
|
||||
{ 'text-gray-400 italic': isEmpty }
|
||||
{ 'text-gray-500 italic': isEmpty }
|
||||
]"
|
||||
:title="'Cliquer pour modifier'"
|
||||
>
|
||||
@@ -28,7 +28,7 @@
|
||||
@blur="saveAndClose"
|
||||
@keydown.enter="saveAndClose"
|
||||
@keydown.escape="cancelEditing"
|
||||
class="px-1 py-0.5 border border-blue-400 rounded bg-blue-50 focus:outline-none focus:ring-1 focus:ring-blue-500 text-sm"
|
||||
class="px-1 py-0.5 border border-blue-500 rounded bg-gray-950 text-white focus:outline-none focus:ring-1 focus:ring-blue-500 text-sm [color-scheme:dark]"
|
||||
:class="[
|
||||
inputClass,
|
||||
{ 'w-full': fullWidth, 'w-24': !fullWidth && (type === 'currency' || type === 'number'), 'w-32': !fullWidth && type === 'date' }
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
<div v-if="isCollapsible" class="group">
|
||||
<div
|
||||
@click="toggle"
|
||||
class="flex items-center gap-1 cursor-pointer hover:bg-gray-100 rounded px-1 -mx-1"
|
||||
class="flex items-center gap-1 cursor-pointer hover:bg-gray-800 rounded px-1 -mx-1"
|
||||
>
|
||||
<!-- Expand/collapse icon -->
|
||||
<svg
|
||||
class="w-3 h-3 text-gray-400 transition-transform"
|
||||
class="w-3 h-3 text-gray-500 transition-transform"
|
||||
:class="{ 'rotate-90': isExpanded }"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
@@ -18,11 +18,11 @@
|
||||
</svg>
|
||||
|
||||
<!-- Key name -->
|
||||
<span v-if="!isRoot" class="text-purple-600">{{ name }}</span>
|
||||
<span v-if="!isRoot" class="text-gray-400">: </span>
|
||||
<span v-if="!isRoot" class="text-purple-400">{{ name }}</span>
|
||||
<span v-if="!isRoot" class="text-gray-500">: </span>
|
||||
|
||||
<!-- Preview when collapsed -->
|
||||
<span class="text-gray-400">
|
||||
<span class="text-gray-500">
|
||||
{{ isArray ? '[' : '{' }}
|
||||
<span v-if="!isExpanded" class="text-gray-500">
|
||||
{{ itemCount }} {{ isArray ? 'elements' : 'cles' }}
|
||||
@@ -32,7 +32,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Children -->
|
||||
<div v-if="isExpanded" class="ml-4 border-l border-gray-200 pl-2">
|
||||
<div v-if="isExpanded" class="ml-4 border-l border-gray-700 pl-2">
|
||||
<JsonNode
|
||||
v-for="(value, key) in data"
|
||||
:key="key"
|
||||
@@ -40,14 +40,14 @@
|
||||
:name="String(key)"
|
||||
:is-root="false"
|
||||
/>
|
||||
<span class="text-gray-400">{{ isArray ? ']' : '}' }}</span>
|
||||
<span class="text-gray-500">{{ isArray ? ']' : '}' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Primitive value -->
|
||||
<div v-else class="flex items-start gap-1 py-0.5">
|
||||
<span class="text-purple-600">{{ name }}</span>
|
||||
<span class="text-gray-400">: </span>
|
||||
<span class="text-purple-400">{{ name }}</span>
|
||||
<span class="text-gray-500">: </span>
|
||||
<span :class="valueClass">{{ formattedValue }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -84,11 +84,11 @@ const itemCount = computed(() => {
|
||||
})
|
||||
|
||||
const valueClass = computed(() => {
|
||||
if (props.data === null) return 'text-gray-400 italic'
|
||||
if (typeof props.data === 'string') return 'text-green-600'
|
||||
if (typeof props.data === 'number') return 'text-blue-600'
|
||||
if (typeof props.data === 'boolean') return 'text-orange-600'
|
||||
return 'text-gray-700'
|
||||
if (props.data === null) return 'text-gray-500 italic'
|
||||
if (typeof props.data === 'string') return 'text-green-400'
|
||||
if (typeof props.data === 'number') return 'text-blue-400'
|
||||
if (typeof props.data === 'boolean') return 'text-orange-400'
|
||||
return 'text-gray-300'
|
||||
})
|
||||
|
||||
const formattedValue = computed(() => {
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
<template>
|
||||
<div class="h-full flex flex-col bg-white overflow-hidden">
|
||||
<div class="h-full flex flex-col bg-gray-950 overflow-hidden">
|
||||
<!-- Header -->
|
||||
<div class="flex-shrink-0 flex items-center justify-between px-4 py-2 bg-gray-100 border-b border-gray-200">
|
||||
<span class="text-sm font-semibold text-gray-700">Donnees extraites</span>
|
||||
<div class="flex-shrink-0 flex items-center justify-between px-4 py-2 bg-gray-900 border-b border-gray-700">
|
||||
<span class="card-title">Donnees extraites</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
v-if="data"
|
||||
@click="expandAll"
|
||||
class="px-2 py-1 text-xs text-gray-600 hover:text-gray-800 hover:bg-gray-200 rounded transition-colors"
|
||||
class="btn btn-sm btn-ghost"
|
||||
>
|
||||
Tout deplier
|
||||
</button>
|
||||
<button
|
||||
v-if="data"
|
||||
@click="collapseAll"
|
||||
class="px-2 py-1 text-xs text-gray-600 hover:text-gray-800 hover:bg-gray-200 rounded transition-colors"
|
||||
class="btn btn-sm btn-ghost"
|
||||
>
|
||||
Tout replier
|
||||
</button>
|
||||
<button
|
||||
v-if="data"
|
||||
@click="copyJson"
|
||||
class="flex items-center gap-1 px-2 py-1 text-xs text-gray-600 hover:text-gray-800 hover:bg-gray-200 rounded transition-colors"
|
||||
class="btn btn-sm btn-ghost"
|
||||
>
|
||||
<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="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
@@ -34,8 +34,8 @@
|
||||
<!-- Content -->
|
||||
<div class="flex-1 overflow-auto custom-scrollbar">
|
||||
<!-- Loading state -->
|
||||
<div v-if="isLoading" class="flex flex-col items-center justify-center h-full text-gray-400">
|
||||
<svg class="animate-spin h-10 w-10 mb-3 text-blue-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<div v-if="isLoading" class="flex flex-col items-center justify-center h-full text-gray-500">
|
||||
<svg class="animate-spin h-10 w-10 mb-3 text-blue-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
@@ -43,8 +43,8 @@
|
||||
</div>
|
||||
|
||||
<!-- Empty state -->
|
||||
<div v-else-if="!data" class="flex flex-col items-center justify-center h-full text-gray-400 p-8">
|
||||
<svg class="w-16 h-16 mb-3 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<div v-else-if="!data" class="flex flex-col items-center justify-center h-full text-gray-500 p-8">
|
||||
<svg class="w-16 h-16 mb-3 text-gray-700" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" 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-sm">En attente de l'extraction...</p>
|
||||
@@ -54,7 +54,7 @@
|
||||
<div v-else class="p-4 space-y-3">
|
||||
<!-- Source file -->
|
||||
<div class="text-xs text-gray-500 mb-4">
|
||||
Fichier: <span class="font-medium text-gray-700">{{ data.source_file }}</span>
|
||||
Fichier: <span class="font-medium text-gray-300">{{ data.source_file }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Metadata Section -->
|
||||
@@ -63,7 +63,8 @@
|
||||
:expanded="expandedSections.metadata"
|
||||
@toggle="toggleSection('metadata')"
|
||||
>
|
||||
<div class="grid grid-cols-1 gap-3">
|
||||
<!-- Les fiches metadata se repartissent sur la largeur disponible -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 2xl:grid-cols-3 gap-3">
|
||||
<!-- Editeur -->
|
||||
<DataCard title="Editeur" icon="building">
|
||||
<DataRow label="Nom" :value="data.data?.metadata?.editeur?.nom" @update:value="updateMetadata('editeur.nom', $event)" />
|
||||
@@ -124,7 +125,7 @@
|
||||
<!-- 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"
|
||||
class="w-full flex items-center justify-center gap-2 px-3 py-2 text-sm text-blue-400 hover:bg-blue-500/10 border border-dashed border-blue-500/40 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" />
|
||||
@@ -155,7 +156,7 @@
|
||||
<!-- 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"
|
||||
class="w-full flex items-center justify-center gap-2 px-3 py-2 text-sm text-blue-400 hover:bg-blue-500/10 border border-dashed border-blue-500/40 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" />
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<div ref="rootEl" class="border rounded-lg overflow-hidden transition-all duration-300" :class="changed ? 'border-amber-400 ring-2 ring-amber-400 bg-amber-50' : (isHighlighted ? 'border-blue-400 ring-2 ring-blue-400 bg-blue-50' : 'border-gray-200')">
|
||||
<div ref="rootEl" class="border rounded-lg overflow-hidden transition-all duration-300" :class="changed ? 'border-amber-500 ring-2 ring-amber-500/40 bg-amber-500/5' : (isHighlighted ? 'border-blue-500 ring-2 ring-blue-500/40 bg-blue-500/5' : 'border-gray-700')">
|
||||
<!-- Header -->
|
||||
<div class="w-full flex items-center justify-between px-3 py-2 bg-white hover:bg-gray-50 transition-colors">
|
||||
<div class="w-full flex items-center justify-between px-3 py-2 bg-gray-900 hover:bg-gray-800 transition-colors">
|
||||
<button
|
||||
@click="expanded = !expanded"
|
||||
class="flex items-center gap-3 flex-1 text-left"
|
||||
>
|
||||
<svg
|
||||
class="w-3 h-3 text-gray-400 transition-transform flex-shrink-0"
|
||||
class="w-3 h-3 text-gray-500 transition-transform flex-shrink-0"
|
||||
:class="{ 'rotate-90': expanded }"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
@@ -21,10 +21,10 @@
|
||||
:modelValue="locataire.lot?.numero"
|
||||
@update:modelValue="updateField('lot.numero', $event)"
|
||||
type="text"
|
||||
displayClass="text-xs text-gray-700 font-medium"
|
||||
displayClass="text-xs text-gray-300 font-medium"
|
||||
@click.stop
|
||||
/>
|
||||
<span class="mx-1 text-gray-300">|</span>
|
||||
<span class="mx-1 text-gray-600">|</span>
|
||||
<EditableField
|
||||
:modelValue="locataire.lot?.type"
|
||||
@update:modelValue="updateField('lot.type', $event)"
|
||||
@@ -37,7 +37,7 @@
|
||||
:modelValue="locataire.locataire?.nom"
|
||||
@update:modelValue="updateField('locataire.nom', $event)"
|
||||
type="text"
|
||||
displayClass="font-medium text-gray-800 text-sm"
|
||||
displayClass="font-medium text-white text-sm"
|
||||
placeholder="Sans nom"
|
||||
@click.stop
|
||||
/>
|
||||
@@ -45,7 +45,7 @@
|
||||
<div class="flex items-center gap-2">
|
||||
<span
|
||||
v-if="changed"
|
||||
class="text-[10px] font-semibold uppercase tracking-wide px-1.5 py-0.5 rounded bg-amber-100 text-amber-700 border border-amber-300"
|
||||
class="badge badge-warning text-[10px] uppercase tracking-wide"
|
||||
>
|
||||
Modifié
|
||||
</span>
|
||||
@@ -53,14 +53,14 @@
|
||||
<div class="text-sm font-semibold" :class="totalClass">
|
||||
{{ formatCurrency(locataire.totaux?.total) }}
|
||||
</div>
|
||||
<div v-if="locataire.totaux?.impayes > 0" class="text-xs text-red-500">
|
||||
<div v-if="locataire.totaux?.impayes > 0" class="text-xs text-red-400">
|
||||
Impayes: {{ formatCurrency(locataire.totaux?.impayes) }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Bouton supprimer -->
|
||||
<button
|
||||
@click.stop="$emit('remove')"
|
||||
class="p-1 text-gray-400 hover:text-red-500 hover:bg-red-50 rounded transition-colors"
|
||||
class="p-1 text-gray-500 hover:text-red-400 hover:bg-red-500/10 rounded transition-colors"
|
||||
title="Supprimer ce locataire"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -71,43 +71,43 @@
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div v-show="expanded" class="border-t border-gray-100 bg-gray-50 p-3">
|
||||
<div v-show="expanded" class="border-t border-gray-700 bg-gray-900/60 p-3">
|
||||
<!-- Totaux editables -->
|
||||
<div class="grid grid-cols-4 gap-2 text-xs mb-3">
|
||||
<div class="text-center p-2 bg-white rounded">
|
||||
<div class="text-center p-2 bg-gray-800 border border-gray-700 rounded">
|
||||
<div class="text-gray-500 mb-1">Loyers</div>
|
||||
<EditableField
|
||||
:modelValue="locataire.totaux?.loyers"
|
||||
@update:modelValue="updateField('totaux.loyers', $event)"
|
||||
type="currency"
|
||||
displayClass="font-semibold text-gray-800"
|
||||
displayClass="font-semibold text-gray-200"
|
||||
/>
|
||||
</div>
|
||||
<div class="text-center p-2 bg-white rounded">
|
||||
<div class="text-center p-2 bg-gray-800 border border-gray-700 rounded">
|
||||
<div class="text-gray-500 mb-1">Taxes</div>
|
||||
<EditableField
|
||||
:modelValue="locataire.totaux?.taxes"
|
||||
@update:modelValue="updateField('totaux.taxes', $event)"
|
||||
type="currency"
|
||||
displayClass="font-semibold text-gray-800"
|
||||
displayClass="font-semibold text-gray-200"
|
||||
/>
|
||||
</div>
|
||||
<div class="text-center p-2 bg-white rounded">
|
||||
<div class="text-center p-2 bg-gray-800 border border-gray-700 rounded">
|
||||
<div class="text-gray-500 mb-1">Provisions</div>
|
||||
<EditableField
|
||||
:modelValue="locataire.totaux?.provisions"
|
||||
@update:modelValue="updateField('totaux.provisions', $event)"
|
||||
type="currency"
|
||||
displayClass="font-semibold text-gray-800"
|
||||
displayClass="font-semibold text-gray-200"
|
||||
/>
|
||||
</div>
|
||||
<div class="text-center p-2 bg-white rounded">
|
||||
<div class="text-center p-2 bg-gray-800 border border-gray-700 rounded">
|
||||
<div class="text-gray-500 mb-1">Regles</div>
|
||||
<EditableField
|
||||
:modelValue="locataire.totaux?.regles"
|
||||
@update:modelValue="updateField('totaux.regles', $event)"
|
||||
type="currency"
|
||||
displayClass="font-semibold text-green-600"
|
||||
displayClass="font-semibold text-green-400"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -118,7 +118,7 @@
|
||||
<span class="text-xs text-gray-500 font-medium">Detail des lignes</span>
|
||||
<button
|
||||
@click="addLigne"
|
||||
class="flex items-center gap-1 px-2 py-1 text-xs text-blue-600 hover:bg-blue-50 rounded transition-colors"
|
||||
class="flex items-center gap-1 px-2 py-1 text-xs text-blue-400 hover:bg-blue-500/10 rounded transition-colors"
|
||||
>
|
||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||
@@ -129,7 +129,7 @@
|
||||
<div
|
||||
v-for="(ligne, idx) in locataire.lignes"
|
||||
:key="idx"
|
||||
class="text-xs bg-white rounded p-2"
|
||||
class="text-xs bg-gray-800 border border-gray-700 rounded p-2"
|
||||
>
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<div class="flex items-center gap-2 flex-1">
|
||||
@@ -137,7 +137,7 @@
|
||||
<select
|
||||
:value="ligne.type"
|
||||
@change="updateLigneField(idx, 'type', $event.target.value)"
|
||||
class="px-1.5 py-0.5 rounded text-xs border border-gray-200 bg-gray-50 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
class="px-1.5 py-0.5 rounded text-xs border border-gray-700 bg-gray-950 text-gray-200 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
>
|
||||
<option value="loyer">loyer</option>
|
||||
<option value="solde_anterieur">solde_anterieur</option>
|
||||
@@ -171,7 +171,7 @@
|
||||
@update:modelValue="updateLigneField(idx, 'divers.libelle', $event)"
|
||||
type="text"
|
||||
placeholder="libellé"
|
||||
displayClass="text-xs text-gray-600 italic truncate"
|
||||
displayClass="text-xs text-gray-400 italic truncate"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -182,20 +182,20 @@
|
||||
:modelValue="ligne.divers?.montant"
|
||||
@update:modelValue="updateLigneField(idx, 'divers.montant', $event)"
|
||||
type="currency"
|
||||
displayClass="font-medium text-gray-800"
|
||||
displayClass="font-medium text-gray-200"
|
||||
/>
|
||||
<EditableField
|
||||
v-else
|
||||
:modelValue="ligne.total || ligne.loyers"
|
||||
@update:modelValue="updateLigneField(idx, 'total', $event)"
|
||||
type="currency"
|
||||
displayClass="font-medium text-gray-800"
|
||||
displayClass="font-medium text-gray-200"
|
||||
/>
|
||||
|
||||
<!-- Bouton supprimer ligne -->
|
||||
<button
|
||||
@click="removeLigne(idx)"
|
||||
class="p-1 text-gray-400 hover:text-red-500 hover:bg-red-50 rounded transition-colors"
|
||||
class="p-1 text-gray-500 hover:text-red-400 hover:bg-red-500/10 rounded transition-colors"
|
||||
title="Supprimer cette ligne"
|
||||
>
|
||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -211,7 +211,7 @@
|
||||
<div v-else class="text-center py-2">
|
||||
<button
|
||||
@click="addLigne"
|
||||
class="flex items-center gap-1 px-3 py-1.5 text-xs text-blue-600 hover:bg-blue-50 rounded transition-colors mx-auto"
|
||||
class="flex items-center gap-1 px-3 py-1.5 text-xs text-blue-400 hover:bg-blue-500/10 rounded transition-colors mx-auto"
|
||||
>
|
||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||
@@ -267,9 +267,9 @@ watch(() => props.highlighted, (val) => {
|
||||
|
||||
const totalClass = computed(() => {
|
||||
const total = props.locataire.totaux?.total || 0
|
||||
if (total > 0) return 'text-green-600'
|
||||
if (total < 0) return 'text-red-600'
|
||||
return 'text-gray-600'
|
||||
if (total > 0) return 'text-green-400'
|
||||
if (total < 0) return 'text-red-400'
|
||||
return 'text-gray-400'
|
||||
})
|
||||
|
||||
function formatCurrency(value) {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<div class="border border-gray-200 rounded-lg overflow-hidden">
|
||||
<div class="card">
|
||||
<!-- Header -->
|
||||
<div class="w-full flex items-center justify-between px-3 py-2 bg-white hover:bg-gray-50 transition-colors">
|
||||
<div class="w-full flex items-center justify-between px-3 py-2 bg-gray-900 hover:bg-gray-800 transition-colors">
|
||||
<button
|
||||
@click="expanded = !expanded"
|
||||
class="flex items-center gap-2 flex-1 text-left"
|
||||
>
|
||||
<svg
|
||||
class="w-3 h-3 text-gray-400 transition-transform flex-shrink-0"
|
||||
class="w-3 h-3 text-gray-500 transition-transform flex-shrink-0"
|
||||
:class="{ 'rotate-90': expanded }"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
@@ -15,25 +15,25 @@
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
<span class="font-medium text-gray-800 text-sm">{{ formatCategorie(categorie) }}</span>
|
||||
<span class="text-xs bg-gray-100 text-gray-600 px-2 py-0.5 rounded-full">
|
||||
<span class="font-medium text-white text-sm">{{ formatCategorie(categorie) }}</span>
|
||||
<span class="badge badge-neutral rounded-full">
|
||||
{{ operations.length }} operation{{ operations.length > 1 ? 's' : '' }}
|
||||
</span>
|
||||
</button>
|
||||
<div class="flex items-center gap-2">
|
||||
<span
|
||||
v-if="changedIndices.length"
|
||||
class="text-[10px] font-semibold uppercase tracking-wide px-1.5 py-0.5 rounded bg-amber-100 text-amber-700 border border-amber-300"
|
||||
class="badge badge-warning text-[10px] uppercase tracking-wide"
|
||||
>
|
||||
{{ changedIndices.length }} modifiée{{ changedIndices.length > 1 ? 's' : '' }}
|
||||
</span>
|
||||
<div class="text-sm font-semibold text-gray-700">
|
||||
<div class="text-sm font-semibold text-gray-200">
|
||||
{{ formatCurrency(totalDebit) }}
|
||||
</div>
|
||||
<!-- Bouton ajouter operation -->
|
||||
<button
|
||||
@click.stop="addOperation"
|
||||
class="p-1 text-gray-400 hover:text-blue-500 hover:bg-blue-50 rounded transition-colors"
|
||||
class="p-1 text-gray-500 hover:text-blue-400 hover:bg-blue-500/10 rounded transition-colors"
|
||||
title="Ajouter une operation"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -44,20 +44,20 @@
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div v-show="expanded" class="border-t border-gray-100 bg-gray-50">
|
||||
<div class="divide-y divide-gray-100">
|
||||
<div v-show="expanded" class="border-t border-gray-700 bg-gray-900/60">
|
||||
<div class="divide-y divide-gray-800">
|
||||
<div
|
||||
v-for="(op, idx) in operations"
|
||||
:key="idx"
|
||||
:ref="el => { if (el) opRefs[idx] = el }"
|
||||
class="px-3 py-2 bg-white text-xs transition-all duration-300"
|
||||
:class="changedIndices.includes(idx) ? 'ring-2 ring-amber-400 bg-amber-50' : (highlightedIdx === idx ? 'ring-2 ring-blue-400 bg-blue-50' : '')"
|
||||
class="px-3 py-2 text-xs transition-all duration-300"
|
||||
:class="changedIndices.includes(idx) ? 'ring-2 ring-amber-500/50 bg-amber-500/5' : (highlightedIdx === idx ? 'ring-2 ring-blue-500/50 bg-blue-500/5' : '')"
|
||||
>
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<div class="flex-1 min-w-0 space-y-1">
|
||||
<!-- Sous-categorie -->
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-gray-400 text-xs w-20">Sous-cat:</span>
|
||||
<span class="text-gray-500 text-xs w-20">Sous-cat:</span>
|
||||
<EditableField
|
||||
:modelValue="op.sous_categorie"
|
||||
@update:modelValue="updateOperationField(idx, 'sous_categorie', $event)"
|
||||
@@ -69,19 +69,19 @@
|
||||
|
||||
<!-- Fournisseur -->
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-gray-400 text-xs w-20">Fournisseur:</span>
|
||||
<span class="text-gray-500 text-xs w-20">Fournisseur:</span>
|
||||
<EditableField
|
||||
:modelValue="op.fournisseur"
|
||||
@update:modelValue="updateOperationField(idx, 'fournisseur', $event)"
|
||||
type="text"
|
||||
placeholder="-"
|
||||
displayClass="font-medium text-gray-800"
|
||||
displayClass="font-medium text-gray-200"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-gray-400 text-xs w-20">Description:</span>
|
||||
<span class="text-gray-500 text-xs w-20">Description:</span>
|
||||
<EditableField
|
||||
:modelValue="op.description"
|
||||
@update:modelValue="updateOperationField(idx, 'description', $event)"
|
||||
@@ -94,20 +94,20 @@
|
||||
|
||||
<!-- Lot -->
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-gray-400 text-xs w-20">Lot:</span>
|
||||
<span class="text-gray-500 text-xs w-20">Lot:</span>
|
||||
<EditableField
|
||||
:modelValue="op.lot_numero"
|
||||
@update:modelValue="updateOperationField(idx, 'lot_numero', $event)"
|
||||
type="text"
|
||||
placeholder="N°"
|
||||
displayClass="text-blue-500 font-medium text-xs"
|
||||
displayClass="text-blue-400 font-medium text-xs"
|
||||
/>
|
||||
<EditableField
|
||||
:modelValue="op.lot_concerne"
|
||||
@update:modelValue="updateOperationField(idx, 'lot_concerne', $event)"
|
||||
type="text"
|
||||
placeholder="Concerne"
|
||||
displayClass="text-blue-500 text-xs"
|
||||
displayClass="text-blue-400 text-xs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -116,48 +116,48 @@
|
||||
<!-- Montants -->
|
||||
<div class="text-right space-y-0.5">
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<span class="text-gray-400 text-xs">Debit:</span>
|
||||
<span class="text-gray-500 text-xs">Debit:</span>
|
||||
<EditableField
|
||||
:modelValue="op.montants?.debit"
|
||||
@update:modelValue="updateOperationField(idx, 'montants.debit', $event)"
|
||||
type="currency"
|
||||
displayClass="font-semibold text-gray-800"
|
||||
displayClass="font-semibold text-gray-200"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<span class="text-gray-400 text-xs">Credit:</span>
|
||||
<span class="text-gray-500 text-xs">Credit:</span>
|
||||
<EditableField
|
||||
:modelValue="op.montants?.credit"
|
||||
@update:modelValue="updateOperationField(idx, 'montants.credit', $event)"
|
||||
type="currency"
|
||||
displayClass="text-green-600"
|
||||
displayClass="text-green-400"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<span class="text-gray-400 text-xs">TVA:</span>
|
||||
<span class="text-gray-500 text-xs">TVA:</span>
|
||||
<EditableField
|
||||
:modelValue="op.montants?.tva"
|
||||
@update:modelValue="updateOperationField(idx, 'montants.tva', $event)"
|
||||
type="currency"
|
||||
displayClass="text-gray-400"
|
||||
displayClass="text-gray-500"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<span class="text-gray-400 text-xs">Locatif:</span>
|
||||
<span class="text-gray-500 text-xs">Locatif:</span>
|
||||
<EditableField
|
||||
:modelValue="op.montants?.locatif"
|
||||
@update:modelValue="updateOperationField(idx, 'montants.locatif', $event)"
|
||||
type="currency"
|
||||
displayClass="text-green-600 text-xs"
|
||||
displayClass="text-green-400 text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<span class="text-gray-400 text-xs">Deductible:</span>
|
||||
<span class="text-gray-500 text-xs">Deductible:</span>
|
||||
<EditableField
|
||||
:modelValue="op.montants?.deductible"
|
||||
@update:modelValue="updateOperationField(idx, 'montants.deductible', $event)"
|
||||
type="currency"
|
||||
displayClass="text-orange-600 text-xs"
|
||||
displayClass="text-orange-400 text-xs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -165,7 +165,7 @@
|
||||
<!-- Bouton supprimer -->
|
||||
<button
|
||||
@click="removeOperation(idx)"
|
||||
class="p-1 text-gray-400 hover:text-red-500 hover:bg-red-50 rounded transition-colors"
|
||||
class="p-1 text-gray-500 hover:text-red-400 hover:bg-red-500/10 rounded transition-colors"
|
||||
title="Supprimer cette operation"
|
||||
>
|
||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -178,11 +178,11 @@
|
||||
</div>
|
||||
|
||||
<!-- Totaux de la categorie -->
|
||||
<div class="px-3 py-2 bg-gray-100 border-t border-gray-200 text-xs">
|
||||
<div class="px-3 py-2 bg-gray-800 border-t border-gray-700 text-xs">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="font-medium text-gray-600">Total {{ formatCategorie(categorie) }}</span>
|
||||
<span class="font-medium text-gray-400">Total {{ formatCategorie(categorie) }}</span>
|
||||
<div class="text-right">
|
||||
<span class="font-semibold text-gray-800">{{ formatCurrency(totalDebit) }}</span>
|
||||
<span class="font-semibold text-white">{{ formatCurrency(totalDebit) }}</span>
|
||||
<span v-if="totalTva > 0" class="text-gray-500 ml-2">(TVA: {{ formatCurrency(totalTva) }})</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<div class="flex flex-col h-full bg-gray-800 overflow-hidden relative">
|
||||
<div class="flex flex-col h-full bg-gray-950 overflow-hidden relative">
|
||||
<!-- Header -->
|
||||
<div class="flex-shrink-0 flex items-center px-3 py-2 bg-gray-900 text-white">
|
||||
<div class="flex-shrink-0 flex items-center px-3 py-2 bg-gray-900 border-b border-gray-700 text-white">
|
||||
<span class="text-sm font-medium truncate">{{ fileName }}</span>
|
||||
</div>
|
||||
|
||||
<!-- États -->
|
||||
<div
|
||||
v-if="loading"
|
||||
class="flex-1 flex items-center justify-center text-gray-400 text-sm"
|
||||
class="flex-1 flex items-center justify-center text-gray-500 text-sm"
|
||||
>
|
||||
Chargement du PDF…
|
||||
</div>
|
||||
@@ -24,12 +24,12 @@
|
||||
<div
|
||||
v-else-if="hasSource"
|
||||
ref="container"
|
||||
class="flex-1 overflow-auto p-2 bg-gray-800"
|
||||
class="flex-1 overflow-auto custom-scrollbar p-2 bg-gray-950"
|
||||
/>
|
||||
<!-- Empty state -->
|
||||
<div
|
||||
v-else
|
||||
class="flex-1 flex items-center justify-center text-gray-400 text-sm"
|
||||
class="flex-1 flex items-center justify-center text-gray-500 text-sm"
|
||||
>
|
||||
Aucun PDF sélectionné
|
||||
</div>
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
<div
|
||||
class="border-2 border-dashed rounded-lg p-8 text-center transition-colors"
|
||||
:class="{
|
||||
'border-blue-500 bg-blue-50': isDragging,
|
||||
'border-gray-300 hover:border-gray-400': !isDragging && !file,
|
||||
'border-green-500 bg-green-50': file && !isLoading
|
||||
'border-blue-500 bg-blue-500/10': isDragging,
|
||||
'border-gray-700 hover:border-gray-600': !isDragging && !file,
|
||||
'border-green-500 bg-green-500/10': file && !isLoading
|
||||
}"
|
||||
@dragenter.prevent="onDragEnter"
|
||||
@dragleave.prevent="onDragLeave"
|
||||
@@ -13,11 +13,11 @@
|
||||
>
|
||||
<!-- Loading state -->
|
||||
<div v-if="isLoading" class="flex flex-col items-center gap-4">
|
||||
<svg class="animate-spin h-12 w-12 text-blue-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<svg class="animate-spin h-12 w-12 text-blue-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
<p class="text-gray-600">Extraction en cours...</p>
|
||||
<p class="text-gray-400">Extraction en cours...</p>
|
||||
</div>
|
||||
|
||||
<!-- File selected state -->
|
||||
@@ -25,18 +25,18 @@
|
||||
<svg class="h-12 w-12 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<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-700 font-medium">{{ file.name }}</p>
|
||||
<p class="text-gray-200 font-medium">{{ file.name }}</p>
|
||||
<p class="text-sm text-gray-500">{{ formatFileSize(file.size) }}</p>
|
||||
<div class="flex gap-3">
|
||||
<button
|
||||
@click="extractPdf"
|
||||
class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||
class="btn btn-primary"
|
||||
>
|
||||
Extraire les donnees
|
||||
</button>
|
||||
<button
|
||||
@click="clearFile"
|
||||
class="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 transition-colors"
|
||||
class="btn btn-secondary"
|
||||
>
|
||||
Annuler
|
||||
</button>
|
||||
@@ -45,12 +45,12 @@
|
||||
|
||||
<!-- Default upload state -->
|
||||
<div v-else class="flex flex-col items-center gap-4">
|
||||
<svg class="h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="h-12 w-12 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
|
||||
</svg>
|
||||
<div>
|
||||
<p class="text-gray-600 mb-1">Glissez un PDF ici ou</p>
|
||||
<label class="cursor-pointer text-blue-600 hover:text-blue-700 font-medium">
|
||||
<p class="text-gray-400 mb-1">Glissez un PDF ici ou</p>
|
||||
<label class="cursor-pointer text-blue-400 hover:text-blue-300 font-medium">
|
||||
cliquez pour selectionner
|
||||
<input
|
||||
type="file"
|
||||
@@ -60,12 +60,12 @@
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<p class="text-sm text-gray-400">Fichiers PDF uniquement</p>
|
||||
<p class="text-sm text-gray-500">Fichiers PDF uniquement</p>
|
||||
</div>
|
||||
|
||||
<!-- Error message -->
|
||||
<div v-if="error" class="mt-4 p-3 bg-red-50 border border-red-200 rounded-lg">
|
||||
<p class="text-red-600 text-sm">{{ error }}</p>
|
||||
<div v-if="error" class="mt-4 p-3 bg-red-500/10 border border-red-500/30 rounded-lg">
|
||||
<p class="text-red-400 text-sm">{{ error }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
<template>
|
||||
<div class="flex-shrink-0 border-b border-amber-300 bg-amber-50">
|
||||
<div class="flex-shrink-0 border-b border-amber-500/40 bg-amber-500/10">
|
||||
<!-- En-tête -->
|
||||
<div class="flex items-center justify-between px-4 py-2">
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<svg class="w-4 h-4 text-amber-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-4 h-4 text-amber-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
</svg>
|
||||
<span v-if="diff.summary.total === 0" class="font-medium text-amber-800">
|
||||
<span v-if="diff.summary.total === 0" class="font-medium text-amber-300">
|
||||
Nouvelle extraction — aucune différence avec la version précédente
|
||||
</span>
|
||||
<span v-else class="font-medium text-amber-800">
|
||||
<span v-else class="font-medium text-amber-300">
|
||||
Nouvelle extraction — {{ diff.summary.total }} différence{{ diff.summary.total > 1 ? 's' : '' }}
|
||||
<span class="text-amber-600 font-normal">
|
||||
<span class="text-amber-400/80 font-normal">
|
||||
({{ diff.summary.metadata }} métadonnée{{ diff.summary.metadata > 1 ? 's' : '' }},
|
||||
{{ diff.summary.locataires }} locataire{{ diff.summary.locataires > 1 ? 's' : '' }},
|
||||
{{ diff.summary.operations }} opération{{ diff.summary.operations > 1 ? 's' : '' }})
|
||||
@@ -22,7 +22,7 @@
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
@click="$emit('revert')"
|
||||
class="px-2 py-1 text-xs text-amber-700 hover:text-amber-900 hover:bg-amber-100 rounded transition-colors"
|
||||
class="px-2 py-1 text-xs text-amber-300 hover:text-amber-100 hover:bg-amber-500/20 rounded transition-colors"
|
||||
title="Restaurer les données d'avant la re-extraction"
|
||||
>
|
||||
Revenir à la version précédente
|
||||
@@ -30,13 +30,13 @@
|
||||
<button
|
||||
v-if="diff.summary.total > 0"
|
||||
@click="open = !open"
|
||||
class="px-2 py-1 text-xs text-amber-700 hover:text-amber-900 hover:bg-amber-100 rounded transition-colors"
|
||||
class="px-2 py-1 text-xs text-amber-300 hover:text-amber-100 hover:bg-amber-500/20 rounded transition-colors"
|
||||
>
|
||||
{{ open ? 'Masquer le détail' : 'Voir le détail' }}
|
||||
</button>
|
||||
<button
|
||||
@click="$emit('close')"
|
||||
class="p-1 text-amber-500 hover:text-amber-700 hover:bg-amber-100 rounded transition-colors"
|
||||
class="p-1 text-amber-400 hover:text-amber-200 hover:bg-amber-500/20 rounded transition-colors"
|
||||
title="Fermer"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -50,33 +50,33 @@
|
||||
<div v-if="open && diff.summary.total > 0" class="max-h-56 overflow-auto px-4 pb-3 space-y-3 text-xs">
|
||||
<!-- Métadonnées -->
|
||||
<div v-if="diff.metadata.length">
|
||||
<div class="font-semibold text-amber-800 mb-1">Métadonnées</div>
|
||||
<div class="font-semibold text-amber-300 mb-1">Métadonnées</div>
|
||||
<div class="space-y-0.5">
|
||||
<div v-for="(c, i) in diff.metadata" :key="'m' + i" class="flex items-baseline gap-2">
|
||||
<span class="text-gray-500 w-40 flex-shrink-0">{{ c.label }}</span>
|
||||
<span class="text-red-600 line-through">{{ c.before }}</span>
|
||||
<span class="text-gray-400">→</span>
|
||||
<span class="text-green-700 font-medium">{{ c.after }}</span>
|
||||
<span class="text-red-400 line-through">{{ c.before }}</span>
|
||||
<span class="text-gray-500">→</span>
|
||||
<span class="text-green-400 font-medium">{{ c.after }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Locataires -->
|
||||
<div v-if="diff.locataires.length">
|
||||
<div class="font-semibold text-amber-800 mb-1">Locataires</div>
|
||||
<div class="font-semibold text-amber-300 mb-1">Locataires</div>
|
||||
<div class="space-y-1.5">
|
||||
<div v-for="(loc, i) in diff.locataires" :key="'l' + i" class="bg-white/60 rounded p-1.5">
|
||||
<div v-for="(loc, i) in diff.locataires" :key="'l' + i" class="bg-gray-900/70 border border-gray-700 rounded p-1.5">
|
||||
<div class="flex items-center gap-2 mb-0.5">
|
||||
<span :class="kindClass(loc.kind)" class="text-[10px] uppercase font-semibold px-1 rounded">
|
||||
{{ kindLabel(loc.kind) }}
|
||||
</span>
|
||||
<span class="font-medium text-gray-700">{{ loc.title }}</span>
|
||||
<span class="font-medium text-gray-200">{{ loc.title }}</span>
|
||||
</div>
|
||||
<div v-for="(f, j) in loc.fields" :key="j" class="flex items-baseline gap-2 pl-2">
|
||||
<span class="text-gray-500 w-24 flex-shrink-0">{{ f.label }}</span>
|
||||
<span class="text-red-600 line-through break-all">{{ f.before }}</span>
|
||||
<span class="text-gray-400">→</span>
|
||||
<span class="text-green-700 font-medium break-all">{{ f.after }}</span>
|
||||
<span class="text-red-400 line-through break-all">{{ f.before }}</span>
|
||||
<span class="text-gray-500">→</span>
|
||||
<span class="text-green-400 font-medium break-all">{{ f.after }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -84,9 +84,9 @@
|
||||
|
||||
<!-- Opérations -->
|
||||
<div v-if="diff.operations.length">
|
||||
<div class="font-semibold text-amber-800 mb-1">Opérations</div>
|
||||
<div class="font-semibold text-amber-300 mb-1">Opérations</div>
|
||||
<div class="space-y-1.5">
|
||||
<div v-for="(op, i) in diff.operations" :key="'o' + i" class="bg-white/60 rounded p-1.5">
|
||||
<div v-for="(op, i) in diff.operations" :key="'o' + i" class="bg-gray-900/70 border border-gray-700 rounded p-1.5">
|
||||
<div class="flex items-center gap-2 mb-0.5">
|
||||
<span :class="kindClass(op.kind)" class="text-[10px] uppercase font-semibold px-1 rounded">
|
||||
{{ kindLabel(op.kind) }}
|
||||
@@ -94,8 +94,8 @@
|
||||
<span v-if="op.index >= 0" class="text-gray-500">opération #{{ op.index + 1 }}</span>
|
||||
</div>
|
||||
<div class="pl-2">
|
||||
<div class="text-red-600 line-through break-all">{{ op.before }}</div>
|
||||
<div class="text-green-700 font-medium break-all">{{ op.after }}</div>
|
||||
<div class="text-red-400 line-through break-all">{{ op.before }}</div>
|
||||
<div class="text-green-400 font-medium break-all">{{ op.after }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -125,8 +125,8 @@ function kindLabel(kind) {
|
||||
}
|
||||
|
||||
function kindClass(kind) {
|
||||
if (kind === 'added') return 'bg-green-100 text-green-700'
|
||||
if (kind === 'removed') return 'bg-red-100 text-red-700'
|
||||
return 'bg-amber-100 text-amber-700'
|
||||
if (kind === 'added') return 'bg-green-500/20 text-green-300'
|
||||
if (kind === 'removed') return 'bg-red-500/20 text-red-300'
|
||||
return 'bg-amber-500/20 text-amber-300'
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
@keydown.escape="closeDropdown"
|
||||
@keydown.tab="closeDropdown"
|
||||
:placeholder="placeholder"
|
||||
class="w-full px-3 py-2 pr-8 border rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors"
|
||||
class="w-full px-3 py-2 pr-8 border rounded-lg text-sm text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors"
|
||||
:class="inputClasses"
|
||||
/>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<button
|
||||
v-if="modelValue"
|
||||
@mousedown.prevent="clearSelection"
|
||||
class="absolute right-2 top-1/2 -translate-y-1/2 p-1 text-gray-400 hover:text-gray-600 rounded"
|
||||
class="absolute right-2 top-1/2 -translate-y-1/2 p-1 text-gray-500 hover:text-white rounded"
|
||||
type="button"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -34,7 +34,7 @@
|
||||
<button
|
||||
v-else
|
||||
@mousedown.prevent="toggleDropdown"
|
||||
class="absolute right-2 top-1/2 -translate-y-1/2 p-1 text-gray-400 hover:text-gray-600 rounded"
|
||||
class="absolute right-2 top-1/2 -translate-y-1/2 p-1 text-gray-500 hover:text-white rounded"
|
||||
type="button"
|
||||
>
|
||||
<svg class="w-4 h-4 transition-transform" :class="{ 'rotate-180': isOpen }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -46,7 +46,7 @@
|
||||
<!-- Dropdown -->
|
||||
<div
|
||||
v-show="isOpen && filteredOptions.length > 0"
|
||||
class="absolute z-50 w-full mt-1 bg-white border border-gray-200 rounded-lg shadow-lg max-h-60 overflow-y-auto"
|
||||
class="absolute z-50 w-full mt-1 bg-gray-900 border border-gray-700 rounded-lg shadow-lg max-h-60 overflow-y-auto custom-scrollbar"
|
||||
>
|
||||
<div
|
||||
v-for="(option, index) in filteredOptions"
|
||||
@@ -55,8 +55,8 @@
|
||||
@mouseenter="highlightedIndex = index"
|
||||
class="px-3 py-2 cursor-pointer text-sm transition-colors"
|
||||
:class="{
|
||||
'bg-blue-50 text-blue-700': index === highlightedIndex,
|
||||
'hover:bg-gray-50': index !== highlightedIndex
|
||||
'bg-blue-500/20 text-blue-300': index === highlightedIndex,
|
||||
'text-gray-300 hover:bg-gray-800': index !== highlightedIndex
|
||||
}"
|
||||
>
|
||||
<span v-html="highlightMatch(option.nom)"></span>
|
||||
@@ -66,7 +66,7 @@
|
||||
<!-- No results -->
|
||||
<div
|
||||
v-show="isOpen && searchQuery && filteredOptions.length === 0"
|
||||
class="absolute z-50 w-full mt-1 bg-white border border-gray-200 rounded-lg shadow-lg p-3 text-sm text-gray-500"
|
||||
class="absolute z-50 w-full mt-1 bg-gray-900 border border-gray-700 rounded-lg shadow-lg p-3 text-sm text-gray-500"
|
||||
>
|
||||
Aucun tag trouvé pour "{{ searchQuery }}"
|
||||
</div>
|
||||
@@ -112,12 +112,12 @@ const isUserInput = ref(true)
|
||||
// Compute input classes based on state
|
||||
const inputClasses = computed(() => {
|
||||
if (props.modelValue && props.hasPrediction) {
|
||||
return 'bg-green-50 border-green-300'
|
||||
return 'bg-gray-950 border-green-500/60'
|
||||
}
|
||||
if (!props.modelValue) {
|
||||
return 'bg-yellow-50 border-yellow-300'
|
||||
return 'bg-gray-950 border-amber-500/60'
|
||||
}
|
||||
return 'border-gray-300'
|
||||
return 'bg-gray-950 border-gray-700'
|
||||
})
|
||||
|
||||
// Get selected option name for display
|
||||
@@ -249,6 +249,6 @@ function highlightMatch(text) {
|
||||
|
||||
const query = searchQuery.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
const regex = new RegExp(`(${query})`, 'gi')
|
||||
return text.replace(regex, '<mark class="bg-yellow-200 rounded px-0.5">$1</mark>')
|
||||
return text.replace(regex, '<mark class="bg-amber-500/30 text-amber-200 rounded px-0.5">$1</mark>')
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<template>
|
||||
<div class="flex flex-col h-full bg-gray-50">
|
||||
<div class="flex flex-col h-full bg-gray-950">
|
||||
<!-- Header -->
|
||||
<div class="flex-shrink-0 p-4 bg-white border-b border-gray-200">
|
||||
<div class="flex-shrink-0 p-4 bg-gray-900 border-b border-gray-700">
|
||||
<div class="flex items-center gap-2">
|
||||
<h2 class="text-lg font-semibold text-gray-900">Tagging des dépenses</h2>
|
||||
<span v-if="isDuplicate" class="px-2 py-1 text-xs font-medium bg-orange-100 text-orange-800 rounded">
|
||||
<h2 class="page-title">Tagging des dépenses</h2>
|
||||
<span v-if="isDuplicate" class="badge badge-warning">
|
||||
⚠️ Écrasement
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600 mt-1">
|
||||
<p class="page-subtitle">
|
||||
{{ taggedCount }} / {{ depenses.length }} dépenses taggées
|
||||
<span v-if="predictions.length > 0" class="text-green-600 ml-2">
|
||||
<span v-if="predictions.length > 0" class="text-green-400 ml-2">
|
||||
({{ predictedCount }} pré-remplies automatiquement)
|
||||
</span>
|
||||
</p>
|
||||
<p v-if="isDuplicate" class="text-xs text-orange-600 mt-1">
|
||||
<p v-if="isDuplicate" class="text-xs text-amber-400 mt-1">
|
||||
Les données existantes seront remplacées lors de la validation
|
||||
</p>
|
||||
</div>
|
||||
@@ -22,8 +22,8 @@
|
||||
<!-- Loading state -->
|
||||
<div v-if="isLoadingPredictions" class="flex-1 flex items-center justify-center">
|
||||
<div class="text-center">
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
|
||||
<p class="text-gray-600">Analyse en cours...</p>
|
||||
<div class="spinner h-12 w-12 mx-auto mb-4"></div>
|
||||
<p class="text-gray-400">Analyse en cours...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -32,15 +32,15 @@
|
||||
<div
|
||||
v-for="(depense, index) in depenses"
|
||||
:key="index"
|
||||
class="bg-white rounded-lg shadow-sm border border-gray-200 p-4"
|
||||
class="card card-body"
|
||||
>
|
||||
<!-- Expense info -->
|
||||
<div class="flex items-start justify-between gap-4 mb-3">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="font-medium text-gray-900 truncate">
|
||||
<div class="font-medium text-white truncate">
|
||||
{{ depense.fournisseur || 'Fournisseur inconnu' }}
|
||||
</div>
|
||||
<div class="text-sm text-gray-600 truncate">
|
||||
<div class="text-sm text-gray-400 truncate">
|
||||
{{ depense.sous_categorie || depense.description || 'Aucune description' }}
|
||||
</div>
|
||||
<div class="text-sm text-gray-500 mt-1">
|
||||
@@ -49,7 +49,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Tag selector -->
|
||||
<div class="flex-shrink-0 w-56">
|
||||
<div class="flex-shrink-0 w-64">
|
||||
<TagAutocomplete
|
||||
v-model="selectedTags[index]"
|
||||
:options="availableTags"
|
||||
@@ -64,9 +64,9 @@
|
||||
<!-- Prediction info -->
|
||||
<div
|
||||
v-if="predictions[index] && predictions[index].tag_id"
|
||||
class="flex items-center gap-2 text-xs text-gray-600 bg-blue-50 border border-blue-200 rounded px-3 py-2"
|
||||
class="flex items-center gap-2 text-xs text-gray-300 bg-blue-500/10 border border-blue-500/30 rounded px-3 py-2"
|
||||
>
|
||||
<svg class="w-4 h-4 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-4 h-4 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span>
|
||||
@@ -77,7 +77,7 @@
|
||||
<!-- No prediction warning -->
|
||||
<div
|
||||
v-else-if="predictions[index] && !predictions[index].tag_id && !selectedTags[index]"
|
||||
class="flex items-center gap-2 text-xs text-gray-600 bg-gray-50 border border-gray-200 rounded px-3 py-2"
|
||||
class="flex items-center gap-2 text-xs text-gray-400 bg-gray-800 border border-gray-700 rounded px-3 py-2"
|
||||
>
|
||||
<svg class="w-4 h-4 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
@@ -88,23 +88,23 @@
|
||||
</div>
|
||||
|
||||
<!-- Action buttons -->
|
||||
<div class="flex-shrink-0 p-4 bg-white border-t border-gray-200">
|
||||
<div class="flex-shrink-0 p-4 bg-gray-900 border-t border-gray-700">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<button
|
||||
@click="$emit('cancel')"
|
||||
class="px-4 py-2 text-gray-700 bg-gray-100 rounded-lg hover:bg-gray-200 transition-colors"
|
||||
class="btn btn-secondary"
|
||||
>
|
||||
Annuler
|
||||
Retour à l'édition
|
||||
</button>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<span v-if="taggedCount < depenses.length" class="text-sm text-yellow-600">
|
||||
<span v-if="taggedCount < depenses.length" class="text-sm text-amber-400">
|
||||
⚠️ {{ depenses.length - taggedCount }} dépense(s) sans tag
|
||||
</span>
|
||||
<button
|
||||
@click="validateAndSave"
|
||||
:disabled="taggedCount === 0"
|
||||
class="px-6 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors font-medium disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
class="btn btn-primary px-6"
|
||||
>
|
||||
Valider et enregistrer
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user