Tableau de bord
Réunir sur une grille des vues enregistrées de n’importe quelle table, des nombres et des notes, avec des filtres qui atteignent chaque table.
Un tableau de bord fonctionne comme ceux de Notion : YayawDashboard place des vues enregistrées de n’importe laquelle de vos tables, des nombres et des notes sur une grille de 4 colonnes. Chaque vue garde son propre mode d’affichage (table, liste, kanban, galerie, calendrier, graphique, formulaire) et charge ses fiches depuis votre serveur. Les filtres du tableau de bord, comme une période ou une catégorie, restreignent chaque widget auquel ils s’appliquent. Les utilisateurs qui ont le droit de modifier déplacent, redimensionnent et ajoutent des widgets ; les autres voient un tableau de bord en lecture seule. React et Vue se comportent de la même façon.
Depuis v3.8.0, un tableau de bord peut aussi être un écran de votre application : des sections dans l’ordre (grilles de cartes et flux en pleine largeur), des tables pleine page avec leur barre d’outils et leurs vues enregistrées, des blocs affichés par votre application, et des sources chargées seulement quand un widget les lit. Voir Écrans. Depuis v3.9.0, les personnes qui peuvent le modifier le font dans l’éditeur de l’écran : des sections, une fenêtre de widget sur vos sources et vos blocs, et la table réelle comme éditeur de vue. Voir Modifier un écran.
Le tableau de bord est un élément de registre optionnel : la table n’a aucune dépendance de grille tant que vous ne l’installez pas. Les deux éditions utilisent gridstack.js, chargé à la demande avec la première grille sur ordinateur, jamais par la table elle-même ni sur téléphone.
Installer
npx shadcn@latest add https://table.yayaw.app/r/yayaw-table-dashboard.jsonCette commande installe gridstack et les composants shadcn button, calendar, checkbox, dialog, dropdown-menu, input, native-select, popover et textarea, et ajoute les fichiers sous components/ui/yayaw-table-dashboard/. Les composants calendar et popover dessinent le filtre de période, comme dans la vue Formulaire. Comme toute table, le tableau de bord s’affiche dans le QueryClientProvider de votre application (voir Setup) : ses widgets partagent ce client unique.
npx shadcn-vue@latest add https://table.yayaw.app/r/yayaw-table-vue-dashboard.jsonCette commande installe gridstack et ajoute les fichiers sous components/ui/yayaw-table-vue/dashboard/. Les contrôles utilisent reka-ui, comme la table.
Les vues affichées en graphique ou en calendrier, ainsi que les widgets Nombre, ont besoin des moteurs de rendu optionnels de la vue Graphique et du Calendrier : installez-les et passez-les dans displayModeRenderers, comme pour une table. Un widget graphique sans le moteur de rendu des graphiques affiche une erreur à la place du graphique.
Essayer un tableau de bord
« Openings overview » affiche deux nombres, une note, un graphique en barres, une liste et un Kanban : des vues enregistrées de la même table Projets. Choisissez une catégorie ou une période de début pour restreindre tous les widgets. Choisissez Modifier pour déplacer, redimensionner ou ajouter des widgets, puis Terminé pour enregistrer le tableau de bord en mémoire. Ouvrir la vue complète indique la table et la vue qu’une vraie application ouvrirait. Agrandir donne à la grille un onglet complet. Le tableau de bord, ses vues enregistrées et son stockage sont communs à React et Vue ; voir les données des exemples pour la table.
Données de démonstration. Les modifications restent dans cet aperçu.
"use client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { NuqsAdapter } from "nuqs/adapters/react";
import { useMemo, useState } from "react";
import { Toaster } from "sonner";
import { calendarRenderer } from "@/components/ui/yayaw-table-calendar/calendar-renderer";
import { chartRenderer } from "@/components/ui/yayaw-table-chart/chart-renderer";
import type { Dashboard } from "@/components/ui/yayaw-table-dashboard/dashboard-model";
import {
type DashboardTableSource,
YayawDashboard,
} from "@/components/ui/yayaw-table-dashboard/yayaw-dashboard";
import { createProjectRows, geocodeProjectPlaces } from "../shared/projects";
import {
createDashboardStorage,
projectsOverview,
projectViews,
} from "../shared/projects-dashboard";
import { createMemoryActions } from "./memory-actions";
import type { ExamplePresentation } from "./presentation";
import { projectConfig } from "./project-config";
const renderers = { calendar: calendarRenderer, chart: chartRenderer };
export default function ProjectsDashboard({
locale,
}: ExamplePresentation = {}) {
const [queryClient] = useState(() => new QueryClient());
const [opened, setOpened] = useState("");
const { storage, tables } = useMemo(() => {
const projects: DashboardTableSource = {
name: "Projects",
config: projectConfig,
actions: createMemoryActions(createProjectRows(), {
geocode: geocodeProjectPlaces,
}),
views: projectViews,
};
return {
storage: createDashboardStorage<Dashboard>([projectsOverview]),
tables: { projects },
};
}, []);
return (
<QueryClientProvider client={queryClient}>
<NuqsAdapter>
<YayawDashboard
actions={{ dashboards: storage }}
canEdit
displayModeRenderers={renderers}
getRowId={(row) => String(row.id)}
locale={locale}
// A real application navigates to the table's page with that view.
openView={(tableId, viewId) =>
setOpened(`${tableId} › ${viewId ?? "default view"}`)
}
tables={tables}
/>
<output className="mt-2 block text-muted-foreground text-sm">
{opened}
</output>
<Toaster />
</NuqsAdapter>
</QueryClientProvider>
);
}Données de démonstration. Les modifications restent dans cet aperçu.
<script setup lang="ts">
import type { ExamplePresentation } from "./presentation";
defineProps<ExamplePresentation>();
import { ref } from "vue";
import { calendarRenderer } from "@/components/ui/yayaw-table-vue/calendar/calendar-renderer";
import { chartRenderer } from "@/components/ui/yayaw-table-vue/chart/chart-renderer";
import type { Dashboard } from "@/components/ui/yayaw-table-vue/dashboard/dashboard-model";
import type { DashboardTableSource } from "@/components/ui/yayaw-table-vue/dashboard/dashboard-types";
import YayawDashboard from "@/components/ui/yayaw-table-vue/dashboard/YayawDashboard.vue";
import { createProjectRows, geocodeProjectPlaces } from "../shared/projects";
import {
createDashboardStorage,
projectsOverview,
projectViews,
} from "../shared/projects-dashboard";
import { createMemoryActions } from "./memory-actions";
import { projectConfig } from "./project-config";
const renderers = { calendar: calendarRenderer, chart: chartRenderer };
const tables: Record<string, DashboardTableSource> = {
projects: {
name: "Projects",
config: projectConfig,
actions: createMemoryActions(createProjectRows(), {
geocode: geocodeProjectPlaces,
}),
views: projectViews,
},
};
const storage = createDashboardStorage<Dashboard>([projectsOverview]);
const opened = ref("");
// A real application navigates to the table's page with that view.
const openView = (tableId: string, viewId: string | null) => {
opened.value = `${tableId} › ${viewId ?? "default view"}`;
};
const getRowId = (row: Record<string, unknown>) => String(row.id);
</script>
<template>
<YayawDashboard
:actions="{ dashboards: storage }"
:tables="tables"
can-edit
:locale="locale"
:display-mode-renderers="renderers"
:open-view="openView"
:get-row-id="getRowId"
/>
<output class="mt-2 block text-sm text-muted-foreground">{{ opened }}</output>
</template>Ajouter un tableau de bord
Listez les tables que les widgets peuvent afficher dans tables, par identifiant de table, et stockez les tableaux de bord via actions.dashboards :
"use client";
import { useRouter } from "next/navigation";
import { calendarRenderer } from "@/components/ui/yayaw-table-calendar/calendar-renderer";
import { chartRenderer } from "@/components/ui/yayaw-table-chart/chart-renderer";
import {
type DashboardTableSource,
YayawDashboard,
} from "@/components/ui/yayaw-table-dashboard/yayaw-dashboard";
const tables: Record<string, DashboardTableSource> = {
projects: { name: "Projets", config: projectsConfig, actions: projectsActions },
tasks: { name: "Tâches", config: tasksConfig, actions: tasksActions },
};
export function ProjectsDashboard({ canEdit }: { canEdit: boolean }) {
const router = useRouter();
return (
<YayawDashboard
actions={{ dashboards: dashboardStorage }}
canEdit={canEdit}
dashboardId="projects-overview"
displayModeRenderers={{ calendar: calendarRenderer, chart: chartRenderer }}
locale="fr"
openView={(tableId, viewId) =>
router.push(`/${tableId}${viewId ? `?view=${viewId}` : ""}`)
}
tables={tables}
/>
);
}<script setup lang="ts">
import { useRouter } from "vue-router";
import { calendarRenderer } from "@/components/ui/yayaw-table-vue/calendar/calendar-renderer";
import { chartRenderer } from "@/components/ui/yayaw-table-vue/chart/chart-renderer";
import type { DashboardTableSource } from "@/components/ui/yayaw-table-vue/dashboard/dashboard-types";
import YayawDashboard from "@/components/ui/yayaw-table-vue/dashboard/YayawDashboard.vue";
defineProps<{ canEdit: boolean }>();
const router = useRouter();
const tables: Record<string, DashboardTableSource> = {
projects: { name: "Projets", config: projectsConfig, actions: projectsActions },
tasks: { name: "Tâches", config: tasksConfig, actions: tasksActions },
};
const openView = (tableId: string, viewId: string | null) =>
router.push({ path: `/${tableId}`, query: viewId ? { view: viewId } : {} });
</script>
<template>
<YayawDashboard
:actions="{ dashboards: dashboardStorage }"
:tables="tables"
:can-edit="canEdit"
dashboard-id="projects-overview"
:display-mode-renderers="{ calendar: calendarRenderer, chart: chartRenderer }"
locale="fr"
:open-view="openView"
/>
</template>Chaque entrée de tables est { config, actions, views?, name? } : la config et les actions de la table, telles que vous les passez à la table, ses vues enregistrées statiques, et le nom affiché dans les listes de choix et les titres (par défaut, le titre de la config). Les vues enregistrées viennent de views et de actions.views.list quand la table en a un, comme dans le gestionnaire de vues de la table.
Props
| Prop | Description |
|---|---|
dashboard | Un document à afficher au lieu d’en charger un : lu sur le serveur, un brouillon à prévisualiser, un écran écrit dans le code. Il est lu comme un document stocké (de n’importe quelle version, réparé) et affiché à nouveau quand il change ; un document égal garde les modifications en cours. |
actions | { dashboards: { list, load, save, remove } }, où l’hôte stocke les tableaux de bord. Voir Stocker les tableaux de bord. Facultatif quand dashboard est fourni et que personne ne le modifie : sans save, Modifier n’est pas proposé. |
tables | Record<tableId, { config, actions, views?, name?, tableProps?, renderTable? }>, les tables que les widgets peuvent afficher, fournies d’emblée. Elles l’emportent sur sources. |
sources | { list, load }, un catalogue chargé à la demande : seules les sources lues par les widgets de l’écran sont chargées. Voir Sources. |
blocks | Les blocs de votre application, par clé. Voir Blocs de l’hôte. |
dashboardId | Le tableau de bord à charger. Par défaut, le premier que renvoie list(). |
canEdit | Si l’utilisateur peut modifier le nom, les sections, les widgets, leurs vues et les filtres dans l’éditeur. false par défaut. |
showTitle | Affiche le nom du tableau de bord comme titre de l’écran (h2). true par défaut ; passez false quand la page autour affiche le titre. |
unavailableWidgets | "show" (par défaut) affiche un avis discret dans les widgets d’une source indisponible et les blocs que votre application n’a pas ; "hide" les retire de l’affichage, les grilles comblant les vides. Le mode modification montre tous les widgets, et le document les garde dans tous les cas. |
syncUrl | Garde dans l’URL les valeurs que les lecteurs choisissent pour les filtres, et laisse les tables pleine page y garder leur propre état. true par défaut ; false les garde tous deux hors de l’URL. |
openView | (tableId, viewId | null, context?) => void. Ajoute Ouvrir la vue complète aux widgets de vue et aux widgets Nombre, par exemple pour aller sur la page de la table avec cette vue ; les blocs peuvent aussi l’appeler. viewId vaut null pour des réglages intégrés et pour la vue par défaut d’une table, et context.view contient la vue intégrée d’un widget. |
renderMarkdown | (text) => node affiche le texte des notes, par exemple en markdown : un nœud React, ou un VNodeChild Vue. Les notes sont en texte brut par défaut. |
displayModeRenderers | Modes d’affichage optionnels que les widgets peuvent utiliser, comme { chart, calendar }. |
locale | "en" par défaut. Les libellés français sont utilisés quand elle commence par fr. La table de chaque widget la reçoit aussi (dates, nombres, libellés), et les textes écrits en plusieurs langues affichent celle-ci. |
translations | Libellés remplacés, avec des clés dashboard.<clé>. Voir Traductions. |
tableTranslations | Les libellés de table utilisés par chaque widget (pagination, états vides, menus), comme les translations de la table. Ils sont transmis avec locale à la table de chaque widget. React n’a pas de libellés de table français intégrés : passez ceux de votre page ; Vue prend ses libellés français intégrés d’après locale et applique ceux-ci en remplacement. |
getRowId | Identifiant de ligne stable pour les tables intégrées. |
onChange (React) / @change (Vue) | Appelé avec le tableau de bord (en version 2) après chaque changement du document, enregistré ou non. Les valeurs que les lecteurs choisissent pour les filtres ne changent pas le document. |
className (React) | Classes appliquées à la racine. |
Widgets
La fenêtre de widget (voir Modifier un écran) propose des nombres, des vues, des pages de table (sections en flux seulement), des notes et les blocs de votre application. Les trois sortes présentes dans tout tableau de bord :
Vue d’une table : une vue enregistrée de n’importe quelle table de
tables, ou la vue par défaut de la table, affichée dans son propre mode d’affichage avec ses filtres, son tri, son regroupement et ses réglages. C’est une table intégrée, sans barre d’outils, ni état d’URL, ni sélection de lignes, avec son propre état de chargement, une erreur avec Réessayer quandlistéchoue, et l’état vide de la table. Un widget dont la table ou la vue n’existe plus le signale au lieu de casser la page, et un widget en échec ne casse jamais les autres.Nombre : un chiffre sur une table ou l’une de ses vues enregistrées : un nombre de fiches, ou la somme, la moyenne, le minimum ou le maximum d’une colonne nombre, au format de nombre de la colonne. Le tableau de bord le dessine lui-même (sans moteur de rendu des graphiques) ; il suit les filtres de la vue et interroge votre serveur via
aggregatequand c’est possible. Il peut comparer des périodes et tracer une courbe de tendance ; voir Nombres.Note : du texte, affiché par
renderMarkdownou en texte brut. Les notes sont écrites par les personnes qui modifient le tableau de bord : si votrerenderMarkdownproduit du HTML, nettoyez-le comme pour les contenus de la vue Fil.
Un widget de vue ou Nombre affiche une vue enregistrée (viewId) ou des réglages intégrés (view, la configuration d’une vue enregistrée : mode d’affichage et ses réglages, filtres, tris, colonnes), sinon la vue par défaut de la table. Un document peut aussi contenir des tables pleine page (table) et des blocs de votre application (block), écrits en JSON ou par votre code : voir Écrans. Les titres peuvent être écrits en plusieurs langues ({ "en": "Revenue", "fr": "Chiffre d’affaires" }) ; le tableau de bord affiche celui de sa locale.
Widgets sans barre de défilement interne
Les widgets de vue acceptent settings.overflow :
"fit"(par défaut) : les tables, listes, galeries, tableaux Kanban et fils n’ont pas de pagination. Ils chargent assez de fiches pour remplir le widget (ou la taille de page de la vue, par exemple une vue « Top 5 ») et masquent les fiches, colonnes Kanban et colonnes de table qui ne tiennent pas, à nouveau au redimensionnement. Un pied de widget indique « +N de plus · Tout voir » et ouvre la vue complète viaopenView."scroll": la vue garde sa pagination dans un widget qui défile.
Le sélecteur de widgets propose ce choix sous « Enregistrements qui ne tiennent pas ». Les widgets graphiques remplissent leur widget : le tableau de bord active le réglage fill du graphique, qui retire son titre et sa bascule vers le tableau, et place sa légende et ses libellés là où ils tiennent.
Nombres
Les settings d’un widget Nombre peuvent ajouter une comparaison de périodes et une courbe de tendance, qui lisent toutes deux une colonne date :
{
"metric": "sum",
"metricColumn": "price",
"label": "Chiffre d’affaires",
"dateColumn": "start",
"compare": { "period": "previous", "days": 30, "better": "up" },
"sparkline": { "bucket": "month", "buckets": 6 }
}dateColumn: la colonne date lue par les périodes et les intervalles de la tendance.compareetsparklineen ont besoin.compare:{ period: "previous", days?, better? }, outrue. Le widget affiche l’évolution par rapport à la période précédente, par exemple « +12 % vs période précédente », avec un ton positif ou négatif. La période en cours est la plage de dates du tableau de bord surdateColumnquand un filtre de date la cible, sinon lesdaysderniers jours (30 par défaut ; le sélecteur propose 7, 30, 90 et 365).bettervaut"up"(par défaut) ou"down"quand une baisse est une bonne nouvelle.sparkline:{ bucket?, buckets? }, outrue. Une petite courbe de la mesure sur lesbucketsdernières périodes (bucketde"day"à"year","month"par défaut ;bucketsde 2 à 24, 6 par défaut). Les widgets très étroits réservent leur largeur au nombre ; la courbe apparaît lorsque la place le permet.
Chaque widget peut avoir son propre titre. Sinon, un widget de vue affiche le nom de la vue (ou « Projets › Vue par défaut »), un widget Nombre son libellé ou le nom de la table, et une note « Note ». Tout actualiser, dans l’en-tête, recharge chaque widget.
Un widget Nombre accepte aussi settings.valueFormat, avec le même
contrat de conversion d'affichage
que les graphiques :
{
"metric": "avg",
"metricColumn": "bitrate",
"valueFormat": { "scale": 0.000001, "unit": "Mbit/s", "decimals": 2 }
}Le nombre et les valeurs accessibles de la courbe de tendance utilisent la
conversion. L'agrégation, les filtres de période, les pourcentages de comparaison
et la géométrie de la courbe restent bruts. L'éditeur préserve le format quand
le titre ou d'autres réglages changent ; configurez la conversion dans le
document plutôt que dans l'éditeur. dashboardJsonSchema() décrit ce format
borné et validateDashboard() signale un format invalide comme erreur avant publication.
Disposition
Les sections d’un tableau de bord se suivent dans l’ordre, chacune avec un titre facultatif (un h3 ; le nom du tableau de bord est le h2). Deux sortes de sections placent les widgets :
Grille : une grille de cartes de 4 colonnes, avec des lignes de 120 px. Les widgets font de 1 à 4 colonnes de large et de 1 à 12 lignes de haut. Les nouveaux widgets prennent la première place libre de la première section en grille, avec une taille selon leur type et leur mode d’affichage : nombres en 1×1, notes en 1×2, tables, listes, graphiques et cartes en 2×2, tableaux Kanban, galeries, calendriers, fils et formulaires en 2×3, arborescences en 1×3, Gantt en 4×3. Quand un widget est déplacé ou agrandi sur d’autres, ceux-ci lui font de la place, et chaque widget remonte pour occuper l’espace libre au-dessus de lui.
Flux : des widgets empilés en pleine largeur, à la hauteur de leur contenu, dans l’ordre où la section les liste. Les vues de fiches gardent leur pagination, les graphiques prennent un corps en 16:10, et un bloc qui n’affiche rien disparaît. Les tables pleine page ne vont que dans un flux.
En mode modification, faites glisser un widget de grille par sa poignée pour le déplacer, et son coin pour le redimensionner. Le menu de chaque widget propose des alternatives au glisser-déposer, utilisables au clavier : Déplacer à gauche, Déplacer à droite, Déplacer vers le haut, Déplacer vers le bas, Plus large, Plus étroit, Plus haut, Moins haut et Retirer ; les widgets d’un flux montent et descendent. Un déplacement échange le widget avec son voisin, et les entrées qui ne peuvent pas s’appliquer sont désactivées. Les lecteurs d’écran annoncent chaque déplacement et redimensionnement.
Tant que gridstack.js n’est pas chargé (ou s’il ne se charge pas), une feuille de style place les widgets à leurs positions enregistrées : rien ne bouge. Les grilles de moins de 640 px de large, comme sur téléphone, empilent les widgets sur une colonne, en pleine largeur et dans l’ordre de lecture, sans glisser-déposer ; le menu du widget permet toujours de les déplacer. Empilés, les nombres et les notes prennent la hauteur de leur contenu, les graphiques un corps en 16:10 selon la largeur du téléphone, et les widgets de fiches gardent la hauteur de leurs lignes.
Modification et lecture seule
Avec canEdit et un endroit où enregistrer (actions.dashboards.save), l’en-tête affiche Modifier. En mode modification, le nom devient un champ, Ajouter un widget, Ajouter une section et Ajouter un filtre apparaissent, chaque section reçoit sa barre et chaque widget sa poignée et son menu. Terminé vérifie le tableau de bord, l’enregistre via actions.dashboards.save et le confirme par une notification, ou affiche l’erreur si l’enregistrement échoue. Voir Modifier un écran.
Sans canEdit, le tableau de bord est en lecture seule : ni bouton Modifier, ni poignées, ni menus de widget. Chacun peut toujours choisir les valeurs des filtres du tableau de bord, actualiser les widgets et ouvrir les vues complètes ; changer la valeur d’un filtre n’enregistre pas le tableau de bord. canEdit ne fait que masquer les contrôles : vérifiez à nouveau les droits de l’utilisateur dans save et remove sur votre serveur.
Filtres du tableau de bord
Un filtre du tableau de bord restreint chaque widget auquel il s’applique. Il en existe deux sortes :
Période : un jour de début, un jour de fin, ou les deux, choisis dans le calendrier en popover de la bibliothèque, ou une période relative : les 7, 30 ou 90 derniers jours (jusqu’à aujourd’hui), ce mois-ci, le mois dernier ou cette année. Le bouton affiche « Toutes les dates », « À partir du 1 sept. 2026 », « Jusqu’au 10 sept. 2026 », « 1 sept. 2026 – 10 sept. 2026 » ou le nom de la période.
Sélection : une ou plusieurs options, choisies dans une liste déroulante avec « Tous » et une case par option, et affichées comme les étiquettes de la table. Les options sont celles du filtre, ou celles de sa première colonne.
En mode modification, Ajouter un filtre demande le type, un nom et, pour chaque table du tableau de bord, la colonne à laquelle le filtre s’applique (une colonne date pour une période ; une colonne select, multi-select, status ou radio pour une sélection), ou Non appliqué. Sous chaque filtre, « S’applique à Projets › Échéance, Tâches › Date limite » liste ses colonnes. La définition d’un filtre et sa valeur dans le document, la valeur par défaut dont partent les lecteurs, sont enregistrées avec le tableau de bord ; le mode modification affiche et change ces valeurs par défaut.
Les valeurs qu’un lecteur choisit sont un état d’affichage, jamais écrit dans le document : chacune est gardée dans l’URL sous <dashboardId>.<filterId>, à côté des clés propres des tables, si bien qu’un lien les partage. Une valeur égale à celle par défaut quitte l’URL.
| Valeur | Dans l’URL |
|---|---|
| Une période relative | ?projects-overview.due=last30Days |
| Des jours | ?projects-overview.due=2026-09-01..2026-09-30, 2026-09-01.., ..2026-09-30 |
| Des options | ?projects-overview.category=Retail&projects-overview.category=Office |
| Effacée alors que le document a une valeur par défaut | ?projects-overview.due= |
Une période relative enregistrée comme valeur par défaut ({ "preset": "last30Days" }) devient des jours dans le fuseau horaire du lecteur quand les widgets interrogent leur source : chaque règle envoie donc toujours des jours. Un nombre qui compare des périodes compare les jours de la période avec autant de jours avant eux.
Comment les filtres atteignent votre serveur
Chaque filtre actif du tableau de bord devient une règle de filtre avancé sur la colonne du widget :
| Filtre | Règle |
|---|---|
| Période, deux jours | { type: "date", operator: "between", values: [start, end] } |
| Période, début seul | { type: "date", operator: "greaterThanOrEqual", values: [start] } |
| Période, fin seule | { type: "date", operator: "lessThanOrEqual", values: [end] } |
| Sélection | { type: "select", operator: "isAnyOf", values: [...] } |
Les règles portent aussi id: "dashboard-<filterId>", columnId et isActive: true, et les dates sont des jours calendaires (YYYY-MM-DD), ceux d’une période relative étant calculés d’abord. Le tableau de bord enveloppe les list et aggregate de chaque widget et :
ajoute les règles aux filtres avancés de la vue, combinées par ET (
advancedFilterJoin: "and") ;les envoie aussi seules dans
requiredFilters.
Une vue dont les filtres propres retiennent les fiches qui vérifient au moins une règle (OU) ne peut pas recevoir les règles du tableau de bord dans la même liste plate, car « (A ou B) et C » n’y tient pas. Pour ces vues, advancedFilters garde les règles de la vue et celles du tableau de bord n’arrivent que dans requiredFilters. Vos list et aggregate doivent donc appliquer requiredFilters par ET en plus de tout le reste. Pour les vues en ET, les règles sont aux deux endroits, et les appliquer deux fois ne change rien.
{
advancedFilters: [
{ id: "hot", columnId: "priority", type: "select", operator: "isAnyOf", values: ["High"], isActive: true },
{ id: "late", columnId: "status", type: "select", operator: "isAnyOf", values: ["Late"], isActive: true },
],
advancedFilterJoin: "or",
requiredFilters: [
{
id: "dashboard-due",
columnId: "dueDate",
type: "date",
operator: "between",
values: ["2026-09-01", "2026-09-30"],
isActive: true,
},
],
// search, sorting, page, pageSize…
}Un list construit sur votre code de filtres existant n’a besoin que d’une condition de plus :
type Rule = { columnId: string; operator: string; values?: unknown[] };
/** Une règle du tableau de bord en condition de base de données. */
function requiredCondition({ columnId, operator, values = [] }: Rule) {
switch (operator) {
case "isAnyOf":
return { [columnId]: { in: values } };
case "between":
return { [columnId]: { gte: startOfDay(values[0]), lte: endOfDay(values[1]) } };
case "greaterThanOrEqual":
return { [columnId]: { gte: startOfDay(values[0]) } };
case "lessThanOrEqual":
return { [columnId]: { lte: endOfDay(values[0]) } };
default:
throw new Error(`Unsupported dashboard filter: ${operator}`);
}
}
export async function listProjects(params: Record<string, unknown>) {
const required = Array.isArray(params.requiredFilters)
? (params.requiredFilters as Rule[])
: [];
const where = {
AND: [
buildWhere(params), // recherche, filtres et filtres avancés de la vue (ET ou OU)
...required.map(requiredCondition), // toujours en ET
],
};
const [rows, totalCount] = await Promise.all([
db.project.findMany({ where, ...buildPaging(params) }),
db.project.count({ where }),
]);
return { data: rows, meta: { totalCount } };
}Vérifiez que columnId est l’une des colonnes filtrables de la table avant de l’utiliser dans une requête, comme pour tout filtre venu du navigateur. Appliquez les mêmes conditions dans aggregate pour que les widgets Nombre et graphiques concordent avec les listes.
JSON du tableau de bord
Un tableau de bord est du JSON simple : vous pouvez le stocker dans n’importe quelle colonne de base de données ou fichier.
{
"version": 2,
"id": "projects-overview",
"name": { "en": "Projects overview", "fr": "Vue d’ensemble des projets" },
"sections": [
{
"id": "numbers",
"type": "grid",
"layout": [
{ "widgetId": "projects-count", "x": 0, "y": 0, "w": 1, "h": 1 },
{ "widgetId": "revenue", "x": 1, "y": 0, "w": 1, "h": 1 },
{ "widgetId": "welcome", "x": 2, "y": 0, "w": 2, "h": 1 },
{ "widgetId": "status-board", "x": 0, "y": 1, "w": 4, "h": 4 }
]
},
{ "id": "tasks", "type": "flow", "title": "Tâches", "widgetIds": ["open-tasks"] }
],
"widgets": [
{ "id": "projects-count", "type": "kpi", "tableId": "projects", "settings": { "metric": "count", "label": "Projets" } },
{ "id": "revenue", "type": "kpi", "tableId": "projects", "settings": { "metric": "sum", "metricColumn": "price", "label": "Chiffre d’affaires" } },
{ "id": "welcome", "type": "note", "title": { "en": "About this dashboard", "fr": "À propos" }, "settings": { "text": "Projets et tâches en un coup d’œil." } },
{ "id": "status-board", "type": "view", "tableId": "projects", "viewId": "status-board", "settings": {} },
{
"id": "open-tasks",
"type": "table",
"tableId": "tasks",
"view": { "displayMode": "table", "sorting": [{ "id": "deadline", "desc": false }] },
"settings": {}
}
],
"filters": [
{
"id": "due",
"type": "dateRange",
"label": "Échéance",
"targets": [
{ "tableId": "projects", "columnId": "dueDate" },
{ "tableId": "tasks", "columnId": "deadline" }
],
"value": { "preset": "last30Days" }
},
{ "id": "category", "type": "select", "label": "Catégorie", "targets": [{ "tableId": "projects", "columnId": "category" }] }
]
}version:2.id(200 caractères au plus),nameet unedescriptionfacultative : des textes, une chaîne pour toutes les langues ou une par langue ({ "en": "Sales", "fr": "Ventes" }), 120 caractères par titre et 500 par description.sections: dans l’ordre d’affichage, 12 au plus. Unegrida unlayoutde{ widgetId, x, y, w, h }(xde 0 à 3,wde 1 à 4,hde 1 à 12 lignes) ; unflowa deswidgetIds. Les deux acceptent untitlefacultatif.widgets: 50 au plus, placés par identifiant par les sections.id,type("view","kpi"pour un nombre,"note","table"pour une table pleine page,"block"pour un bloc de votre application), untitlefacultatif,tableId(vues, nombres et tables),viewId(une vue enregistrée) ouview(des réglages intégrés ; ils l’emportent quand les deux sont fournis),blocketprops(blocs), etsettings:{ overflow? }pour les vues,{ metric, metricColumn?, label?, dateColumn?, compare?, sparkline? }pour les nombres (metricvautcount,sum,avg,minoumax),{ text }pour les notes (20 000 caractères au plus),{}pour les tables et les blocs.filters: 12 au plus.id,type("dateRange"ou"select"),label,targets([{ tableId, columnId, widgetIds? }], oùwidgetIdslimite une cible à certains widgets de cette table), desoptionsfacultatives ([{ value, label }]) pour une sélection, etvalue, la valeur par défaut : des jours ({ start?, end? }) ou une période relative ({ preset }:last7Days,last30Days,last90Days,thisMonth,lastMonth,thisYear) pour une période, une liste de valeurs d’options pour une sélection.Les identifiants de widgets, sections et filtres sont faits de lettres, de chiffres, de
-et de_, commencent par une lettre ou un chiffre et font 64 caractères au plus ; les clés de blocs peuvent aussi contenir.et:(home.summary).updatedAtest facultatif ; le tableau de bord l’écrit à l’enregistrement.
Versions et validation
version est la version du format, actuellement 2. validateDashboard(input, { limits?, blocks? }) vérifie et répare un document de n’importe quelle version et le renvoie en version 2 : { dashboard?, issues, ok, migratedFrom? }. Elle ne lève jamais d’erreur.
La version 1 (
layoutetwidgets, avant les sections) devient une section en grillemainqui porte sa disposition. Un JSON sansversion(version 0) est une version 1 dont les éléments de disposition ont une cléi, comme dans react-grid-layout.Une
versionplus récente est refusée (unsupportedVersion) plutôt que devinée, et un type de widget ou de section que la version du document ne connaît pas est retiré : un nouveau type fait monter la version, si bien qu’un lecteur plus ancien refuse un document plus récent au lieu de perdre ses widgets. v3.7.0 et les versions antérieures refusent la version 2.Chaque problème a un
code, uneseverityet unpathJSON dans l’entrée, commewidgets[2].view.sorting[0].id. Les erreurs signifient que le document a perdu quelque chose qu’il demandait : widgets, sections, filtres et valeurs invalides, listes et textes coupés à leur limite, document ou propriétés de bloc trop volumineux, propriétés refusées par un bloc. Les avertissements sont des réparations qui gardent son sens : une disposition remise dans la grille, des widgets orphelins ou mal placés déplacés vers une section qui les accepte, des identifiants rendus valides et uniques, des clés inconnues retirées, une vue intégrée préférée à une vue enregistrée, un bloc que votre application n’a pas (avecblocks).okvaut vrai quand il y a un document et aucune erreur.Limites (
DASHBOARD_LIMITS, chacune remplaçable danslimits) : 12 sections, 50 widgets, 12 filtres, 120 caractères par titre, 500 par description, 20 000 par note, 16 384 caractères de propriétés de bloc sur 8 niveaux, 50 règles par liste d’une vue intégrée, 262 144 octets par document. Les problèmes s’arrêtent à 200.
normalizeDashboard(input) est la lecture tolérante qu’utilise le tableau de bord lui-même : le document réparé quels que soient ses problèmes, ou une erreur quand l’entrée n’est pas un tableau de bord ou vient d’une version plus récente. Les vues intégrées passent par sanitizeViewConfig (utils/view-config.ts de la table) : clés inconnues retirées, types des valeurs vérifiés, JSON hostile sans effet.
Sur votre serveur, checkDashboardReferences(dashboard, { sources, blocks? }) vérifie un document validé par rapport à ce que cet utilisateur peut voir : les sources (unknownSource, ou unavailableSource quand elle est listée mais indisponible), les vues enregistrées (unknownView), les colonnes des tris, filtres, visibilités, regroupements, nombres, réglages des modes d’affichage et cibles de filtres (unknownColumn), les modes d’affichage (unsupportedDisplayMode) et les blocs (unknownBlock). Les columns, views et displayModes du résumé d’une source ne sont vérifiés que s’ils sont fournis. dashboardJsonSchema({ sourceIds?, blocks?, limits? }) est le JSON Schema d’un document en version 2 pour les entrées des outils d’IA (MCP) : avec sourceIds, les widgets et filtres ne peuvent nommer que ces sources ; avec blocks, chaque bloc est une variante de widget qui porte son propsSchema. canonicalDashboardJson(input) et dashboardFingerprint(input) (SHA-256, identique dans le navigateur et sur le serveur) comparent des documents quels que soient leur version, l’ordre de leurs clés ou leur date d’enregistrement.
Les fonctions de construction renvoient de nouveaux documents sans jamais modifier leur entrée : createDashboard, addDashboardSection, addDashboardWidget, moveWidgetToSection, removeDashboardWidget, moveDashboardWidget, resizeDashboardWidget et applyDashboardSectionLayout.
Ces fonctions sont dans dashboard-schema.ts (components/ui/yayaw-table-dashboard/ en React, components/ui/yayaw-table-vue/dashboard/ en Vue), et le contrat des sources dans dashboard-sources.ts : aucun des deux n’importe React, Vue ou du CSS, si bien qu’un serveur peut les utiliser. dashboard-model.ts exporte toujours ce qui y a été déplacé.
Stocker les tableaux de bord
actions.dashboards a quatre fonctions :
| Fonction | Description |
|---|---|
list() | Les tableaux de bord que l’utilisateur peut voir, sous la forme [{ id, name }]. Sans dashboardId, le premier est affiché. |
load(id) | Le JSON du tableau de bord, de n’importe quelle version : il est migré en version 2 avant usage. |
save(dashboard) | Enregistre le tableau de bord, en version 2 (Terminé en mode modification). |
remove(id) | Supprime un tableau de bord. |
Par exemple, avec des server actions Next.js :
"use server";
import {
type Dashboard,
validateDashboard,
} from "@/components/ui/yayaw-table-dashboard/dashboard-schema";
export async function listDashboards() {
const user = await requireUser();
return db.dashboard.findMany({
where: { organizationId: user.organizationId },
select: { id: true, name: true },
orderBy: { name: "asc" },
});
}
export async function loadDashboard(id: string) {
const user = await requireUser();
const row = await db.dashboard.findFirstOrThrow({
where: { id, organizationId: user.organizationId },
});
return row.content; // colonne JSON
}
export async function saveDashboard(input: Dashboard) {
const user = await requireDashboardEditor(); // la même règle que canEdit
const { dashboard, issues, ok } = validateDashboard(input);
if (!(ok && dashboard)) {
const error = issues.find((issue) => issue.severity === "error");
throw new Error(error?.message ?? "Not a dashboard.");
}
const content = { ...dashboard, updatedAt: new Date().toISOString() };
await db.dashboard.upsert({
where: { id: dashboard.id },
create: { id: dashboard.id, organizationId: user.organizationId, name: dashboard.name, content },
update: { name: dashboard.name, content },
});
return content;
}
export async function removeDashboard(id: string) {
const user = await requireDashboardEditor();
await db.dashboard.deleteMany({ where: { id, organizationId: user.organizationId } });
}const dashboardStorage = {
list: listDashboards,
load: loadDashboard,
save: saveDashboard,
remove: removeDashboard,
};Gardez dashboardStorage stable (hors du composant, ou mémoïsé), car le tableau de bord se recharge quand il change. Avec une API REST, chaque fonction est un fetch vers vos routes, comme GET /api/dashboards, GET, PUT et DELETE /api/dashboards/:id. Un tableau de bord stocke des dispositions de widgets et des identifiants de vues, jamais des lignes : chaque widget charge toujours ses fiches via le list de sa table, avec les règles d’accès de cette table.
Écrans
Un écran est un tableau de bord qui compose une page de votre application : ses sections, ses tables pleine page et vos propres blocs, affichés avec dashboard à partir d’un document chargé sur le serveur (une copie stockée, ou une valeur par défaut écrite dans le code), et en général showTitle={false}.
<YayawDashboard
dashboard={screen} // ou actions={{ dashboards }} et dashboardId
sources={catalogue} // DashboardSources<DashboardTableSource>
blocks={blocks} // vos blocs, par clé
showTitle={false} // la page autour affiche le titre
unavailableWidgets={canManage ? "show" : "hide"}
openView={(tableId, viewId, context) => router.push(listPage(tableId, viewId, context?.view))}
locale="fr"
/>Sources
sources est un catalogue, chargé à la demande, des tables que les widgets peuvent lire : list() donne leurs résumés (pour les éditeurs et les outils d’IA), et load(id) donne une source, { config, actions, views?, name?, tableProps?, renderTable? }, ou { unavailable: true, reason?, message? } quand cet utilisateur ne peut pas la voir (forbidden), qu’elle n’est pas configurée (notConfigured) ou qu’elle n’existe plus (notFound). Le tableau de bord ne charge que les sources lues par ses widgets, une fois chacune, en partageant les chargements simultanés ; les tables fournies d’emblée l’emportent sur le catalogue. Un widget affiche « Chargement… », puis son contenu, ou une erreur avec Réessayer quand le chargement a échoué, ou un avis discret quand la source est indisponible : votre message, sinon un texte selon sa raison. Les widgets indisponibles ne sont jamais retirés : le mode modification les montre et Terminé les enregistre avec leurs réglages. createDashboardSourceLoader, dans dashboard-sources.ts, offre le même chargement à votre propre code.
Tables pleine page
Un widget table (dans un flux uniquement) est la page de liste de la source : la table avec sa barre d’outils, ses vues enregistrées, sa sélection, ses actions groupées et son état d’URL, sans carte. Votre code reste hors du document :
tablePropsdonne à la table les props propres à l’hôte : actions de ligne, de barre d’outils et groupées,getFormConfig, fiches, crochets de l’arborescence (Partial<DataTableProps>en React, props deYayawDataTableen camelCase en Vue).renderTable(props)enveloppe ou remplace la table : il reçoit les props que le tableau de bord donnerait àDataTable(YayawDataTableen Vue) et doit les transmettre. Le tableau de bord garde l’identifiant de la table, sa config, ses actions et ses vues de départ : la vue intégrée du widget devient une vue par défaut de la table,screen:<dashboardId>:<widgetId>, après la vue favorite du lecteur, et les filtres de l’écran atteignent chaque requête dansrequiredFilters.La première table de l’écran garde les clés d’URL propres à la table (
view,<tableId>-…), si bien que les liens vers sa page de liste continuent de fonctionner ; les autres utilisent l’identifiant de leur widget.
Blocs de l’hôte
Un widget block place du code de votre application par sa clé, avec des propriétés JSON : { "id": "storage", "type": "block", "block": "media.storage", "props": { "unit": "GB" }, "settings": {} }. Passez les blocs dans blocks, chacun étant un DashboardBlockSchema (label, description, group, placement "grid", "flow" ou "any", defaultSize, defaultProps, propsSchema, validateProps) avec son component :
import type { DashboardBlockRegistry } from "@/components/ui/yayaw-table-dashboard/dashboard-block";
const blocks: DashboardBlockRegistry = {
"media.storage": {
label: { en: "Storage", fr: "Stockage" },
placement: "flow",
defaultProps: { unit: "GB" },
propsSchema: { type: "object", properties: { unit: { enum: ["GB", "MB"] } } },
component: StorageBlock,
},
};Un bloc reçoit { widgetId, props, size?, editing, locale, revision, filters, refresh, setFilter, filterRules, openView? } : ses propriétés par-dessus ses defaultProps, sa taille dans une grille (aucune dans un flux), les valeurs des filtres de l’écran par identifiant de filtre (une période relative déjà convertie en jours), revision, qui change avec Tout actualiser et après des changements des données de l’écran, et depuis v3.9.0 setFilter et filterRules. Un bloc peut aussi fournir un composant settings, que la fenêtre de widget affiche pour modifier ses propriétés. Un bloc qui lève une erreur l’affiche dans son seul widget ; un bloc qui n’affiche rien disparaît d’un flux. Une clé que votre application n’a pas affiche « Bloc indisponible » et est conservée à l’enregistrement. Les propriétés d’un bloc sont des données : validez-les avec validateProps (elle s’exécute aussi dans validateDashboard) et ne les exécutez jamais.
Blocs qui règlent les filtres
Un bloc peut piloter l’écran. Ses props contiennent aussi setFilter(filterId, value), qui change un filtre de l’écran exactement comme la barre de filtres (la valeur du lecteur va dans l’URL ; en mode modification, elle devient la valeur par défaut du document), et filterRules(tableId, { exclude? }), les règles que les filtres de l’écran donnent à une source, à joindre à ses propres requêtes list ou aggregate en requiredFilters. setFilter vérifie d’abord la valeur et répond { ok: true, value }, ou { ok: false, code, message } sans rien changer :
| Filtre | Accepte | Refusé avec |
|---|---|---|
select | Un texte ou un nombre, ou une liste, parmi les options du filtre (sinon celles de la colonne qu’il cible) | invalidValue |
dateRange | Des jours { start?, end? } (YYYY-MM-DD, dans l’ordre), ou un { preset } connu | invalidValue |
| tous | undefined, null, [] ou {} : efface le filtre | |
| un filtre que l’écran n’a pas | rien | unknownFilter |
La bibliothèque fournit un tel bloc, la liste de facettes : les valeurs d’une colonne avec leur nombre d’enregistrements sous les autres filtres de l’écran, dont les clics règlent un filtre select (« Toutes » l’efface). Les comptages viennent de l’aggregate de la source, sinon des lignes que renvoie son list (2 000 au plus). Déclarez-la avec createFacetBlock :
// React : "@/components/ui/yayaw-table-dashboard/dashboard-facet-block"
// Vue : "@/components/ui/yayaw-table-vue/dashboard/dashboard-facet-block"
import { createFacetBlock } from "@/components/ui/yayaw-table-dashboard/dashboard-facet-block";
const blocks: DashboardBlockRegistry = {
"pages.sections": createFacetBlock({
filterId: "section", // un filtre select de l’écran
tableId: "pages", // la source qu’il compte
column: { id: "section", header: "Section", type: "select", options },
actions: pageActions, // le list et l’aggregate de la source
label: { en: "Sections", fr: "Rubriques" },
layout: "chips", // ou "list"
}),
};Les props du widget sont { filterId?, layout?: "chips" | "list", showCounts? }, vérifiées par le validateProps du bloc.
Avis et actualisations
Une réponse de list ou aggregate peut porter meta.notice (TableNotice : { code?, message? } ou un texte) quand la source n’a rien à montrer pour une raison précise, comme { code: "notConfigured", message: "Connectez un outil d’analyse d’audience." }. Les nombres, les vues et les tables pleine page affichent alors un avis discret au lieu de données vides : son message, sinon le texte d’un code connu (forbidden, notConfigured, notFound, error).
Tout actualiser recharge chaque widget : nombres, vues, blocs (leur revision) et tables pleine page, et relance le chargement des sources qui ont échoué. Un changement fait dans une table pleine page (création, modification, suppression, duplication, actions groupées, import, déplacement ou nouveau dossier dans l’arborescence) recharge les autres widgets de cette source et les blocs, une fois pour une série de changements.
Modifier un écran
Avec canEdit et actions.dashboards.save, Modifier transforme l’écran en son éditeur, le même en React et en Vue. Votre application décide qui peut modifier et stocke ce qu’enregistre Terminé ; un outil d’IA peut préparer des documents avec la même grammaire (dashboardJsonSchema) pour qu’une personne les publie. L’éditeur est un morceau à part, chargé au passage en mode modification, si bien que les lecteurs ne le téléchargent jamais.
Sections
Ajouter une section ajoute une section Grille de cartes ou Pleine largeur à la fin, jusqu’à 12. En mode modification, chaque section a une barre avec son titre (le texte de la langue courante ; un titre vide le retire) et un menu : Déplacer vers le haut, Déplacer vers le bas, Ajouter un widget ici et Retirer, qui demande confirmation quand la section contient encore des widgets. Les sections vides n’apparaissent qu’en mode modification. Un widget passe dans une autre section avec Déplacer vers la section dans son menu, qui liste les sections qui l’acceptent (une page de table va seulement dans un flux, un bloc là où son placement le permet) : il prend la première place libre d’une grille, ou la dernière place d’un flux. Dans une section, les cartes d’une grille se glissent, se déplacent et se redimensionnent toujours, et les widgets d’un flux montent et descendent.
La fenêtre de widget
Ajouter un widget (l’en-tête), Ajouter un widget ici (une section) et Modifier… (le menu d’un widget) ouvrent une seule fenêtre en trois étapes :
Quoi : un nombre, une vue, une page de table (flux seulement), une note, puis ceux de vos blocs que la section accepte, sous leur
group, avec leurdescription.Source (nombres, vues et pages de table) : votre catalogue, demandé une fois avec
sources.list(), avec une recherche par nom, identifiant, description, groupe et mots-clés, regroupé, les sources que cet utilisateur ne peut pas utiliser étant listées désactivées avec leur raison. En choisir une la charge (sources.load, une fois) ; si elle se révèle indisponible, la fenêtre dit pourquoi.Réglages : le titre en premier, puis la source et sa vue Partir de (par défaut, enregistrée ou personnalisée ; Modifier la vue… ouvre l’éditeur de vue). Les nombres regroupent la mesure et la colonne sous Calcul, puis la colonne de date, la comparaison et la courbe sous Période et comparaison. Les vues exposent leur débordement, les notes leur texte et les blocs leurs propriétés.
L’en-tête et les actions de la fenêtre restent visibles pendant le défilement des champs. Sur petit écran, les champs de comparaison et les actions du pied de fenêtre s’empilent. Les titres des widgets restent subordonnés aux titres de section ; un titre de widget long peut occuper deux lignes. Cette présentation conserve les documents et réglages enregistrés.
Modifier un widget ouvre l’étape des réglages, et Appliquer garde son identifiant et sa place. Un bloc qui a un composant settings l’affiche : il reçoit { widgetId, props, locale, onChange }, avec props par-dessus les defaultProps du bloc. Sinon, les propriétés sont du JSON dans une zone de texte. Dans les deux cas, elles sont vérifiées avant Ajouter ou Appliquer (checkDashboardBlockProps, puis le validateProps du bloc) : un JSON invalide et les erreurs sont refusés et listés avec leur chemin.
L’éditeur de vue
Modifier la vue… (le menu d’un widget de vue, de nombre ou de page de table, ou une vue personnalisée dans la fenêtre) ouvre une fenêtre presque plein écran dont l’éditeur est la table réelle de la source : sa barre d’outils, sa recherche, ses filtres, son tri, ses colonnes, ses modes d’affichage et les réglages de chaque mode, sans synchronisation d’URL, vues enregistrées, sélection de lignes ni modification des enregistrements. Appliquer enregistre la vue qu’affiche la table dans la view intégrée du widget, sans la taille de page sauf si vous l’avez changée. Fermer avec des changements demande d’abord confirmation.
La table rapporte pour cela sa vue : le DataTable de React accepte onViewConfigChange(config), le YayawDataTable de Vue émet view-config-change et expose getViewConfig(), et les actions de barre d’outils reçoivent getViewConfig() dans leur contexte. La config est canonicalViewConfig(view), les réglages de la vue nettoyés comme ceux des vues enregistrées, sans les colonnes select et actions et avec ses clés dans un ordre unique, rapportée au démarrage de la table puis après chaque changement.
Vues : copies et vue par défaut de l’écran
Utiliser une copie de cette vue (un widget qui nomme une vue enregistrée) : les réglages de la vue enregistrée deviennent la vue intégrée du widget, si bien que les changements ultérieurs de la vue enregistrée n’atteignent plus l’écran.
Faire de la vue actuelle la vue par défaut de l’écran (pages de table) : la vue qu’affiche la page de table devient la vue intégrée du widget, la vue par défaut propre à l’écran (
screen:<dashboardId>:<widgetId>).
Enregistrer
Terminé vérifie le document avec validateDashboard(document, { blocks }). S’il y a des erreurs, rien n’est enregistré : l’écran reste en mode modification et les liste au-dessus des sections, chacune nommée d’après son widget, sa section ou son filtre. Sinon, le document version 2 est enregistré avec updatedAt. Votre serveur doit le vérifier à nouveau (validateDashboard, checkDashboardReferences et vos propres règles) avant de le stocker.
Plusieurs tables sur une page
Chaque widget de vue ou Nombre est une instance de table à part entière : synchronisation d’URL désactivée, état propre, et vue enregistrée appliquée avant sa première requête. Il utilise les props de table instanceId et initialView, que vous pouvez aussi utiliser vous-même pour placer plusieurs tables sur une page. Voir Plusieurs tables sur une page. Les tables pleine page gardent leur état d’URL, la première sous les clés propres à la table.
Traductions
Le tableau de bord a des libellés anglais et français intégrés ; le français est utilisé quand locale commence par fr. Remplacez-en n’importe lequel avec des clés dashboard.<clé> dans la prop translations, en React comme en Vue, par exemple "dashboard.refresh": "Recharger".
En-tête et états :
dashboard,edit,done,saving,saved,saveError,loadError(avec{error}),loading,notFound,refresh,empty,emptyEditable.Ajout de widget :
addWidget,addWidgetTitle,widgetType,typeView,typeKpi,typeNote,typeTable,typeBlock,table,view,defaultView,metric,metricCount,metricSum,metricAvg,metricMin,metricMax,metricColumn,widgetTitle,noteText,add,cancel.Widgets :
widgetMenu,dragHandle(avec{title}),moveLeft,moveRight,moveUp,moveDown,wider,narrower,taller,shorter,remove,openFullView,widgetLoading,widgetError(avec{error}),retry,missingTable,missingView,emptyNote,kpiCount,moved,resized(avec{title}),moreCount(avec{count}),viewAll,overflow,overflowFit,overflowScroll,screenDefaultView,unknownBlock.Sources et avis :
unavailableForbidden,unavailableNotConfigured,unavailableNotFound,unavailableError,noticeDefault.Nombres :
dateColumn,noDateColumn,compare,compareDays,lastDays(avec{count}),compareBetter,compareUp,compareDown,sparkline,compareChange(avec{change}),compareNoPrevious,comparePeriods(avec{current}et{previous}),trendTitle(avec{values}).Filtres :
filters,from,to,any,clear,appliesTo(avec{targets}),addFilter,addFilterTitle,filterType,filterDateRange,filterSelect,filterName,filterColumn(avec{table}),notApplied,removeFilter(avec{name}),noFilterColumns,anyDate,fromDate,untilDate(avec{date}),dateRange(avec{start}et{end}),presets,presetLast7Days,presetLast30Days,presetLast90Days,presetThisMonth,presetLastMonth,presetThisYear.Sections (v3.9.0) :
addSection,sectionGrid,sectionFlow,sectionTitle,sectionNumber,sectionMenu,addWidgetHere,emptySection,removeSectionTitle,removeSectionOne,removeSectionMany,sectionRemoved,moveToSection,movedToSection.La fenêtre de widget et l’éditeur de vue (v3.9.0) :
editWidget,editWidgetTitle,editView,useViewCopy,viewCopied,makeScreenDefault,screenDefaultSet,stepWhat,stepSource,stepSettings,stepOf,chooseKind,chooseSource,kindKpi,kindKpiHint,kindView,kindViewHint,kindTable,kindTableHint,kindNote,kindNoteHint,blocks,searchSources,noSources,loadingSources,sourcesError,sourceLoading,back,apply,startFrom,savedViews,customView,customViewHint,blockProps,invalidJson,propsRefused,viewEditorTitle,viewEditorDescription,unsavedChanges,close,discardTitle,discardDescription,keepEditing,discard,applyAndClose,saveIssues,dismiss.Filtres réglés par des blocs et liste de facettes (v3.9.0) :
unknownFilter,invalidDateRange,invalidSelect,unknownOption,facetBlock,facetBlockDescription,facetAll,facetClear,facetLoading,facetEmpty,facetError.
Changements visibles
La version du tableau de bord aligne aussi quelques détails des tables React et Vue, visibles en dehors des tableaux de bord :
Booléens : ils s’affichent de la même façon dans chaque cellule (table, cartes de liste, de kanban et de galerie) : une case cochée pour vrai et une case vide pour faux, nommées « Vrai » ou « Faux » pour les lecteurs d’écran (
common.true,common.false). React affichait des badges « Vrai »/« Faux » verts ou rouges, et Vue un ✓ ou « — ».Cartes Kanban : les cartes compactes (sans libellés de propriétés) omettent les propriétés sans rien à afficher (vide, texte blanc, liste vide ;
falseet0restent affichés). Les cartes Kanban Vue affichent désormais par défaut les colonnes visibles, comme React, au lieu de toutes les colonnes.Pagination des cartes (liste, galerie et kanban) : elle apparaît dès qu’il y a plus d’une page, selon le nombre de pages du serveur ou selon le nombre de lignes, dans les deux éditions. Vue ne tenait compte que du nombre de pages du serveur.
Graphiques Nombre en Vue : ils affichent désormais le chiffre et ce qu’il compte, comme en React.
Vue Carte
Afficher les fiches d’une colonne de lieu sur une carte, avec regroupements, bulles, liste des fiches visibles et recherche par zone côté serveur.
Vue Arborescence
Afficher les fiches comme des dossiers et des fichiers, avec chargement serveur à la demande, glisser-déposer, Déplacer vers…, annulation et volet de détails.