gauth-0005-dashboard-v3-reporting · frontend/src
Référence visuelle : Admin Dashboard v2.dc.html (ce projet). Écran cible : /account/:slug/admin/dashboard — remplace le contenu de frontend/src/pages/account/admin/dashboard/index.tsx.
Le but de ce document : décrire chaque composant à introduire, ce qui change par rapport à l'existant, et de quelles données/endpoints chacun dépend. Tout ce qui est marqué [backend] n'est pas servi aujourd'hui.
| Existant | v2 |
|---|---|
PageLayout + 4 <Section> empilées, contenu limité à ~900px | Page pleine largeur (1230px), grille de cartes |
3 OverviewCard (dont une primary violette) | 4 MetricTile avec valeur, variation et sparkline |
| Graphe de participation en pleine largeur, filtres dans la barre d'onglets | Graphe dans une carte, filtres remontés dans un header de page unique |
Campaigns Stats : Tabs (All/Running/Inactive) + Table avec expand des sessions | ProgramsTable : une seule table, statut en Tag, opt-in en Progress, prochaine date de match |
Top Randomers : Table 5 colonnes + recherche + export | TopRandomersList : liste compacte avatar / nom / équipe / compteur |
| — | FunnelCard, SeatsCard, NextMatchesCard, CategoryBreakdownTable (nouveaux) |
Ce qui ne change pas : la route, le garde-fou de permission (EmptyPermission + getPermissions().hasMetricsAccessOverallPermission()), le thème antd (layouts/config.tsx, prefixCls="rc", primaire #4654f1), le modèle umi dashboard.
<EmptyPermission hasPermission={...hasMetricsAccessOverallPermission()}>
<PageLayout title={gettext('Dashboard')} actions={[<DashboardFilters />]}>
<MetricsRow /> {/* 4 colonnes */}
<Row gutter={16}> {/* 16 / 8 */}
<FunnelCard /> <SeatsCard />
</Row>
<ParticipationCard /> {/* pleine largeur */}
<Row gutter={16}>
<ProgramsTable /> <NextMatchesCard />
</Row>
<Row gutter={16}>
<TopRandomersList /> <CategoryBreakdownTable />
</Row>
</PageLayout>
</EmptyPermission>
Grille : Row gutter={[16, 16]}, colonnes Col span={16} / Col span={8} (soit 2fr / 1fr). Les <Section> et leurs titres Overall KPIs, Participation, Campaigns Stats, Top Randomers disparaissent : chaque carte porte son propre titre. components/section reste utilisé ailleurs, ne pas le supprimer.
Fond de page --rc-color-bg-layout (#fbfbfb), cartes en #ffffff sans ombre (boxShadow: 'none').
DashboardFilterspages/account/admin/dashboard/filters.tsx (nouveau) — remplace l'usage de graphs/filters.tsx dans la barre d'onglets.
Contenu : Segmented (30D / 3M / 6M / 12M) + RangePicker + FiltersDropdown (réutiliser tel quel depuis dashboard/graphs/filters.tsx).
Segmented est un raccourci qui écrit start/end dans le modèle : réutiliser getPredefinedRanges() de utils/dates (Last 30 Days / Last Month / Last 6 Months / Last 12 Months) plutôt que de recalculer.onChange → dispatch({ type: 'dashboard/updateFilters', payload: { start, end, campaigns, category } }), comme le fait déjà FilterDropdown.Feb 12 – Aug 12, 2026 · 5 programs · all audiences) se calcule côté client : start.format('ll'), end.format('ll'), campaignsStats.length, et campaigns.length ? interpolate(...) : gettext('all audiences').Props : aucune (branché sur le modèle dashboard).
MetricTile + MetricsRowcomponents/metric-tile/index.tsx (nouveau, à exporter depuis components/index.ts).
type MetricTileProps = {
label: string; // ex. gettext('Members')
value: React.ReactNode; // déjà formaté (espace insécable comme séparateur de milliers)
delta?: number; // variation en % ou en points
deltaUnit?: '%' | 'pts';
trend?: number[]; // série pour la sparkline, ordre chronologique
loading?: boolean; // -> <Skeleton active paragraph={false} />
};
Rendu : Card (body padding: 20), label 13px colorTextTertiary, valeur 28px / 600 / letter-spacing: -0.02em, delta 13px coloré par le signe (colorSuccess / colorError), sparkline 34px de haut en dessous.
Sparkline : <Line> bizcharts sans axes ni légende (padding={0}, <Axis visible={false} />) — c'est la lib déjà utilisée dans le projet, ne pas en introduire une seconde. Le rendu SVG du fichier de design n'est qu'une maquette.
Les 4 tuiles :
| Tuile | Valeur | Source |
|---|---|---|
| Members | nombre de membres | high_level_stats.engaged_users_count (existant) ou reporting → average_members_count |
| Unique members invited | membres uniques invités | reporting → optins_sent_count |
| Opt-in rate | taux d'opt-in | high_level_stats.optin_conversion (existant) |
| Meetings scheduled | RandomCoffees planifiés | reporting → meetings_scheduled_count |
[backend] delta et trend : aucun endpoint ne renvoie de comparaison période / période. Deux options, à trancher avant implémentation :
reporting deux fois (période courante + période précédente de même durée) et calcule la variation ;high_level_stats renvoie previous_* et une série mensuelle.L'option 1 est faisable sans backend : POST reporting accepte déjà { reports, campaigns, start, end }, et randomcoffee_funnel_trend renvoie déjà une série mensuelle (label: 'YYYY-MM') qui alimente directement les sparklines. C'est le chemin recommandé : un seul appel reporting avec reports: ['randomcoffee_funnel_trend'] fournit les 4 séries et les valeurs courantes.
FunnelCardpages/account/admin/dashboard/funnel.tsx (nouveau).
4 étapes en barres horizontales empilées verticalement, chaque étape affichant sa valeur et son taux de conversion par rapport à l'étape précédente :
| Étape | Champ | Taux affiché |
|---|---|---|
| Members in platform | average_members_count | — (référence, barre 100%) |
| Unique members invited | optins_sent_count | invitation_rate = invités / membres |
| Unique members matched | unique_members_matched_count | match_rate = matchés / invités |
| Meetings scheduled | meetings_scheduled_count | scheduling_rate = planifiés / matchés |
Les trois taux sont déjà calculés dans reporting/randomcoffee_data_per_category.tsx : extraire cette logique dans utils/reporting.ts (computeRates(row)) et l'appeler des deux côtés plutôt que de la dupliquer.
Largeur de barre = value / members * 100, dégradé de teinte du primaire du plus clair au plus foncé (#e8eafd, #c3c8f9, #8e97f5, #4654f1). Hauteur de barre 28px, borderRadius: 6.
Source : POST reporting avec reports: ['randomcoffee_funnel_trend'], en sommant/prenant le dernier point de la série selon la métrique. Aucun nouvel endpoint.
SeatsCardpages/account/admin/dashboard/seats.tsx (nouveau).
Progress type="dashboard" (140px) + Tag du plan + ligne X of Y seats used + lien « Manage » vers la page de facturation.
max_seats est déjà présent dans le payload de randomcoffee_funnel_trend (voir reporting/randomcoffee_funnel_trend.tsx).classes/billing.tsx plutôt que de l'inventer. Si l'info n'est pas dispo côté front, masquer le Tag.colorWarning, > 95 % colorError.max_seats est nul ou absent.ParticipationCardReprend dashboard/graphs/participation.tsx sans changement de données : même OptinsRate (série total = Users reached, accepted = Participants), même endpoint participations_stats via dashboard/updateParticipations.
Changements :
Card titrée « Participation over time » ;Segmented Total / By category remplace les Tabs ; « By category » monte le composant Engagement existant (graphs/engagement.tsx, endpoint engagement_stats), avec le CategoriesDropdown déjà présent dans graphs/filters.tsx ;#4654f1 (Users reached) et colorSuccess #17b26a (Participants), au lieu du palette bizcharts par défaut ;ProgramsTableRemplace campaigns-following/index.tsx. Même source : dashboard/getCampaignsStats → campaigns_stats.
Colonnes :
| Colonne | Contenu | Champ |
|---|---|---|
| Program | nom en lien + sous-ligne X reached · Y coffees | name, active_users_count, randomcoffees_count |
| Status | Tag bordered={false} vert « Running » / neutre « Inactive » | status |
| Opt-in rate | Progress size="small" + valeur | optin_conversion |
| Next match | date du prochain envoi | [backend] |
Ce qui est retiré, à confirmer avant de coder :
expandable des sessions (campaigns-following/sessions.tsx) → à conserver si le détail par round est utilisé ; sinon la table de sessions vit déjà sur la page campagne ;CampaignActions (export) → déplacé dans le lien « All programs » du header de carte, ou remis en colonne d'action si l'export par campagne est utilisé.[backend] campaigns_stats ne renvoie pas de date de prochain match. Elle est dérivable côté front depuis la campagne (classes/campaign.ts, getClosestDate(refDate, date, frequency) dans utils/dates) si la fréquence et la date de référence sont dans le payload ; sinon l'ajouter à campaigns_stats.
NextMatchesCardpages/account/admin/dashboard/next-matches.tsx (nouveau).
3 prochains envois : pastille date (mois + jour), nom de campagne, sous-ligne X reached · fréquence · canal. Pied de carte : total des invitations partant dans la semaine.
[backend] même dépendance que la colonne Next match. Si la date de prochain match n'est pas disponible, ne pas livrer cette carte dans la v1 et passer ProgramsTable en pleine largeur.
TopRandomersListRemplace top-randomers/randomers.tsx. Même source : dashboard/getTopRandomers → top_randomers.
Liste de 5 lignes : Avatar (initiales, couleur dérivée de l'index), nom + équipe, compteur à droite. Séparateur 1px solid #f0f0f0 entre les lignes.
Tag circulaire, TrophyOutlined) disparaissent : l'ordre de la liste porte l'information.top_randomers ne renvoie que full_name, email, count, afficher l'email à la place plutôt que d'ajouter un appel.CategoryBreakdownTableRéutilise le rapport randomcoffee_data_per_category (déjà écrit, page /admin/reporting).
Select du header = choix de la catégorie (Department / Office / Seniority) ; la table du reporting affiche toutes les catégories avec une colonne Category, ici on filtre sur une seule et cette colonne disparaît.value (renommée avec le nom de la catégorie), members_count, invitation_rate, match_rate, scheduling_rate. Les trois colonnes de comptes bruts (optins_sent_count, unique_members_matched_count, meetings_scheduled_count) sont retirées pour tenir dans 1/3 de largeur — elles restent sur la page reporting.XX %) et non à deux décimales comme sur la page reporting.POST reporting avec reports: ['randomcoffee_data_per_category']. Aucun nouvel endpoint.Endpoints déjà disponibles, aucun à créer pour le cœur de la page :
| Endpoint | Via | Alimente |
|---|---|---|
companies/:id/high_level_stats/ | dashboard/stats/index.tsx | Members, Opt-in rate |
participations_stats | dashboard/updateParticipations | ParticipationCard |
engagement_stats | dashboard/updateEngagement | ParticipationCard (By category) |
campaigns_stats | dashboard/getCampaignsStats | ProgramsTable |
top_randomers | dashboard/getTopRandomers | TopRandomersList |
reporting (POST {reports, campaigns, start, end}) | pages/account/admin/reporting | MetricTiles, FunnelCard, SeatsCard, CategoryBreakdownTable |
À ajouter au modèle models/dashboard.ts : un effect updateReporting({ start, end, campaigns, reports }) + un reducer setReporting, sur le même patron que updateParticipations (setLoading → call → set*). Le service correspondant va dans services/dashboard.tsx à côté de getParticipations, en réutilisant getUrl('reporting').
Manques côté backend, par ordre d'impact :
NextMatchesCard et la colonne Next match) ;reporting) ;top_randomers (contournable : afficher l'email).layouts/config.tsx (colorPrimary: #4654f1, colorSuccess: #17b26a, colorError: #f04438, colorWarning: #f79009, borderRadius: 6, componentSize: 'large') et les variables CSS --rc-color-*. Les hex de la maquette sont ces tokens.size="middle" avec scroll={{ x }} pour rester lisibles dans une demi-largeur.loading (Skeleton), vide (Empty avec la emptyDescription déjà rédigée dans campaigns-following/index.tsx), erreur.gettext / interpolate, comme dans le code existant.md, les paires 2fr/1fr s'empilent ; onMobile() (utils/msteams) est déjà utilisé dans graphs/filters.tsx pour dégrader la barre de filtres.classes/permission.ts avant de la monter.Nouveaux :
pages/account/admin/dashboard/filters.tsx
pages/account/admin/dashboard/funnel.tsx
pages/account/admin/dashboard/seats.tsx
pages/account/admin/dashboard/next-matches.tsx
pages/account/admin/dashboard/metrics.tsx
components/metric-tile/index.tsx
utils/reporting.ts
Modifiés :
pages/account/admin/dashboard/index.tsx nouvelle composition
pages/account/admin/dashboard/graphs/index.tsx Segmented au lieu des Tabs
pages/account/admin/dashboard/campaigns-following/index.tsx -> ProgramsTable
pages/account/admin/dashboard/top-randomers/randomers.tsx -> liste
pages/account/admin/models/dashboard.ts effect reporting
services/dashboard.tsx getReporting
components/index.ts export MetricTile
Inchangés et réutilisés : dashboard/graphs/filters.tsx (FiltersDropdown, CategoriesDropdown), dashboard/graphs/participation.tsx (OptinsRate), dashboard/graphs/engagement.tsx, reporting/randomcoffee_data_per_category.tsx, utils/dates.ts, components/section.
ProgramsTable.reporting (front seul) ou évolution de high_level_stats (backend) ?campaigns_stats ?