Modules ProS’applique àSuperScheduler Pro
Minimap et métriques dérivées
Affichez SchedulerMinimap, importé de super-scheduler/minimap, avec le contrôle fourni par useSchedulerControl() ; sans options, la minimap indique combien d’événements se chevauchent chaque jour. Pour une métrique métier comme l’utilisation ou le taux d’occupation, passez une fonction series que votre application calcule par tranche, avec peak: 'absolute', max: 1 et une fonction tone pour les couleurs d’alerte et de danger. Faire glisser la fenêtre déplace la frise, faire glisser ses bords zoome, et la fenêtre s’utilise au clavier comme un curseur.
Une année de réservations ne tient pas à l’écran. La minimap est une fine bande placée sous le planificateur (ou au-dessus) qui montre toute la frise d’un coup : une barre par tranche de temps, avec une fenêtre qui marque la période visible. Les utilisateurs repèrent les semaines chargées et s’y rendent. Les barres affichent le nombre que votre application choisit, ce qui fait de la bande un graphique compact d’utilisation, d’occupation, de charge ou de chiffre d’affaires.
La minimap nécessite SuperScheduler Pro.
Ajouter une minimap
SchedulerMinimap est le composant React. Il a besoin du contrôle du planificateur, qui n’existe qu’une fois le planificateur monté ; useSchedulerControl() vous fournit control sous forme d’état (null au départ), et la minimap accepte null et attend.
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" />
</>
)
}Vous devriez voir une bande de 32 px avec les initiales des mois, un trait pour aujourd’hui, les jours passés estompés et une fenêtre couvrant les semaines visibles. Faites glisser la fenêtre : le planificateur défile avec elle.
Sans series, la bande utilise eventDensity(control) : le nombre d’événements qui chevauchent chaque tranche. Le comptage se fait par courtes tâches en arrière-plan (jusqu’à 8 ms ou 15 000 événements par tâche), le résultat est mis en cache et la bande est redessinée à la fin, si bien qu’un grand volume d’événements ne bloque jamais la page.
Construction de la bande
La minimap découpe une plage de temps en tranches et dessine une valeur par tranche :
- Plage. Par défaut, la frise du contrôle (à partir de
startDatepourdays) ; avec le défilement infini, la portion actuellement générée.range: { start, end }fixe une autre période, par exemple une année entière alors que le planificateur affiche un mois. - Tranches. Une journée chacune ; une heure avec
scale: 'Hour'ou'Minute'; une semaine quand la plage dépasse 730 jours. - Valeurs. Votre série renvoie un nombre par tranche. Quand il y a plus de tranches que de pixels, les valeurs voisines sont moyennées en une barre par colonne de pixels, alignée sur les pixels physiques de l’écran.
- Hauteur. Avec
peak: 'relative'(par défaut), la barre la plus haute correspond à la plus grande valeur. Avecpeak: 'absolute', les barres se mesurent par rapport àmax(1 par défaut), si bien qu’une journée complète paraît toujours pleine.
Alimenter la bande avec votre métrique
series est soit un Float32Array qui couvre toute la plage, soit une fonction qui reçoit la plage (start, end, buckets, bucketMs) et renvoie une valeur par tranche. La forme fonction s’adapte quand l’utilisateur zoome et que la taille des tranches change.
La bibliothèque ne sait pas ce que « chargé » signifie pour votre activité : la métrique relève donc de votre code. Celle-ci calcule l’utilisation, c’est-à-dire la part réservée du temps disponible, pour un nombre quelconque de ressources.
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
}
}Avec deux fourgons, l’un réservé toute la journée et l’autre à partir de midi, la journée vaut 0,75. Les intervalles sont semi-ouverts, comme dans le planificateur : une réservation qui se termine à minuit ne touche pas le jour suivant.
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"
/>
</>
)
}La bande se lit désormais comme un graphique d’utilisation : des barres sobres qui foncent avec la valeur, ambre à partir de 80 %, rouges à partir de 95 %, et un lecteur d’écran annonce « Fleet utilization overview, Showing 1 Jan 2026 to 26 Jan 2026 ».
Dans le cas courant où l’on pondère les événements au lieu de les compter, eventDensity(control, { weight }) accepte une fonction des données de l’événement (heures, unités, clients). L’extrait impératif plus bas l’utilise.
Teintes, pic, repères et libellés
| Option | Défaut | Effet |
|---|---|---|
height | 28 | Hauteur de la bande en pixels ; les initiales des mois exigent 24 ou plus |
peak | 'relative' | 'absolute' mesure les barres par rapport à max |
max | 1 | La valeur qui remplit une barre, avec peak: 'absolute' |
tone(value, index) | toutes à 'base' | 'base', 'warn' ou 'danger' par tranche |
marks | tous à true | today (un trait à la date courante du navigateur), months (séparateurs et initiales, l’année en janvier), past (tranches antérieures estompées) |
range | la frise | La période couverte par la bande |
labels | anglais ou espagnol | label (le nom accessible de la fenêtre), zoom (instructions de redimensionnement) et valueText(start, end) |
Les libellés par défaut sont en anglais, ou en espagnol quand la locale du planificateur commence par es : « Visible period », « Drag either edge to zoom, or use + and − », et les dates visibles au format yyyy-MM-dd – yyyy-MM-dd. Fournissez labels pour toute autre langue.
Les couleurs viennent de tokens, qui se rabattent sur le thème du planificateur. Définissez-les sur le conteneur de la minimap ou sur n’importe quel ancêtre :
| Token | Valeur de repli |
|---|---|
--super-scheduler-minimap-base | la couleur d’accent |
--super-scheduler-minimap-warn | #f59e0b |
--super-scheduler-minimap-danger | #ef4444 |
--super-scheduler-minimap-past | le texte atténué |
--super-scheduler-minimap-today | la couleur d’accent |
--super-scheduler-minimap-months | la couleur de bordure |
--super-scheduler-minimap-label | le texte atténué |
--super-scheduler-minimap-brush | la couleur d’accent |
La bande est redessinée quand le thème change : modification de class, data-theme ou data-color-scheme sur <html>, sur la racine du planificateur ou sur le conteneur, ou changement du jeu de couleurs du système.
Interactions avec la fenêtre
| Action | Effet |
|---|---|
| Faire glisser la fenêtre | Déplace la frise |
| Faire glisser l’un des bords de la fenêtre | Zoome : vers l’extérieur, plus de temps visible ; vers l’intérieur, moins ; le bord opposé reste fixe, dans les limites min et max de zoomGesture |
| Cliquer sur la bande hors de la fenêtre | Fait défiler pour centrer cette date (avec animation, sauf si la réduction des animations est activée) |
| Flèches Gauche / Droite, Bas / Haut | Un jour plus tôt ou plus tard ; avec Maj, sept jours |
| Page préc. / Page suiv. | Un mois plus tôt ou plus tard |
| Début / Fin | Début ou fin de la plage |
+ ou =, - ou − | Zoom avant ou arrière autour du centre |
La fenêtre est un role="slider" focalisable avec aria-valuetext ; le canvas est masqué aux technologies d’assistance. Avec cellWidthSpec: 'Auto', les poignées des bords et les touches de zoom sont désactivées, puisque le planificateur affiche de toute façon toute la frise. Les mouvements du pointeur sont appliqués une fois par image d’animation.
API impérative et libération
createMinimap(control, container, options) crée la bande dans n’importe quel élément et renvoie { element, update, refresh, dispose }. Elle lève une exception si le contrôle n’a pas été initialisé ; en React, créez-la donc dans un effet qui dépend 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)modifie les options et redessine ;refresh()redemande la série ;dispose()retire la bande et libère ses observers, ses écouteurs et le travail en attente. La libération du contrôle produit le même effet.
SchedulerMinimap fait tout cela pour vous : il crée la bande dès que control est disponible, la recrée si le contrôle change et la libère au démontage.
Garder la bande à jour
La minimap est redessinée, et une fonction de série rappelée, quand :
- les événements du contrôle changent (un glissement, un appel d’API, un chargement) ;
- un zoom se termine, le conteneur est redimensionné ou le thème change ;
- vous appelez
refresh()ouupdate().
Pendant un geste, les rafraîchissements attendent et ont lieu une fois le geste terminé. Une fonction de série qui lit les événements du contrôle lui-même est donc toujours à jour. Une série calculée à partir des données de votre application l’est quand vous passez une nouvelle fonction après la modification de ces données, comme le fait useMemo dans l’extrait sur l’utilisation.
SchedulerMinimap appelle update avec ses props à chaque rendu de son parent. Mémoïsez series, tone et labels (ou définissez-les au niveau du module) pour qu’un nouveau rendu ne recalcule pas la série pour rien.
Ce qui revient à votre application
- La métrique. Ce qui compte comme capacité, quels événements comptent (provisoires, annulés, blocages) et comment les pondérer.
- Les données que vous n’avez pas chargées. La série ne voit que ce que votre code lui donne. Avec le chargement par plages, il se peut que seule une partie de l’année soit en mémoire : pour une vue sur l’année entière, récupérez des agrégats par jour depuis votre backend et passez-les comme série, avec une
rangefixe. - Seuils et formulations. Limites des teintes, libellés et leurs traductions.
Voir aussi
Planification d’une flotte de locationUne citadine est immobilisée le jour du départ. Confiez son contrat à une autre voiture, gardez le temps de préparation et voyez où la flotte manque. Ordonnancement des ordres de fabricationLa maintenance a été avancée. Déplacez l’ordre et gardez ses opérations dans le bon ordre. Planification des postes à quaiUn navire arrive avec douze heures de retard. Déplacez sa fenêtre d’accostage, puis entraînez son remorqueur et ses grues. Planning des chambres d’hôtelLa douche de la 104 fuit. Relogez le prochain client, bloquez la chambre pour le plombier et repérez les nuits déjà complètes.
- Échelles de temps et zoom pour les limites de zoom que respectent les bords de la fenêtre.
- Thèmes, tokens, Tailwind et mode sombre pour les tokens sur lesquels la minimap se rabat.
Exemples liés
- FleetlinePlanification d’une flotte de locationUne citadine est immobilisée le jour du départ. Confiez son contrat à une autre voiture, gardez le temps de préparation et voyez où la flotte manque.
- ForgeOrdonnancement des ordres de fabricationLa maintenance a été avancée. Déplacez l’ordre et gardez ses opérations dans le bon ordre.
- HarborworksPlanification des postes à quaiUn navire arrive avec douze heures de retard. Déplacez sa fenêtre d’accostage, puis entraînez son remorqueur et ses grues.
- Casa NomaPlanning des chambres d’hôtelLa douche de la 104 fuit. Relogez le prochain client, bloquez la chambre pour le plombier et repérez les nuits déjà complètes.