Vues enregistrées
Enregistrer des dispositions, partager des vues et choisir un favori personnel.
Une vue enregistrée est un instantané sérialisable de la disposition du tableau. Elle reste distincte du catalogue de configuration et des données affichées.
Droits propres à chaque vue
TableView.canEdit et TableView.canDelete permettent à l’application de décrire séparément les droits de l’utilisateur courant. La valeur false désactive l’action correspondante dans React et Vue. Si les propriétés sont absentes, le comportement existant des vues non système est conservé. Les vues système et allowViewSave: false restent en lecture seule.
Une vue partagée reste sélectionnable, copiable et utilisable comme favori personnel sans droit de modification. Renvoyez ces propriétés depuis les actions de liste, de création et de modification selon le propriétaire et l’espace courant. L’adaptateur local conserve et respecte les propriétés explicites ; les actions distantes doivent contrôler elles-mêmes les autorisations et les modifications concurrentes. Les propriétés d’interface ne constituent pas une protection côté serveur.
Stockez le favori dans une préférence individuelle, séparée de la définition partagée. Choisir un favori ne doit ni renommer ni remplacer la vue d’un collègue, ni la définir par défaut pour toute l’organisation.
Contrôles d’affichage cohérents
Les choix de densité utilisent une graisse normale, y compris le choix sélectionné, la sélection étant indiquée par son fond et son état accessible. Le contrôle Mode d’affichage est une liste déroulante étiquetée (Select Base UI en React, TableSelect en Vue) ; les barres d’outils compactes et les tiroirs tactiles conservent les boutons. Les réglages de galerie, Kanban et Gantt utilisent des champs compacts et un seul tiroir sur mobile, y compris pour les choix imbriqués. Sur les layouts tactiles, la mise en page et la densité sont des lignes affichant leur valeur courante qui ouvrent le même écran de choix que les propriétés, le filtre, le tri et le groupement. Partager se trouve dans le menu Données, à côté d’Exporter et de Connecter, avec la même hauteur de 32 px sur desktop et une cible tactile d’au moins 44 px sur les layouts compacts.
Exemple de configuration
Partez du démarrage React ou du démarrage Vue. Le fichier suivant complète leur catalogue de produits. Passez cette configuration via getTableConfig en React ou config en Vue.
import type {
TableView,
TableViewActions,
} from "@/components/ui/yayaw-table/types/view-types";
/** Demo persistence lasts for this module's lifetime. Scope server storage by user and organization. */
const views = new Map<string, TableView>();
const favorites = new Map<string, string | null>();
export const viewActions: TableViewActions = {
list: ({ tableId }) =>
Promise.resolve({
data: [...views.values()].filter((view) => view.tableId === tableId),
}),
create: (input) => {
const view = {
...input,
id: crypto.randomUUID(),
createdById: "demo-user",
};
views.set(view.id, structuredClone(view));
return Promise.resolve({ success: true, data: view });
},
update: (id, input) => {
const current = views.get(id);
if (!current || (input.tableId && input.tableId !== current.tableId)) {
return Promise.resolve({ success: false, error: "View not found." });
}
const view = { ...current, ...input };
views.set(id, structuredClone(view));
return Promise.resolve({ success: true, data: view });
},
delete: (id, { tableId }) => {
if (views.get(id)?.tableId !== tableId) {
return Promise.resolve({ success: false, error: "View not found." });
}
views.delete(id);
if (favorites.get(tableId) === id) {
favorites.delete(tableId);
}
return Promise.resolve({ success: true, data: { id } });
},
getFavorite: ({ tableId }) =>
Promise.resolve({
success: true,
data: { viewId: favorites.get(tableId) ?? null },
}),
setFavorite: (viewId, { tableId }) => {
if (viewId && views.get(viewId)?.tableId !== tableId) {
return Promise.resolve({ success: false, error: "View not found." });
}
favorites.set(tableId, viewId);
return Promise.resolve({ success: true, data: { viewId } });
},
};import type {
TableView,
TableViewActions,
} from "@/components/ui/yayaw-table-vue/types";
/** Demo persistence lasts for this module's lifetime. Scope server storage by user and organization. */
const views = new Map<string, TableView>();
const favorites = new Map<string, string | null>();
export const viewActions: TableViewActions = {
list: ({ tableId }) =>
Promise.resolve({
data: [...views.values()].filter((view) => view.tableId === tableId),
}),
create: (input) => {
const view = {
...input,
id: crypto.randomUUID(),
createdById: "demo-user",
};
views.set(view.id, structuredClone(view));
return Promise.resolve({ success: true, data: view });
},
update: (id, input) => {
const current = views.get(id);
if (!current || (input.tableId && input.tableId !== current.tableId)) {
return Promise.resolve({ success: false, error: "View not found." });
}
const view = { ...current, ...input };
views.set(id, structuredClone(view));
return Promise.resolve({ success: true, data: view });
},
delete: (id, { tableId }) => {
if (views.get(id)?.tableId !== tableId) {
return Promise.resolve({ success: false, error: "View not found." });
}
views.delete(id);
if (favorites.get(tableId) === id) {
favorites.delete(tableId);
}
return Promise.resolve({ success: true });
},
getFavorite: ({ tableId }) =>
Promise.resolve({
success: true,
data: { viewId: favorites.get(tableId) ?? null },
}),
setFavorite: (viewId, { tableId }) => {
if (viewId && views.get(viewId)?.tableId !== tableId) {
return Promise.resolve({ success: false, error: "View not found." });
}
favorites.set(tableId, viewId);
return Promise.resolve({ success: true, data: { viewId } });
},
};Brancher les actions
Ajoutez viewActions dans getTableActions(tableType).views. Activez enableViews et allowViewSave ; activez allowViewSharing seulement si l’application gère un partage par organisation. L’exemple stocke les vues en mémoire et les perd au rechargement. Utilisez une base pour des vues persistantes. La recette fiche branche le parcours complet.
Contenu d’une vue
Une vue (TableViewConfig) peut restaurer la recherche (globalSearch), les filtres simples et avancés, le tri, le groupement, la visibilité, l’ordre, les dimensions et l’épinglage des colonnes, la densité, l’affichage des calculs de pied de table, la taille de page, le mode d’affichage et les réglages de chaque mode : kanban, gallery, list, filetree, calendar, chart, feed, map, form et gantt. Stockez des valeurs et identifiants de colonnes stables. Ne sérialisez pas les callbacks, renderers, autorisations ou clients de requête.
Les règles de date sont enregistrées et partagées comme des jours du calendrier (AAAA-MM-JJ). Les vues enregistrées par les versions antérieures à v3.8.0 gardent leurs instants jusqu’à leur prochain enregistrement, et s’ouvrent toujours sur les mêmes jours : la table lit ces instants comme les jours du lecteur, sans marquer la vue comme modifiée.
Favoris et partage
isGlobal décrit une vue partagée dans le périmètre d’organisation de l’application. getFavorite et setFavorite gèrent une vue d’arrivée personnelle sans modifier la vue partagée. Effacez un favori dont la vue a été supprimée ou devient inaccessible. L’URL peut représenter une disposition temporaire ; consultez l’état dans l’URL.
Modifier ou réinitialiser une vue
Ouvrez le menu de la vue pour choisir une vue ou un favori, modifier ses réglages,
enregistrer, réinitialiser ou supprimer. Le mode et les six densités du tableau
sont accessibles directement. Filtres, tri, groupement et propriétés ont leurs
écrans avec retour. Désactiver enableViews conserve un bouton Vue pour les
réglages.
La pastille bleue indique uniquement une différence avec la vue enregistrée active. Une vue enregistrée filtrée s'ouvre sans pastille. Densité, mode, recherche, filtres, tri, groupement, colonnes, cartes, taille de page et visibilité des calculs participent à la même comparaison. La sélection et la page courante sont exclues. Revenir manuellement à la configuration enregistrée fait disparaître la pastille.
Enregistrer les modifications reste visible mais grisé sans changement. Une infobulle l'explique au survol et au focus sur desktop ; le texte apparaît sous l'action sur mobile. Enregistrer comme nouvelle vue crée une autre vue. Une vue temporaire propose Enregistrer cette vue…, sans pastille. Les erreurs de sauvegarde conservent les modifications ; les favoris restent disponibles sans droit d'enregistrement.
Réinitialiser la vue, avec Lucide ListRestart, restaure la version enregistrée
ou la configuration initiale de l'application pour une vue temporaire. Cette action
ne modifie aucune donnée et ne supprime pas la vue. showClearFilters et son alias
historique showResetFilters gardent leur portée limitée aux filtres, dans l'écran
Filtres.
Le snapshot partagé inclut maintenant footerCalculationsVisible?: boolean. Les
anciennes vues sans cette propriété héritent de la visibilité initiale ; le flag
enableCalculations continue de contrôler la disponibilité de la fonction.
Conservez les réglages des modes inactifs lors de la persistance des snapshots.
Par exemple, ouvrez une vue enregistrée sans groupement, filtrée sur Open, groupez-la par statut et changez sa densité. Réinitialiser la vue retire ce groupe, restaure la densité enregistrée (ou celle de la configuration pour une ancienne vue), conserve le filtre Open et retire la pastille bleue. Une colonne kanban mémorisée pour un mode inactif n'ajoute pas de groupement au tableau. Réinitialiser une vue temporaire retire également les groupes ajoutés.
Onglets de vues
La barre d'outils sépare le sélecteur de vues, à gauche, des réglages de la vue, à droite. Les vues enregistrées s'affichent sous forme d'onglets dans le sélecteur sur les grands écrans, dès que la table compte au moins une vue enregistrée. La vue par défaut est toujours le premier onglet ; chaque onglet affiche l'icône de son mode d'affichage et un point quand la vue active a des changements non enregistrés. Cliquer sur un onglet applique la vue.
Quand il y a plus de vues que table.viewTabs.maxVisible (4 par défaut), le
reste passe sous …, un bouton à icône nommé « Plus de vues » (avec la même
infobulle) dont le menu affiche leurs noms complets ; la vue active reste
toujours visible, à la place du dernier onglet affiché. + (« Nouvelle vue ») ouvre le formulaire de
sauvegarde et ne s'affiche que si des vues peuvent être créées (allowViewSave
et une action de création). Le formulaire de sauvegarde propose un choix
Mise en page — les modes d'affichage que la table propose, celui en cours
par défaut ; créer une vue dans une autre mise en page bascule vers elle.
Avec les onglets, un chevron Actions de la vue à côté d'eux regroupe
enregistrer les changements, enregistrer comme nouvelle vue, favori,
Déplacer à gauche et Déplacer à droite (voir Ordre des
vues), réinitialiser et supprimer. Les barres d'outils compactes et les layouts
tactiles remplacent les onglets par un déclencheur à icône seule — sans
libellé ni chevron, pour laisser la place à la recherche sur la ligne — avec
un nom accessible construit à partir de views.current (« Current view » par
défaut) suivi du nom de la vue, et un petit point pour les changements non
enregistrés. Son menu liste les vues — défilant, avec un filtre Chercher une
vue au-delà de sept — suivies des mêmes actions. Mettez table.viewTabs:
false pour garder le déclencheur nommé (icône, nom de la vue et chevron)
même sur les grands écrans.
Ordre des vues
Chaque personne ordonne ses propres vues enregistrées. Le menu de la vue
déplace la vue enregistrée courante d'un cran : Déplacer à gauche et
Déplacer à droite à côté des onglets, Déplacer vers le haut et
Déplacer vers le bas là où le menu liste les vues (téléphones et
viewTabs: false), juste après le favori. À chaque extrémité, l'action reste
focalisable mais inactive (aria-disabled), pour que le focus y reste, et
chaque déplacement est annoncé aux lecteurs d'écran, par exemple « Vue
« Ventes » déplacée en position 3 sur 6 » (la position compte d'abord la vue
par défaut intégrée). L'ordre s'applique aux onglets, à la liste … et à
la liste des vues du menu ; la vue sur laquelle une table s'ouvre n'en dépend
pas.
Les vues système (
isSystem) et la vue par défaut (isDefault, comme la vue propre d'un écran de tableau de bord) restent en tête, dans l'ordre de la liste, sans actions de déplacement.Les vues que l'ordre ne nomme pas, comme les nouvelles, viennent en dernier dans l'ordre de la liste. Les identifiants inconnus sont ignorés, et avec moins de deux vues à ordonner, il n'y a rien à déplacer.
Conservez l'ordre sur votre serveur avec l'action facultative
views.setOrder. Elle reçoit le nouvel ordre complet de la personne, du
premier au dernier, sans les vues système ni la vue par défaut ; stockez-le
tel quel, par utilisateur, organisation, type de table et identifiant de
table, et renvoyez-le depuis list dans order. La table trie les vues avec,
selon les règles ci-dessus, donc votre serveur n'a rien à trier (un serveur
peut aussi lister les vues déjà dans cet ordre et omettre order).
orderViews(views, order), dans utils/view-order.ts, trie de la même façon
quand votre serveur en a besoin. Une écriture refusée
({ success: false, error }) garde l'ordre précédent et affiche l'erreur dans
le gestionnaire de vues.
// À côté de list, create, update, delete, getFavorite et setFavorite.
const orders = new Map<string, string[]>(); // par utilisateur, organisation, type de table et table
export const viewActions: TableViewActions = {
list: async ({ tableId, tableType }) => ({
data: await listViews(tableId),
order: orders.get(orderKey(tableType, tableId)),
}),
setOrder: ({ tableId, tableType, viewIds }) => {
orders.set(orderKey(tableType, tableId), viewIds);
return Promise.resolve({ success: true, data: { viewIds } });
},
};L'action et la réponse de list (TableViewListResult, { data, order? })
sont les mêmes en React et en Vue ; le list de Vue peut toujours répondre un
simple tableau. Sans setOrder, l'ordre reste dans le localStorage du
navigateur, sous yayaw-table-view-order:<JSON [tableType, tableId]>, la
portée du favori, partagée par tous les gestionnaires de cette table sur la
page. createLocalTableViewActions() n'a pas de setOrder (son type est
LocalTableViewActions), donc les tables qui l'utilisent gardent ce repli.
Partager le lien courant
Partager se trouve dans le menu Données, ouvert depuis le bouton à icône
de base de données à côté des Réglages de la vue, aux côtés d'Exporter et de
Connecter. Sans destination de partage personnalisée, l'action copie
directement l'URL courante exacte sur desktop (sur mobile, elle utilise le
partage natif quand il est disponible, avec copie en repli ; une annulation
reste silencieuse). Cette action ne donne aucun droit et ne modifie pas
isGlobal. Le partage organisationnel reste une option distincte du
formulaire de sauvegarde. Mettez table.share: false pour masquer la ligne
Partager.
Les destinations de connexion et de partage personnalisées déclarées
dans actions.destinations (webhooks, n8n, connecteurs) ont chacune leur
propre ligne dans le même menu Données : les destinations de
connexion sous Connecter › (masquée s'il n'y en a aucune), les
destinations de partage sous Partager ›, après le « Copier le lien »
intégré — avec des destinations de partage déclarées, Partager ouvre cet
écran au lieu de copier directement. Voir Destinations
sur mesure.
Activer les vues enregistrées
Branchez l’adaptateur viewActions présenté plus haut dans la propriété views des actions. Enregistrez une vue filtrée, rouvrez-la, changez sa densité et réinitialisez : le filtre enregistré reste et la pastille disparaît.
Copiez cette configuration complète à côté de product-config.ts. Utilisez () => exampleConfig pour getTableConfig en React, ou :config="exampleConfig" en Vue.
Données de démonstration. Les modifications restent dans cet aperçu.
import { defineTableConfig } from "@/components/ui/yayaw-table/config/helpers";
import { productConfig } from "./product-config";
export const exampleConfig = defineTableConfig({
...productConfig,
table: {
...productConfig.table,
enableViews: true,
allowViewSave: true,
syncUrl: true,
},
});Données de démonstration. Les modifications restent dans cet aperçu.
import { defineTableConfig } from "@/components/ui/yayaw-table-vue/config";
import { productConfig } from "./product-config";
export const exampleConfig = defineTableConfig({
...productConfig,
table: {
...productConfig.table,
enableViews: true,
allowViewSave: true,
syncUrl: true,
},
});Conserver les réglages sans vues enregistrées
Utilisez le bouton Vue pour les filtres, le tri, les propriétés et la densité sans catalogue de vues enregistrées. Réinitialiser restaure la configuration initiale de l’application.
Copiez cette configuration complète à côté de product-config.ts. Utilisez () => exampleConfig pour getTableConfig en React, ou :config="exampleConfig" en Vue.
Données de démonstration. Les modifications restent dans cet aperçu.
import { defineTableConfig } from "@/components/ui/yayaw-table/config/helpers";
import { productConfig } from "./product-config";
export const exampleConfig = defineTableConfig({
...productConfig,
table: {
...productConfig.table,
enableViews: false,
enableAdvancedFilters: true,
enableSorting: true,
density: "medium",
},
});Données de démonstration. Les modifications restent dans cet aperçu.
import { defineTableConfig } from "@/components/ui/yayaw-table-vue/config";
import { productConfig } from "./product-config";
export const exampleConfig = defineTableConfig({
...productConfig,
table: {
...productConfig.table,
enableViews: false,
enableAdvancedFilters: true,
enableSorting: true,
density: "medium",
},
});