Colonnes de lieu
Stocker des lieux avec des coordonnées, un nom et une adresse, les modifier avec les suggestions d’adresses de votre géocodeur, et filtrer par distance ou par zone.
Une colonne location contient un lieu : des coordonnées avec un nom et une adresse optionnels. Les cellules affichent le lieu, l’éditeur recherche les adresses via votre géocodeur, les filtres trouvent les fiches à une certaine distance ou dans une zone, et la vue Carte place les fiches sur une carte. React et Vue se comportent de la même façon.
Valeurs
Un lieu est stocké sous forme d’objet :
type LocationValue = {
lat: number; // latitude en degrés (WGS 84), de -90 à 90
lng: number; // longitude en degrés, de -180 à 180
label?: string; // nom court, par ex. "Siège"
address?: string; // adresse postale
};La table lit aussi les clés latitude/longitude et lon, les points GeoJSON ({ type: "Point", coordinates: [lng, lat] }) et le texte « lat, lng » comme "48.8566, 2.3522", pour afficher des données existantes sans migration. L’éditeur enregistre toujours l’objet ci-dessus. Une valeur sans coordonnées valides n’est pas un lieu : une valeur stockée illisible échoue au contrôle de type avec « Expected a location (lat, lng) ».
Déclarer une colonne
const columns = [
{ id: "name", header: "Nom", type: "text" },
{ id: "site", header: "Site", type: "location", inlineEdit: true },
];Le type de la colonne définit la cellule, l’éditeur inline, le champ du formulaire généré et les contrôles de filtre, comme pour les autres types.
Essayer une colonne de lieu
Site est une colonne location modifiable en ligne. Double-cliquez sur un site, tapez au moins trois lettres comme « lyon » ou « port », puis choisissez une suggestion de l’action geocode de l’exemple, qui cherche dans une courte liste de lieux là où un vrai hôte appelle son géocodeur. Taper « 48.85, 2.35 » remplit directement les coordonnées. Les filtres avancés proposent les règles de lieu décrites plus bas. L’aperçu utilise les projets d’ouverture de magasins communs à tous les guides de vues ; voir les données des exemples pour project-config.ts et l’hôte en mémoire.
Données de démonstration. Les modifications restent dans cet aperçu.
import { defineTableConfig } from "@/components/ui/yayaw-table";
import { projectConfig } from "./project-config";
// The `site` column is `{ type: "location", inlineEdit: true }`; the host
// declares `actions.geocode` for address suggestions.
export const exampleConfig = defineTableConfig({
...projectConfig,
columns: {
...projectConfig.columns,
visible: ["name", "site", "status"],
},
table: {
...projectConfig.table,
displayModes: ["table"],
allowInlineEdit: true,
inlineEdit: { enabled: true },
enableAdvancedFilters: true,
},
});Données de démonstration. Les modifications restent dans cet aperçu.
import { defineTableConfig } from "@/components/ui/yayaw-table-vue";
import { projectConfig } from "./project-config";
// The `site` column is `{ type: "location", inlineEdit: true }`; the host
// declares `actions.geocode` for address suggestions.
export const exampleConfig = defineTableConfig({
...projectConfig,
columns: {
...projectConfig.columns,
visible: ["name", "site", "status"],
},
table: {
...projectConfig.table,
displayModes: ["table"],
allowInlineEdit: true,
inlineEdit: { enabled: true },
enableAdvancedFilters: true,
},
});Cellules et détails de la fiche
Une cellule affiche une épingle et le nom, sinon l’adresse, sinon les coordonnées (cinq décimales au plus, environ un mètre). Son survol montre les coordonnées. La vue enregistrement affiche « nom · adresse » quand les deux sont renseignés. Les exports mis en forme écrivent le même texte que la cellule.
Modification
L’éditeur comporte un champ d’adresse, un nom, la latitude et la longitude, et Effacer :
Avec
actions.geocode, saisir au moins trois caractères dans le champ d’adresse affiche jusqu’à huit suggestions, 300 ms après la dernière touche ; les flèches les parcourent et Entrée en choisit une, qui remplit l’adresse, le nom et les coordonnées. Sansgeocode, le champ est une simple adresse et les coordonnées se saisissent à la main.Saisir « lat, lng » dans le champ d’adresse, par exemple
48.8566, 2.3522, remplit les coordonnées.En inline, l’éditeur flotte au-dessus de la table sous sa cellule, ajoute Annuler et Terminé, et enregistre quand le focus le quitte, comme les autres éditeurs inline. Échap annule.
Les formulaires générés de création et d’édition utilisent le même éditeur. Dans un catalogue de formulaires,
createLocationField({ name, label })construit le champ (React).Des coordonnées incomplètes ou hors limites affichent une erreur de validation et empêchent l’enregistrement. Rendre un lieu invalide ne réenregistre jamais silencieusement son ancienne valeur. Corrigez les coordonnées ou utilisez Effacer ; un lieu facultatif peut rester vide, tandis qu’un lieu obligatoire doit être renseigné.
Dans la vue Formulaire, une question de lieu utilise le même éditeur. Le formulaire garde le lieu en texte JSON dans son brouillon : les conditions sur une question de lieu doivent donc utiliser « est vide » ou « n’est pas vide », et les valeurs fixes masquées ne peuvent pas contenir de lieu.
Suggestions d’adresses
Déclarez geocode à côté de list et des autres actions. Elle reçoit le texte saisi et { locale, signal }, et renvoie des lieux, le meilleur en premier :
geocode: (query: string, context?: { locale?: string; signal?: AbortSignal }) =>
Promise<Array<{ lat: number; lng: number; label: string; address?: string }>>La table espace les appels, annule la requête précédente avec signal quand l’utilisateur continue de taper, garde au plus huit résultats valides et ignore les autres. Appelez votre fournisseur de géocodage depuis votre serveur, pour que sa clé et ses limites de débit y restent. Cette route Next.js appelle Photon, un géocodeur OpenStreetMap :
import { NextResponse } from "next/server";
const PHOTON_URL = process.env.PHOTON_URL ?? "https://photon.komoot.io/api/";
const LANGUAGES = new Set(["en", "fr", "de"]);
type PhotonFeature = {
geometry: { coordinates: [number, number] };
properties: Record<string, string | undefined>;
};
export async function GET(request: Request) {
// Vérifiez la session ici : la route ne doit servir que des utilisateurs connectés.
const params = new URL(request.url).searchParams;
const query = (params.get("q") ?? "").trim().slice(0, 200);
if (query.length < 3) {
return NextResponse.json([]);
}
const language = (params.get("locale") ?? "en").slice(0, 2);
const url = new URL(PHOTON_URL);
url.searchParams.set("q", query);
url.searchParams.set("limit", "8");
if (LANGUAGES.has(language)) {
url.searchParams.set("lang", language);
}
const response = await fetch(url, { signal: request.signal });
if (!response.ok) {
return NextResponse.json({ error: "Geocoding failed" }, { status: 502 });
}
const { features } = (await response.json()) as { features: PhotonFeature[] };
return NextResponse.json(
features.map(({ geometry, properties: p }) => {
const street = [p.housenumber, p.street].filter(Boolean).join(" ");
const address = [street, [p.postcode, p.city].filter(Boolean).join(" "), p.country]
.filter(Boolean)
.join(", ");
return {
lng: geometry.coordinates[0],
lat: geometry.coordinates[1],
label: p.name ?? (street || address),
address,
};
})
);
}geocode: async (query, { locale, signal } = {}) => {
const params = new URLSearchParams({ q: query, locale: locale ?? "en" });
const response = await fetch(`/api/geocode?${params}`, { signal });
if (!response.ok) {
throw new Error("Geocoding failed");
}
return response.json();
},Une recherche en échec affiche « La recherche a échoué » et garde le texte saisi. L’instance publique de Photon est prévue pour un usage léger : en production, hébergez votre propre instance ou utilisez un fournisseur dont les conditions autorisent la recherche pendant la saisie, et affichez l’attribution qu’il exige. Le service public de Nominatim, par exemple, n’autorise pas l’autocomplétion. La même action geocode convertit les adresses lors des imports.
Filtres
Les colonnes de lieu proposent quatre opérateurs dans les filtres de colonne et dans le constructeur de filtres avancés :
| Opérateur | values | Correspond à |
|---|---|---|
isEmpty | Aucune | Les fiches sans lieu. |
isNotEmpty | Aucune | Les fiches avec un lieu. |
withinDistance | [lat, lng, km] | Les lieux à moins de km kilomètres du point (distance orthodromique). |
withinBounds | [west, south, east, north] | Les lieux dans le rectangle ; west plus grand que east traverse l’antiméridien. |
Les saisies sont des champs numériques avec libellé. Une règle aux valeurs incomplètes correspond à toutes les fiches, comme pour les autres types. Une règle de filtre avancé arrive dans list sous cette forme, par exemple :
{ id: "near-paris", columnId: "site", type: "location", operator: "withinDistance", values: [48.8566, 2.3522, 10], isActive: true }Le matchesContractFilter partagé implémente ces opérateurs pour les données locales et les démos. Sur PostgreSQL avec PostGIS et la colonne site_geom de l’exemple de la vue Carte, les deux opérateurs spatiaux deviennent :
-- withinDistance: [lat, lng, km]
st_dwithin(site_geom::geography, st_setsrid(st_makepoint($2, $1), 4326)::geography, $3 * 1000)
-- withinBounds: [west, south, east, north], quand west <= east
site_geom && st_makeenvelope($1, $2, $3, $4, 4326)Import et export
L’import CSV lit « lat,lng » (aussi avec
;ou une espace entre les nombres) et les lieux en JSON. Tout autre texte est une adresse : avecactions.geocode, chaque adresse distincte est géocodée une fois avant l’étape de revue, une requête à la fois, et la première suggestion est gardée avec l’adresse. Sansgeocode, ou quand rien n’est trouvé, la cellule est une erreurinvalid_location.Les exports écrivent le lieu mis en forme (le nom, sinon l’adresse, sinon les coordonnées) quand Valeurs vaut « Telles qu’affichées », et « lat,lng » quand il vaut « Brutes », ce que l’import relit.
Connecteurs
Les connecteurs Notion et Google Sheets écrivent les lieux en texte « lat, lng » : une propriété de texte enrichi dans Notion et une cellule de texte dans Google Sheets, et ils comparent les lieux sous ce texte. Les valeurs ramenées depuis la cible sont stockées sous ce texte, que la table lit comme un lieu.
Limites connues :
Le nom et l’adresse ne sont pas conservés lors d’un aller-retour par Notion ou Google Sheets : seules les coordonnées reviennent.
Une feuille avec des colonnes de latitude et de longitude séparées n’est pas combinée en un seul lieu. Associez plutôt une colonne de texte contenant « lat, lng ».
Traductions
L’éditeur, les cellules et les filtres ont des libellés intégrés en anglais et en français ; le français est utilisé quand la locale commence par fr. Remplacez-en n’importe lequel avec des clés plates location.<key> dans les traductions de la table, en React et en Vue, par exemple "location.search": "Trouver une adresse".
Éditeur :
location,pin,label,address,latitude,longitude,search,searching,noResults,searchFailed,suggestions,clear,done,cancel,edit,invalidCoordinates,coordinatesHint,noLocation.Filtres :
withinDistance,withinBounds,distance,west,south,east,north,withinKm(avec{km}et{point}),inArea(avec{south},{west},{north}et{east}).
Les noms des opérateurs dans les menus de filtre sont filters.operators.within_distance et filters.operators.within_bounds.
Notes de compatibilité
L’union des types de colonne inclut désormais "location", tout comme InlineEditEditor et FormFieldType ; les opérateurs de filtre gagnent withinDistance et withinBounds. Si votre code garde une table exhaustive Record<…> sur l’une de ces unions, par exemple des icônes par type de colonne, ajoutez une entrée location pour qu’il compile toujours.