Gammes de l'API à valider dans la Grille, analyses pleine largeur, référence dans Meilleures ventes

- API /api/v1 : PUT /products/:codein/gamme et POST /gammes déposent une
  proposition (table gammes_a_valider) au lieu d'enregistrer. La Grille la
  charge comme modification non enregistrée (badge « API ») : Enregistrer
  la valide, Annuler la rejette. Lecture : champ gammeAValider, statut
  a_valider dans la réponse ; OpenAPI mise à jour.
- Meilleures ventes, Ventes par mois, Publicités : plus de largeur max
  centrée, les pages occupent toute la largeur.
- Meilleures ventes : colonne Référence (référence fournisseur), recherche
  et export Excel compris.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FaniLFvegAZNXFrFFSStqa
This commit is contained in:
Claude committed 2026-10-06 09:27:07 +00:00
1 parent efd6c00cf0
commit b373dabeaa
21 files changed
+413 -73

No files matched your search

+1 -1
View File
@@ -141,7 +141,7 @@ export default async function AnalyticsPage(props: {
}
return (
<div className="mx-auto w-full max-w-screen-2xl">
<div className="w-full min-w-0">
<PageHeader
icon={CalendarRange}
title="Ventes par mois"
+14 -5
View File
@@ -134,6 +134,14 @@ const COLONNES: DataColumn<Ligne>[] = [
footer: () => "Total",
className: "whitespace-nowrap",
},
{
id: "reference",
header: "Référence",
hint: "Référence fournisseur de l'article",
sortValue: r => r.reference,
cell: r => (r.reference ? <span className="font-mono text-[13px] text-[var(--text-secondary)]">{r.reference}</span> : vide),
className: "whitespace-nowrap",
},
{
id: "libelle",
header: "Désignation",
@@ -170,7 +178,7 @@ const FILTRES: DataFilter<Ligne>[] = [
{ id: "famille", label: "Famille", valueOf: famille, allLabel: "Toutes les familles" },
];
const recherche = (r: Ligne) => [r.codein, r.libelle];
const recherche = (r: Ligne) => [r.codein, r.reference, r.libelle];
interface Props {
dateDebut: string;
@@ -204,21 +212,22 @@ export function HitParadeClient({ dateDebut, dateFin, pivotted }: Props) {
feuille: "Meilleures ventes",
fichier: `meilleures-ventes_${dateDebut}_${dateFin}`,
entetes: [
"Code", "Désignation", "Fournisseur", "Nomenclature",
"Code", "Référence", "Désignation", "Fournisseur", "Nomenclature",
...GROUPES.flatMap(g => [
`Qté ${nomExport(g)}`, `CA TTC ${nomExport(g)}`, `% Marge ${nomExport(g)}`, `Stock ${nomExport(g)}`,
]),
],
largeurs: [12, 40, 25, 30, ...GROUPES.flatMap(() => [10, 14, 12, 10])],
largeurs: [12, 18, 40, 25, 30, ...GROUPES.flatMap(() => [10, 14, 12, 10])],
lignes: rows.map(r => [
r.codein,
r.reference,
r.libelle.trim(),
r.fournisseur,
r.nomenclature_code ? `${r.nomenclature_code} — ${r.nomenclature}` : "",
...GROUPES.flatMap(g => [r[g.qte], r[g.ca], pct(r[g.ca], r[g.marge]), r[g.stock]]),
]),
totaux: [
`TOTAL — ${rows.length} articles`, "", "", "",
`TOTAL — ${rows.length} articles`, "", "", "", "",
...GROUPES.flatMap(g => {
const ca = somme(rows, g.ca);
return [somme(rows, g.qte), ca, pct(ca, somme(rows, g.marge)), somme(rows, g.stock)];
@@ -266,7 +275,7 @@ export function HitParadeClient({ dateDebut, dateFin, pivotted }: Props) {
columns={COLONNES}
rowKey={r => r.codein}
searchIn={recherche}
searchPlaceholder="Code ou désignation…"
searchPlaceholder="Code, référence ou désignation…"
filters={FILTRES}
pageSize={100}
unite="produits"
+4 -1
View File
@@ -13,6 +13,8 @@ export const metadata: Metadata = { title: "Meilleures ventes" };
export interface HitParadePivotRow {
codein: string;
libelle: string;
/** Référence fournisseur de l'article. */
reference: string;
fournisseur: string;
nomenclature_code: string;
nomenclature: string;
@@ -38,6 +40,7 @@ function pivotHitParade(rows: HitParadeRow[]): HitParadePivotRow[] {
map.set(row.codein, {
codein: row.codein,
libelle: row.libelle,
reference: row.reference ?? "",
fournisseur: row.fournisseur,
nomenclature_code: row.nomenclature_code,
nomenclature: row.nomenclature,
@@ -100,7 +103,7 @@ export default async function HitParadePage(props: {
}
return (
<div className="mx-auto w-full max-w-screen-2xl">
<div className="w-full min-w-0">
<PageHeader
icon={Trophy}
title="Meilleures ventes"
+1 -1
View File
@@ -120,7 +120,7 @@ export default async function PublicitesPage(props: {
]);
return (
<div className="mx-auto w-full max-w-screen-2xl">
<div className="w-full min-w-0">
<PageHeader
icon={Megaphone}
title="Publicités"