Surface signalement (flag) tags on the per-compte dashboard

Fetches field_flag alongside the existing entrée/versement data (same
JSON:API include list, same buildRows() shape as home.js's flags/
hasFlag). Two changes:

- Rows already shown in the reste-à-verser/sur-versé/non-liés tables
  get the same amber left-edge accent + tag badges as /lignes when
  they're also flagged.
- New "Lignes signalées" section lists every flagged entrée/versement
  for the selected compte, including ones that don't appear in any of
  the other tables -- a fully-settled line can still carry a flag for
  an unrelated reason (e.g. "client injoignable"), which none of the
  reconciliation-based tables would otherwise surface.

Verified live against an isolated test node plus two real flagged lines
already present in production data (client had already started using
the /lignes signalement feature) -- both the section and the inline
badges render correctly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-06 16:43:44 +02:00
co-authored by Claude Sonnet 5
parent 3f8767b4ef
commit 9ce5d87ace
3 changed files with 62 additions and 7 deletions
@@ -8,6 +8,7 @@
--figli-border: #dcdee2;
--figli-error: #c9312b;
--figli-positive: #1a7f37;
--figli-warning: #b8860b;
font-family: Inter, -apple-system, sans-serif;
max-width: 1200px;
@@ -23,6 +24,7 @@ html.gin--dark-mode #figli-dashboard-app {
--figli-border: #3d3e42;
--figli-error: #ff6b6b;
--figli-positive: #4ade80;
--figli-warning: #f0b429;
}
#figli-dashboard-app h2 {
@@ -124,6 +126,23 @@ html.gin--dark-mode #figli-dashboard-app {
#figli-dashboard-app .figli-recon-table tfoot td { font-weight: 700; border-bottom: none; border-top: 2px solid var(--figli-border); }
#figli-dashboard-app .figli-recon-table a { color: inherit; text-decoration: underline; text-decoration-color: var(--figli-border); }
/* Signalée rows: same amber left-edge accent as /lignes (box-shadow, not
border, so it doesn't fight the row's existing bottom border). */
#figli-dashboard-app .figli-recon-table tr.figli-flag-row td:first-child {
box-shadow: inset 4px 0 0 var(--figli-warning);
}
#figli-dashboard-app .figli-flag-badge {
display: inline-block;
padding: 0.05rem 0.4rem;
margin: 0 0 0 0.4rem;
border-radius: 8px;
font-size: 0.68rem;
font-weight: 600;
background: color-mix(in srgb, var(--figli-warning) 15%, transparent);
color: var(--figli-warning);
white-space: nowrap;
}
/* --- Year bars chart (évolution du solde, entrées vs versements) --- */
#figli-dashboard-app .figli-year-chart {
display: flex;
@@ -42,6 +42,8 @@
const clientTerm = resolve(includedMap, rels.field_client && rels.field_client.data);
const entreeLieeRefs = (rels.field_entree_liee && rels.field_entree_liee.data) || [];
const entreeLieeIds = entreeLieeRefs.map((ref) => ref.id);
const flagRefs = (rels.field_flag && rels.field_flag.data) || [];
const flags = flagRefs.map((ref) => resolve(includedMap, ref)).filter(Boolean).map((t) => t.attributes.name);
const parCompte = {};
const repartitionRefs = (rels.field_repartition && rels.field_repartition.data) || [];
for (const ref of repartitionRefs) {
@@ -61,6 +63,8 @@
libelle: attrs.field_notes || attrs.title,
parCompte,
entreeLieeIds,
flags,
hasFlag: flags.length > 0,
});
}
rows.sort((a, b) => (a.date || '').localeCompare(b.date || ''));
@@ -76,7 +80,7 @@
// autre/ouverture -- is exactly what this page deliberately excludes).
async function fetchEntreesEtVersements() {
const params = new URLSearchParams();
params.set('include', 'field_repartition,field_repartition.field_compte,field_client,field_entree_liee');
params.set('include', 'field_repartition,field_repartition.field_compte,field_client,field_entree_liee,field_flag');
params.set('filter[typeFilter][condition][path]', 'field_type_ligne');
params.set('filter[typeFilter][condition][operator]', 'IN');
params.append('filter[typeFilter][condition][value][]', 'entree');
@@ -286,6 +290,18 @@
.filter((r) => r.type === 'versement' && r.parCompte[this.selectedCompte] !== undefined && r.entreeLieeIds.length === 0)
.sort((a, b) => (b.date || '').localeCompare(a.date || ''));
},
// Toute ligne entrée/versement de ce compte portant un signalement
// (voir /lignes), qu'elle apparaisse déjà dans une des listes
// ci-dessus ou non -- une ligne entièrement soldée peut quand même
// porter un problème sans rapport avec le montant (ex. "client
// injoignable"), auquel cas aucune des trois listes ci-dessus ne
// la montrerait autrement.
lignesSignalees() {
if (!this.selectedCompte) return [];
return this.allRows
.filter((r) => r.hasFlag && r.parCompte[this.selectedCompte] !== undefined)
.sort((a, b) => (b.date || '').localeCompare(a.date || ''));
},
totalResteAVerser() {
return Math.round(this.resteAVerserPositif.reduce((sum, r) => sum + r.residual, 0) * 100) / 100;
},
@@ -52,10 +52,10 @@
<tr><th>Date</th><th>Client</th><th>Libellé</th><th>Attribué</th><th>Déjà versé</th><th>Reste à verser</th></tr>
</thead>
<tbody>
<tr v-for="r in resteAVerserPositif" :key="r.entree.id">
<tr v-for="r in resteAVerserPositif" :key="r.entree.id" :class="{'figli-flag-row': r.entree.hasFlag}">
<td>{{ formatDate(r.entree.date) }}</td>
<td><a :href="ligneHref(r.entree.client)">{{ r.entree.client || '—' }}</a></td>
<td>{{ r.entree.libelle || '—' }}</td>
<td>{{ r.entree.libelle || '—' }}<span v-for="f in r.entree.flags" :key="f" class="figli-flag-badge">{{ f }}</span></td>
<td>{{ formatEur(r.montantAttribue) }}</td>
<td>{{ formatEur(r.dejaVerse) }}</td>
<td class="is-negative">{{ formatEur(r.residual) }}</td>
@@ -76,10 +76,10 @@
<tr><th>Date</th><th>Client</th><th>Libellé</th><th>Attribué</th><th>Versé</th><th>Sur-versé</th></tr>
</thead>
<tbody>
<tr v-for="r in surVerseRows" :key="r.entree.id">
<tr v-for="r in surVerseRows" :key="r.entree.id" :class="{'figli-flag-row': r.entree.hasFlag}">
<td>{{ formatDate(r.entree.date) }}</td>
<td><a :href="ligneHref(r.entree.client)">{{ r.entree.client || '—' }}</a></td>
<td>{{ r.entree.libelle || '—' }}</td>
<td>{{ r.entree.libelle || '—' }}<span v-for="f in r.entree.flags" :key="f" class="figli-flag-badge">{{ f }}</span></td>
<td>{{ formatEur(r.montantAttribue) }}</td>
<td>{{ formatEur(r.dejaVerse) }}</td>
<td class="is-negative">{{ formatEur(-r.residual) }}</td>
@@ -96,16 +96,36 @@
<tr><th>Date</th><th>Client</th><th>Libellé</th><th>Montant</th></tr>
</thead>
<tbody>
<tr v-for="r in versementsNonLies" :key="r.id">
<tr v-for="r in versementsNonLies" :key="r.id" :class="{'figli-flag-row': r.hasFlag}">
<td>{{ formatDate(r.date) }}</td>
<td><a :href="ligneHref(r.client)">{{ r.client || '—' }}</a></td>
<td>{{ r.libelle || '—' }}</td>
<td>{{ r.libelle || '—' }}<span v-for="f in r.flags" :key="f" class="figli-flag-badge">{{ f }}</span></td>
<td class="is-negative">{{ formatEur(r.parCompte[selectedCompte]) }}</td>
</tr>
</tbody>
</table>
</section>
<section class="figli-chart-section" v-if="lignesSignalees.length">
<h2>Lignes signalées</h2>
<p class="figli-note">Entrées et versements de {{ selectedCompte }} portant un signalement (voir la colonne "Signalement" dans le grand livre) -- indépendant du solde, une ligne déjà réglée peut quand même être signalée pour une autre raison.</p>
<table class="figli-recon-table">
<thead>
<tr><th>Date</th><th>Type</th><th>Client</th><th>Libellé</th><th>Signalement</th><th>Montant</th></tr>
</thead>
<tbody>
<tr v-for="r in lignesSignalees" :key="r.id" class="figli-flag-row">
<td>{{ formatDate(r.date) }}</td>
<td>{{ r.type === 'entree' ? 'Entrée' : 'Versement' }}</td>
<td><a :href="ligneHref(r.client)">{{ r.client || '—' }}</a></td>
<td>{{ r.libelle || '—' }}</td>
<td><span v-for="f in r.flags" :key="f" class="figli-flag-badge">{{ f }}</span></td>
<td :class="r.parCompte[selectedCompte] < 0 ? 'is-negative' : ''">{{ formatEur(r.parCompte[selectedCompte]) }}</td>
</tr>
</tbody>
</table>
</section>
<section class="figli-chart-section">
<h2>Entrées vs versements par année</h2>
<p class="figli-note">Part de {{ selectedCompte }} dans les entrées client (vert) et ses versements (rouge), année par année.</p>