Módulos ProSe aplica aSuperScheduler Pro
Minimapa y métricas derivadas
Renderiza SchedulerMinimap de super-scheduler/minimap con el control de useSchedulerControl(); sin opciones, muestra cuántos eventos se solapan cada día. Para una métrica de negocio como la utilización o la ocupación, pasa una función series que tu aplicación calcula por tramo, con peak: 'absolute', max: 1 y una función tone para los colores de aviso y de peligro. Arrastrar la ventana desplaza la línea de tiempo, arrastrar sus bordes hace zoom, y la ventana funciona con el teclado como un control deslizante.
Un año de reservas no cabe en pantalla. El minimapa es una franja estrecha debajo (o encima) del scheduler que muestra toda la línea de tiempo de una vez: una barra por tramo de tiempo y una ventana que marca el periodo visible. Los usuarios ven dónde están las semanas con más carga y saltan allí. Las barras muestran el número que decida tu aplicación, así que la franja se convierte en un gráfico compacto de utilización, ocupación, carga o ingresos.
El minimapa requiere SuperScheduler Pro.
Añadir un minimapa
SchedulerMinimap es el componente React. Necesita el control del scheduler, que solo existe después de que el scheduler se monte; useSchedulerControl() te da control como estado (null al principio), y el minimapa acepta null y espera.
import { useMemo } from 'react'
import { SuperSchedulerComponent, useSchedulerControl } from 'super-scheduler'
import type { SuperScheduler } from 'super-scheduler'
import { SchedulerMinimap } from 'super-scheduler/minimap'
import 'super-scheduler/styles.css'
export function PlanningWithOverview(props: {
resources: SuperScheduler.ResourceData[]
events: SuperScheduler.EventData[]
}) {
// `control` is null until the scheduler has mounted; the minimap waits for it.
const { controlRef, control } = useSchedulerControl()
const events = useMemo(() => props.events.slice(), [props.events])
return (
<>
<SuperSchedulerComponent
controlRef={controlRef}
startDate="2026-01-01"
days={365}
scale="Day"
cellWidth={40}
resources={props.resources}
events={events}
/>
{/* Without `series`, the strip shows how many events overlap each day. */}
<SchedulerMinimap control={control} height={32} className="planning-minimap" />
</>
)
}Deberías ver una franja de 32 px con las iniciales de los meses, una marca en el día de hoy, los días anteriores atenuados y una ventana que cubre las semanas visibles. Arrastra la ventana y el scheduler se desplaza con ella.
Sin series, la franja usa eventDensity(control): el número de eventos que se solapan con cada tramo. Cuenta en tareas cortas en segundo plano (hasta 8 ms o 15.000 eventos por tarea), guarda el resultado en caché y vuelve a pintar al terminar, de modo que un almacén grande nunca bloquea la página.
Cómo se construye la franja
El minimapa divide un rango de tiempo en tramos y dibuja un valor por tramo:
- Rango. Por defecto, la línea de tiempo del control (desde
startDatedurantedays); con scroll infinito, el periodo generado en ese momento.range: { start, end }fija otro periodo, por ejemplo un año entero mientras el scheduler muestra un mes. - Tramos. Un día cada uno; una hora con
scale: 'Hour'o'Minute'; una semana cuando el rango supera los 730 días. - Valores. Tu serie devuelve un número por tramo. Cuando hay más tramos que píxeles, los valores contiguos se promedian en una barra por columna de píxeles, alineada con los píxeles del dispositivo.
- Altura. Con
peak: 'relative'(por defecto), la barra más alta corresponde al valor más grande. Conpeak: 'absolute', las barras se miden respecto amax(por defecto 1), así que un día completo siempre se ve lleno.
Alimentarlo con tu propia métrica
series es un Float32Array que cubre todo el rango, o una función que recibe el rango (start, end, buckets, bucketMs) y devuelve un valor por tramo. La forma de función se adapta cuando el usuario hace zoom y cambia el tamaño del tramo.
La librería no sabe qué significa «ocupado» en tu negocio, así que la métrica es código tuyo. Esta calcula la utilización: la parte del tiempo disponible que está reservada, para cualquier número de recursos.
import { SuperScheduler } from 'super-scheduler'
import type { MinimapRange, MinimapSeries } from 'super-scheduler/minimap'
export interface Booking {
/** ISO wall-clock values with seconds; `end` is exclusive. */
readonly start: string
readonly end: string
}
/**
* Booked share of the available time in each bucket: 0 is idle, 1 is every resource busy
* for the whole bucket. The application decides what "capacity" means; here it is the
* number of bookable resources.
*/
export function utilizationSeries(bookings: readonly Booking[], capacity: number): MinimapSeries {
// Parse once; the series function runs again on every redraw.
const spans = bookings.map((booking) => ({
start: new SuperScheduler.Date(booking.start).getTime(),
end: new SuperScheduler.Date(booking.end).getTime(),
}))
return (range: MinimapRange) => {
const values = new Float32Array(range.buckets)
const origin = range.start.getTime()
const available = range.bucketMs * Math.max(1, capacity)
for (const span of spans) {
// Half-open [start, end): a booking ending at midnight does not touch the next day.
const first = Math.max(0, Math.floor((span.start - origin) / range.bucketMs))
const last = Math.min(range.buckets, Math.ceil((span.end - origin) / range.bucketMs))
for (let i = first; i < last; i++) {
const bucketStart = origin + i * range.bucketMs
const overlap =
Math.min(span.end, bucketStart + range.bucketMs) - Math.max(span.start, bucketStart)
if (overlap > 0) values[i] = (values[i] ?? 0) + overlap / available
}
}
return values
}
}Con dos furgonetas, una reservada todo el día y la otra desde el mediodía, el día marca 0,75. Los intervalos son semiabiertos, como en el scheduler: una reserva que termina a medianoche no toca el día siguiente.
import { useMemo } from 'react'
import { SuperSchedulerComponent, useSchedulerControl } from 'super-scheduler'
import type { SuperScheduler } from 'super-scheduler'
import { SchedulerMinimap } from 'super-scheduler/minimap'
import type { MinimapLabels, MinimapTone } from 'super-scheduler/minimap'
import { utilizationSeries } from './utilization-series'
// Module-level: the minimap receives the same functions on every render.
const tone = (value: number): MinimapTone =>
value >= 0.95 ? 'danger' : value >= 0.8 ? 'warn' : 'base'
const LABELS: Partial<MinimapLabels> = {
label: 'Fleet utilization overview',
valueText: (start, end) =>
`Showing ${start.toString('d MMM yyyy')} to ${end.toString('d MMM yyyy')}`,
}
export function FleetPlanning(props: {
vehicles: SuperScheduler.ResourceData[]
bookings: SuperScheduler.EventData[]
}) {
const { controlRef, control } = useSchedulerControl()
const events = useMemo(() => props.bookings.slice(), [props.bookings])
// Recomputed only when the data changes; a new series function makes the strip redraw.
const series = useMemo(
() =>
utilizationSeries(
props.bookings.map((booking) => ({
start: String(booking.start),
end: String(booking.end),
})),
props.vehicles.length,
),
[props.bookings, props.vehicles.length],
)
return (
<>
<SuperSchedulerComponent
controlRef={controlRef}
startDate="2026-01-01"
days={365}
scale="Day"
cellWidth={40}
resources={props.vehicles}
events={events}
/>
<SchedulerMinimap
control={control}
series={series}
// 1 means full, whatever the busiest bucket is.
peak="absolute"
max={1}
tone={tone}
labels={LABELS}
marks={{ today: true, months: true, past: true }}
height={32}
className="fleet-minimap"
/>
</>
)
}Ahora la franja se lee como un gráfico de utilización: barras suaves que se oscurecen con el valor, ámbar a partir del 80 %, rojo a partir del 95 %, y un lector de pantalla anuncia «Fleet utilization overview, Showing 1 Jan 2026 to 26 Jan 2026».
Para el caso habitual de ponderar los eventos en lugar de contarlos, eventDensity(control, { weight }) acepta una función de los datos del evento (horas, unidades, huéspedes). El fragmento imperativo de más abajo la usa.
Tonos, pico, marcas y etiquetas
| Opción | Por defecto | Efecto |
|---|---|---|
height | 28 | Altura de la franja en píxeles; las iniciales de los meses necesitan 24 o más |
peak | 'relative' | 'absolute' mide las barras respecto a max |
max | 1 | El valor que llena una barra, con peak: 'absolute' |
tone(value, index) | todo 'base' | 'base', 'warn' o 'danger' por tramo |
marks | todo true | today (una marca en la fecha actual del navegador), months (separadores e iniciales, con el año en enero), past (tramos anteriores atenuados) |
range | la línea de tiempo | El periodo que cubre la franja |
labels | inglés o español | label (el nombre accesible de la ventana), zoom (instrucciones para redimensionar) y valueText(start, end) |
Las etiquetas por defecto están en inglés, o en español cuando el locale del scheduler empieza por es: «Visible period», «Drag either edge to zoom, or use + and −» y las fechas visibles como yyyy-MM-dd – yyyy-MM-dd. Para cualquier otro idioma, proporciona labels.
Los colores salen de tokens que, si no los defines, toman los del tema del scheduler. Defínelos en el contenedor del minimapa o en cualquier ancestro:
| Token | Valor de respaldo |
|---|---|
--super-scheduler-minimap-base | el acento |
--super-scheduler-minimap-warn | #f59e0b |
--super-scheduler-minimap-danger | #ef4444 |
--super-scheduler-minimap-past | el texto atenuado |
--super-scheduler-minimap-today | el acento |
--super-scheduler-minimap-months | el color de borde |
--super-scheduler-minimap-label | el texto atenuado |
--super-scheduler-minimap-brush | el acento |
La franja se vuelve a pintar cuando cambia el tema: un cambio de class, data-theme o data-color-scheme en <html>, en la raíz del scheduler o en el contenedor, o un cambio del esquema de color del sistema.
Interacción con la ventana
| Entrada | Efecto |
|---|---|
| Arrastrar la ventana | Desplaza la línea de tiempo |
| Arrastrar cualquiera de los bordes de la ventana | Hace zoom: hacia fuera muestra más tiempo, hacia dentro menos; el borde opuesto queda fijo, dentro del mínimo y el máximo de zoomGesture |
| Hacer clic en la franja fuera de la ventana | Desplaza la vista para que esa fecha quede en el centro (con animación, salvo que esté activado el movimiento reducido) |
| Flecha izquierda / derecha, abajo / arriba | Un día antes o después; con Mayús, siete días |
| RePág / AvPág | Un mes antes o después |
| Inicio / Fin | Principio o final del rango |
+ o =, - o − | Acerca o aleja el zoom alrededor del centro |
La ventana es un role="slider" enfocable con aria-valuetext; el canvas queda oculto para las tecnologías de apoyo. Con cellWidthSpec: 'Auto', los tiradores de los bordes y las teclas de zoom se desactivan, porque el scheduler ya ajusta toda la línea de tiempo a la vista. Los movimientos del puntero se aplican una vez por fotograma de animación.
API imperativa y liberación
createMinimap(control, container, options) crea la franja dentro de cualquier elemento y devuelve { element, update, refresh, dispose }. Lanza una excepción si el control no se ha inicializado, así que en React créala en un efecto que dependa de control:
import { useEffect, useMemo, useRef } from 'react'
import { SuperSchedulerComponent, useSchedulerControl } from 'super-scheduler'
import type { SuperScheduler } from 'super-scheduler'
import { createMinimap, eventDensity } from 'super-scheduler/minimap'
type Order = { quantity: number }
export function ProductionOverview(props: {
lines: SuperScheduler.ResourceData[]
orders: SuperScheduler.EventData<Order>[]
}) {
const { controlRef, control } = useSchedulerControl()
const host = useRef<HTMLDivElement>(null)
const events = useMemo(() => props.orders.slice(), [props.orders])
useEffect(() => {
// createMinimap needs an initialized control: run it after mount, keyed on the control.
if (control === null || host.current === null) return
const minimap = createMinimap(control, host.current, {
height: 28,
// Each order weighs its quantity instead of counting 1.
series: eventDensity(control, {
weight: (e) => (e as SuperScheduler.EventData<Order>).quantity,
}),
labels: { label: 'Production load overview' },
})
// Releases observers and pending work; control.dispose() does it too.
return () => minimap.dispose()
}, [control])
return (
<>
<SuperSchedulerComponent
controlRef={controlRef}
startDate="2026-10-01"
days={90}
scale="Day"
resources={props.lines}
events={events}
/>
<div ref={host} className="production-minimap" />
</>
)
}update(partialOptions)cambia opciones y vuelve a pintar;refresh()vuelve a pedir la serie;dispose()elimina la franja y libera sus observers, listeners y trabajo pendiente. Liberar el control también lo hace.
SchedulerMinimap hace todo esto por ti: crea la franja cuando control está disponible, la vuelve a crear si el control cambia y la libera al desmontarse.
Mantener la franja al día
El minimapa se vuelve a pintar, y vuelve a llamar a la función de serie, cuando:
- cambian los eventos del control (un arrastre, una llamada a la API, una carga);
- termina un zoom, se redimensiona el contenedor o cambia el tema;
- llamas a
refresh()o aupdate().
Mientras dura un gesto, los repintados esperan y se hacen cuando termina. Por eso, una función de serie que lee los eventos del propio control siempre está al día. Una serie calculada a partir de los datos de tu aplicación está al día cuando pasas una función nueva después de que cambien esos datos, como hace useMemo en el fragmento de utilización.
SchedulerMinimap llama a update con sus props en cada render de su componente padre. Memoiza series, tone y labels (o defínelos a nivel de módulo) para que un nuevo render no vuelva a calcular la serie sin motivo.
Qué le corresponde a tu aplicación
- La métrica. Qué cuenta como capacidad, qué eventos se tienen en cuenta (provisionales, cancelados, bloqueos) y cómo ponderarlos.
- Los datos que no has cargado. La serie solo ve lo que tu código le da. Con la carga por rangos, puede que solo una parte del año esté en memoria: para una vista del año completo, pide a tu backend agregados por día y pásalos como serie junto con un
rangefijo. - Umbrales y textos. Los límites de los tonos, las etiquetas y sus traducciones.
Relacionado
Planificación de una flota de alquilerUn compacto queda inmovilizado el día de la recogida. Pasa su alquiler a otro coche, respeta la limpieza y mira cuándo se queda sin flota la oficina. Planificación de órdenes de fabricaciónEl mantenimiento se ha adelantado. Aparta la orden y mantén sus operaciones en secuencia. Planificación de atraquesUn buque llega doce horas tarde. Mueve su ventana de atraque y lleva con ella el remolcador y las grúas. Planning de habitaciones de hotelGotea la ducha de la 104. Recoloca al siguiente cliente, bloquea la habitación para el fontanero y localiza las noches que ya están llenas.
- Escalas de tiempo y zoom para los límites de zoom que respetan los bordes de la ventana.
- Temas, tokens, Tailwind y modo oscuro para los tokens de los que el minimapa toma sus valores de respaldo.
Ejemplos relacionados
- FleetlinePlanificación de una flota de alquilerUn compacto queda inmovilizado el día de la recogida. Pasa su alquiler a otro coche, respeta la limpieza y mira cuándo se queda sin flota la oficina.
- ForgePlanificación de órdenes de fabricaciónEl mantenimiento se ha adelantado. Aparta la orden y mantén sus operaciones en secuencia.
- HarborworksPlanificación de atraquesUn buque llega doce horas tarde. Mueve su ventana de atraque y lleva con ella el remolcador y las grúas.
- Casa NomaPlanning de habitaciones de hotelGotea la ducha de la 104. Recoloca al siguiente cliente, bloquea la habitación para el fontanero y localiza las noches que ya están llenas.