Checkpoint: Budget réel confirmé en ordre décroissant de montant total ; graphique en barres annuel comparant les montants BAP et abonnements ajouté au tableau de bord.
All checks were successful
Validation applicative / TypeScript, tests et build (push) Successful in 3m19s
All checks were successful
Validation applicative / TypeScript, tests et build (push) Successful in 3m19s
This commit is contained in:
@@ -155,12 +155,27 @@ export default function Dashboard() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{stats?.annualSummary?.length ? (
|
{stats?.annualSummary?.length ? (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="h-[300px] rounded-lg border bg-white p-4">
|
||||||
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
|
<BarChart data={[...stats.annualSummary].reverse()} margin={{ top: 8, right: 16, left: 12, bottom: 4 }}>
|
||||||
|
<CartesianGrid strokeDasharray="3 3" vertical={false} />
|
||||||
|
<XAxis dataKey="year" />
|
||||||
|
<YAxis tickFormatter={(value) => new Intl.NumberFormat("fr-FR", { notation: "compact", maximumFractionDigits: 1 }).format(value)} />
|
||||||
|
<Tooltip formatter={(value: number) => formatCurrency(Number(value))} labelFormatter={(year) => `Année ${year}`} />
|
||||||
|
<Legend />
|
||||||
|
<Bar dataKey="bapAmount" name="Montant BAP" fill="#2563eb" radius={[4, 4, 0, 0]} />
|
||||||
|
<Bar dataKey="subscriptionAmount" name="Montant abonnements" fill="#7c3aed" radius={[4, 4, 0, 0]} />
|
||||||
|
</BarChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
<div className="overflow-x-auto rounded-lg border">
|
<div className="overflow-x-auto rounded-lg border">
|
||||||
<table className="w-full min-w-[780px] text-sm">
|
<table className="w-full min-w-[780px] text-sm">
|
||||||
<thead className="bg-muted/60 text-left text-muted-foreground"><tr><th className="px-4 py-3 font-medium">Année</th><th className="px-4 py-3 text-right font-medium">Factures BAP</th><th className="px-4 py-3 text-right font-medium">Montant BAP</th><th className="px-4 py-3 text-right font-medium">Abonnements</th><th className="px-4 py-3 text-right font-medium">Montant abonnements</th><th className="px-4 py-3 text-right font-medium">Total annuel</th></tr></thead>
|
<thead className="bg-muted/60 text-left text-muted-foreground"><tr><th className="px-4 py-3 font-medium">Année</th><th className="px-4 py-3 text-right font-medium">Factures BAP</th><th className="px-4 py-3 text-right font-medium">Montant BAP</th><th className="px-4 py-3 text-right font-medium">Abonnements</th><th className="px-4 py-3 text-right font-medium">Montant abonnements</th><th className="px-4 py-3 text-right font-medium">Total annuel</th></tr></thead>
|
||||||
<tbody>{stats.annualSummary.map((row) => <tr key={row.year} className="border-t hover:bg-muted/30"><td className="px-4 py-3 font-semibold">{row.year}</td><td className="px-4 py-3 text-right">{row.bapCount}</td><td className="px-4 py-3 text-right text-blue-700">{formatCurrency(row.bapAmount)}</td><td className="px-4 py-3 text-right">{row.subscriptionCount}</td><td className="px-4 py-3 text-right text-violet-700">{formatCurrency(row.subscriptionAmount)}</td><td className="px-4 py-3 text-right font-semibold text-emerald-700">{formatCurrency(row.totalAmount)}</td></tr>)}</tbody>
|
<tbody>{stats.annualSummary.map((row) => <tr key={row.year} className="border-t hover:bg-muted/30"><td className="px-4 py-3 font-semibold">{row.year}</td><td className="px-4 py-3 text-right">{row.bapCount}</td><td className="px-4 py-3 text-right text-blue-700">{formatCurrency(row.bapAmount)}</td><td className="px-4 py-3 text-right">{row.subscriptionCount}</td><td className="px-4 py-3 text-right text-violet-700">{formatCurrency(row.subscriptionAmount)}</td><td className="px-4 py-3 text-right font-semibold text-emerald-700">{formatCurrency(row.totalAmount)}</td></tr>)}</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
) : <div className="py-8 text-center text-muted-foreground">Aucune facture finalisée avec une date disponible.</div>}
|
) : <div className="py-8 text-center text-muted-foreground">Aucune facture finalisée avec une date disponible.</div>}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -21,4 +21,13 @@ describe("agrégats de facturation", () => {
|
|||||||
{ supplierName: "SFR", subscriptionCount: 1, subscriptionAmount: 20, nonSubscriptionCount: 1, nonSubscriptionAmount: 100, totalAmount: 120 },
|
{ supplierName: "SFR", subscriptionCount: 1, subscriptionAmount: 20, nonSubscriptionCount: 1, nonSubscriptionAmount: 100, totalAmount: 120 },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("classe le budget du fournisseur le plus facturé au moins facturé", () => {
|
||||||
|
const rows = buildSupplierBudgetSummary([
|
||||||
|
...invoices,
|
||||||
|
{ invoiceDate: "2026-05-22", createdAt: "2026-05-22", status: "completed", isSubscription: 0, supplierName: "Orange", totalAmount: "300" },
|
||||||
|
], "2026", "05");
|
||||||
|
|
||||||
|
expect(rows.map((row) => row.supplierName)).toEqual(["Orange", "SFR"]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
5
todo.md
5
todo.md
@@ -820,3 +820,8 @@
|
|||||||
- [x] Créer l’écran Budget réel à la fin du menu Facturation
|
- [x] Créer l’écran Budget réel à la fin du menu Facturation
|
||||||
- [x] Afficher par fournisseur les montants Abonnement, Hors abonnement et le total
|
- [x] Afficher par fournisseur les montants Abonnement, Hors abonnement et le total
|
||||||
- [x] Ajouter les tests et valider TypeScript, build et affichage en sandbox
|
- [x] Ajouter les tests et valider TypeScript, build et affichage en sandbox
|
||||||
|
|
||||||
|
## Améliorations Budget réel et tableau de bord
|
||||||
|
- [x] Confirmer le tri décroissant par montant total dans le budget
|
||||||
|
- [x] Ajouter un graphique annuel BAP versus abonnements au tableau de bord
|
||||||
|
- [x] Ajouter les tests et valider TypeScript, build et rendu sandbox
|
||||||
|
|||||||
Reference in New Issue
Block a user