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:
@@ -8,6 +8,7 @@
|
|||||||
--figli-border: #dcdee2;
|
--figli-border: #dcdee2;
|
||||||
--figli-error: #c9312b;
|
--figli-error: #c9312b;
|
||||||
--figli-positive: #1a7f37;
|
--figli-positive: #1a7f37;
|
||||||
|
--figli-warning: #b8860b;
|
||||||
|
|
||||||
font-family: Inter, -apple-system, sans-serif;
|
font-family: Inter, -apple-system, sans-serif;
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
@@ -23,6 +24,7 @@ html.gin--dark-mode #figli-dashboard-app {
|
|||||||
--figli-border: #3d3e42;
|
--figli-border: #3d3e42;
|
||||||
--figli-error: #ff6b6b;
|
--figli-error: #ff6b6b;
|
||||||
--figli-positive: #4ade80;
|
--figli-positive: #4ade80;
|
||||||
|
--figli-warning: #f0b429;
|
||||||
}
|
}
|
||||||
|
|
||||||
#figli-dashboard-app h2 {
|
#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 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); }
|
#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) --- */
|
/* --- Year bars chart (évolution du solde, entrées vs versements) --- */
|
||||||
#figli-dashboard-app .figli-year-chart {
|
#figli-dashboard-app .figli-year-chart {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -42,6 +42,8 @@
|
|||||||
const clientTerm = resolve(includedMap, rels.field_client && rels.field_client.data);
|
const clientTerm = resolve(includedMap, rels.field_client && rels.field_client.data);
|
||||||
const entreeLieeRefs = (rels.field_entree_liee && rels.field_entree_liee.data) || [];
|
const entreeLieeRefs = (rels.field_entree_liee && rels.field_entree_liee.data) || [];
|
||||||
const entreeLieeIds = entreeLieeRefs.map((ref) => ref.id);
|
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 parCompte = {};
|
||||||
const repartitionRefs = (rels.field_repartition && rels.field_repartition.data) || [];
|
const repartitionRefs = (rels.field_repartition && rels.field_repartition.data) || [];
|
||||||
for (const ref of repartitionRefs) {
|
for (const ref of repartitionRefs) {
|
||||||
@@ -61,6 +63,8 @@
|
|||||||
libelle: attrs.field_notes || attrs.title,
|
libelle: attrs.field_notes || attrs.title,
|
||||||
parCompte,
|
parCompte,
|
||||||
entreeLieeIds,
|
entreeLieeIds,
|
||||||
|
flags,
|
||||||
|
hasFlag: flags.length > 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
rows.sort((a, b) => (a.date || '').localeCompare(b.date || ''));
|
rows.sort((a, b) => (a.date || '').localeCompare(b.date || ''));
|
||||||
@@ -76,7 +80,7 @@
|
|||||||
// autre/ouverture -- is exactly what this page deliberately excludes).
|
// autre/ouverture -- is exactly what this page deliberately excludes).
|
||||||
async function fetchEntreesEtVersements() {
|
async function fetchEntreesEtVersements() {
|
||||||
const params = new URLSearchParams();
|
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][path]', 'field_type_ligne');
|
||||||
params.set('filter[typeFilter][condition][operator]', 'IN');
|
params.set('filter[typeFilter][condition][operator]', 'IN');
|
||||||
params.append('filter[typeFilter][condition][value][]', 'entree');
|
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)
|
.filter((r) => r.type === 'versement' && r.parCompte[this.selectedCompte] !== undefined && r.entreeLieeIds.length === 0)
|
||||||
.sort((a, b) => (b.date || '').localeCompare(a.date || ''));
|
.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() {
|
totalResteAVerser() {
|
||||||
return Math.round(this.resteAVerserPositif.reduce((sum, r) => sum + r.residual, 0) * 100) / 100;
|
return Math.round(this.resteAVerserPositif.reduce((sum, r) => sum + r.residual, 0) * 100) / 100;
|
||||||
},
|
},
|
||||||
|
|||||||
+26
-6
@@ -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>
|
<tr><th>Date</th><th>Client</th><th>Libellé</th><th>Attribué</th><th>Déjà versé</th><th>Reste à verser</th></tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<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>{{ formatDate(r.entree.date) }}</td>
|
||||||
<td><a :href="ligneHref(r.entree.client)">{{ r.entree.client || '—' }}</a></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.montantAttribue) }}</td>
|
||||||
<td>{{ formatEur(r.dejaVerse) }}</td>
|
<td>{{ formatEur(r.dejaVerse) }}</td>
|
||||||
<td class="is-negative">{{ formatEur(r.residual) }}</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>
|
<tr><th>Date</th><th>Client</th><th>Libellé</th><th>Attribué</th><th>Versé</th><th>Sur-versé</th></tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<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>{{ formatDate(r.entree.date) }}</td>
|
||||||
<td><a :href="ligneHref(r.entree.client)">{{ r.entree.client || '—' }}</a></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.montantAttribue) }}</td>
|
||||||
<td>{{ formatEur(r.dejaVerse) }}</td>
|
<td>{{ formatEur(r.dejaVerse) }}</td>
|
||||||
<td class="is-negative">{{ formatEur(-r.residual) }}</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>
|
<tr><th>Date</th><th>Client</th><th>Libellé</th><th>Montant</th></tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<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>{{ formatDate(r.date) }}</td>
|
||||||
<td><a :href="ligneHref(r.client)">{{ r.client || '—' }}</a></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>
|
<td class="is-negative">{{ formatEur(r.parCompte[selectedCompte]) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</section>
|
</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">
|
<section class="figli-chart-section">
|
||||||
<h2>Entrées vs versements par année</h2>
|
<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>
|
<p class="figli-note">Part de {{ selectedCompte }} dans les entrées client (vert) et ses versements (rouge), année par année.</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user