UrbanViable’s scouting interface is composed of five React components.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/danizd/urbanviable/llms.txt
Use this file to discover all available pages before exploring further.
MapViewer owns the MapLibre GL JS instance and all map interaction. ToolPanel is the 320 px sidebar that hosts the seven ScoreSlider controls and the Tooltip detail panel. DataStatus renders a freshness badge in the header. Together they communicate through props and callbacks flowing down from ScoutingPage — no global state library is used.
MapViewer
File:src/components/MapViewer.jsx
Initialises the MapLibre GL JS map on mount, adds the GeoJSON source and two rendering layers, and wires up click and hover event handlers.
Props
| Prop | Type | Description |
|---|---|---|
mapRef | React.MutableRefObject | Ref that receives the maplibregl.Map instance — shared with useMapStyle |
onFeatureClick | (properties: object) => void | Called with the raw GeoJSON feature properties when the user clicks a census section |
Map Initialisation
NavigationControl (zoom + compass) is added to 'top-left'.
Layers
Both layers are added insidemap.on('load') after the GeoJSON is fetched and added as a source.
| Layer ID | Type | Purpose |
|---|---|---|
secciones-fill | fill | Heat-map colour layer — paint property updated by useMapStyle |
secciones-outline | line | Subtle white section borders — opacity ramps from invisible at zoom 8 to visible at zoom 10+ |
source: sourceName (default 'galicia-scouting') and the GeoJSON feature collection loaded at mount time. When using vector tiles the equivalent field would be source-layer: 'secciones'.
Events
| Event | Trigger | Effect |
|---|---|---|
click on secciones-fill | User clicks a census section | Calls onFeatureClick(feature.properties) and opens a floating maplibregl.Popup with municipality name, cusec code, renta, and actividad |
mouseenter on secciones-fill | Cursor enters a section | Sets canvas.style.cursor = 'pointer' |
mouseleave on secciones-fill | Cursor leaves a section | Resets cursor to '' |
ToolPanel
File:src/components/ToolPanel.jsx
The 320 px fixed left sidebar. It receives the weight state and change handler from ScoutingPage and distributes them to the child components.
Props
| Prop | Type | Description |
|---|---|---|
variables | Array<VariableDescriptor> | The VARIABLES array from constants/variables.js |
weights | object | Current weight values keyed by variable.key |
onWeightChange | (key: string, value: number) => void | Passed to each ScoreSlider as onChange |
feature | object | null | GeoJSON properties of the clicked feature, or null |
onClearFeature | () => void | Passed to Tooltip as onClose |
Structure
The sidebar is rendered as an<aside className="sidebar"> containing three <section className="sidebar-section"> blocks:
ScoreSlider
File:src/components/ScoreSlider.jsx
A single labelled range input representing the importance weight for one scouting indicator.
Props
| Prop | Type | Description |
|---|---|---|
variable | VariableDescriptor | Descriptor object with key, label, description, enabled, absKey, absLabel, absFormat |
value | number (0–1) | Current slider position — controlled by ScoutingPage state |
onChange | (key: string, value: number) => void | Fires on every input event with the variable key and the new numeric value |
Rendering
Visual States
| State | Appearance |
|---|---|
value === 0 | Label text in --color-gray-500; track rendered in gray |
value > 0 | Label text in --color-gray-900; thumb and filled track in --color-secondary (green accent) |
variable.enabled === false | Entire card receives disabled CSS class; <input> has the disabled attribute; cursor is not-allowed |
DataStatus
File:src/components/DataStatus.jsx
A lightweight status badge rendered in the application header that communicates whether the underlying scouting dataset is up to date.
Behaviour
On mount,DataStatus calls getDataStatus() from services/api.js, which issues GET /api/status (configurable via REACT_APP_DATA_STATUS_URL). The endpoint is expected to return a JSON object containing an updated_at ISO-8601 timestamp.
ageInDays = (now - updatedAt) / 86_400_000 and chooses one of two badge variants:
| Condition | Badge |
|---|---|
ageInDays ≤ 400 | ✅ Datos: DD/MM/YYYY — normal style |
ageInDays > 400 | ⚠️ Datos: DD/MM/YYYY — warning style (yellow) |
hasError = true and renders null — the badge disappears silently without blocking the rest of the application.
Tooltip
File:src/components/Tooltip.jsx
Displays the full metric breakdown for a clicked census section. Rendered inside the sidebar rather than as a floating map popup, which keeps the dark map canvas unobstructed.
Props
| Prop | Type | Description |
|---|---|---|
feature | object | null | Raw GeoJSON feature properties from the click event, or null when nothing is selected |
onClose | () => void | Called when the user clicks the close button — clears the feature in ScoutingPage state |
variables | Array<VariableDescriptor> | The VARIABLES array, used to render each metric row via absLabel and absFormat |
Idle State
Whenfeature is null, the component renders a neutral placeholder:
Active State
When a feature is selected, the panel shows:- Title —
feature.NMUN(municipality name), falling back to'Municipio' - Subtitle —
feature.cusec(census section code) - Close button — calls
onClose; styled asmap-floating-btn - Metric rows — one row per entry in
variables, rendered withvariable.absLabelandvariable.absFormat(feature[variable.absKey])
Quick Map Popup
In addition to the sidebar panel,MapViewer creates a maplibregl.Popup anchored to the click coordinates with a brief summary (municipality name, cusec code, renta, and actividad). This popup closes automatically when the user clicks a different section.
VARIABLES Array — Full Reference
Defined in src/constants/variables.js and passed as a prop throughout the component tree: