InteractionS’applique àSuperScheduler Pro
Arborescences de ressources, colonnes et sélection
Activez treeEnabled et imbriquez les ressources dans children ; un parent reste replié sauf s’il a expanded: true. Ajoutez des colonnes d’en-tête de ligne avec rowHeaderColumns, en remplissant les cellules à partir des champs de ressource via display. Filtrez avec control.rows.filter() et control.events.filter() plus onRowFilter et onEventFilter, et sélectionnez avec eventClickHandling: 'Select', la sélection rectangulaire, rowClickHandling: 'Select' et les API multiselect et multirange.
Les vrais plannings ont rarement une liste plate de ressources. Les chambres appartiennent à des étages, les techniciens à des régions, les machines à des lignes de production. Ce guide présente les fonctionnalités Pro qui organisent les lignes et permettent d’agir sur plusieurs éléments à la fois : arborescences de ressources, colonnes d’en-tête de ligne, filtres, et les trois types de sélection (événements, lignes et plages de temps).
Tout ce qui suit nécessite SuperScheduler Pro (super-scheduler). L’édition Lite affiche une liste plate et refuse les children et columns de ressource.
Grouper les ressources en arborescence
Une arborescence, ce sont de simples données : une ressource avec un tableau children devient une ligne parente. Deux réglages l’activent :
treeEnabled: truesur le planificateur. Sans lui, leschildrensont ignorés et l’en-tête de ligne n’a pas de bouton de dépliage.expanded: truesur chaque parent qui doit être ouvert au départ. Les parents sont repliés sauf siexpandedvaut exactementtrue.
import { useMemo } from 'react'
import { SuperSchedulerComponent, useSchedulerControl } from 'super-scheduler'
import type { SuperScheduler } from 'super-scheduler'
import 'super-scheduler/styles.css'
// Regions are parent rows and technicians their children. A parent stays collapsed
// unless its resource says `expanded: true`.
const RESOURCES: SuperScheduler.ResourceData[] = [
{
id: 'north',
name: 'North region',
expanded: true,
children: [
{ id: 'ana', name: 'Ana Ruiz' },
{ id: 'ben', name: 'Ben Ortiz' },
],
},
{
id: 'south',
name: 'South region',
children: [
{ id: 'cai', name: 'Cai Moreno' },
{ id: 'dee', name: 'Dee Patel' },
],
},
]
const JOBS: SuperScheduler.EventData[] = [
{
id: 'job-1',
resource: 'ana',
start: '2026-10-05T00:00:00',
end: '2026-10-07T00:00:00',
text: 'Panel upgrade',
},
{
id: 'job-2',
resource: 'cai',
start: '2026-10-06T00:00:00',
end: '2026-10-09T00:00:00',
text: 'Boiler service',
},
]
export function TeamPlanner() {
const { controlRef, control } = useSchedulerControl()
// The control adopts the events array and mutates it: hand it a copy.
const events = useMemo(() => JOBS.slice(), [])
return (
<section aria-labelledby="team-plan-title">
<h2 id="team-plan-title">Field teams</h2>
<div role="toolbar" aria-label="Rows">
<button type="button" disabled={control === null} onClick={() => control?.rows.expandAll()}>
Expand all
</button>
<button
type="button"
disabled={control === null}
onClick={() => control?.rows.collapseAll()}
>
Collapse all
</button>
</div>
<SuperSchedulerComponent
controlRef={controlRef}
startDate="2026-10-05"
days={14}
scale="Day"
cellWidth={56}
treeEnabled
treeIndent={16}
// Region rows group technicians: no jobs and no time selection on them.
treePreventParentUsage
resources={RESOURCES}
events={events}
/>
</section>
)
}Vous devriez voir « North region » ouvert avec ses deux techniciens, « South region » fermé, et les deux boutons fonctionnels une fois le planificateur monté (control vaut null jusque-là).
Options associées :
| Option | Défaut | Effet |
|---|---|---|
treeEnabled | false | Construit les lignes à partir de children. |
treeIndent | 20 | Indentation par niveau, en pixels. |
treePreventParentUsage | false | Les lignes parentes n’acceptent ni événements ni sélection de temps. |
treeAutoExpand | true | Un parent replié s’ouvre quand un événement est glissé au-dessus de lui pendant une demi-seconde. |
rowFilterParentsAlwaysVisible | true | Conserve les ancêtres d’une ligne qui correspond à un filtre. |
Les parents peuvent aussi porter des événements (une réservation pour « toute l’équipe »), sauf si vous activez treePreventParentUsage. Une ressource isolée refuse les événements déposés avec preventUsage: true, et les ressources de premier niveau peuvent être épinglées au-dessus ou en dessous des lignes défilantes avec frozen: 'top' ou frozen: 'bottom', ce qui convient aux lignes de synthèse.
Déplier et replier depuis le code
Le contrôle expose l’arborescence via control.rows :
control.rows.expandAll()etcontrol.rows.collapseAll();control.rows.expand(level)ouvre les parents situés au-dessus d’un niveau (1par défaut,-1pour tous les niveaux) ;control.rows.find(id)renvoie une ligne ;row.expand(),row.collapse()etrow.toggle()la modifient.
onResourceExpand et onResourceCollapse signalent un dépliage ou un repliage une fois qu’il a eu lieu, avec la ligne dans args.resource. Utilisez-les pour mémoriser ce que l’utilisateur a ouvert ou pour synchroniser d’autres parties de votre interface.
Ajouter des colonnes d’en-tête de ligne
Sans rowHeaderColumns, l’en-tête de ligne affiche le name de chaque ressource. Avec cette option, l’en-tête devient un petit tableau :
textest le titre de la colonne (nameettitlesont aussi acceptés) ;displaydésigne un champ de la ressource dont la valeur remplit la cellule (un champ de même nom danstagsest lu en premier) ;- la première colonne affiche le
namede la ressource quand elle n’a pas dedisplay; widthdéfinit la largeur en pixels (par défautrowHeaderColumnDefaultWidth, soit 80) ;sortrend le titre cliquable : il trie les lignes selon ce champ, par ordre croissant puis décroissant, et définitaria-sortsur le titre.
Une ressource peut aussi porter ses propres columns: [{ text }, { html }]. Ces entrées sont associées par position, une par colonne.
import { useState } from 'react'
import { type SuperScheduler, SuperSchedulerComponent } from 'super-scheduler'
import 'super-scheduler/styles.css'
type Status = 'available' | 'off'
const TECHNICIANS: SuperScheduler.ResourceData[] = [
{ id: 'ana', name: 'Ana Ruiz', skill: 'Electrical', shift: 'Early', status: 'available' },
{ id: 'ben', name: 'Ben Ortiz', skill: 'HVAC', shift: 'Late', status: 'off' },
{ id: 'cai', name: 'Cai Moreno', skill: 'Plumbing', shift: 'Early', status: 'available' },
]
// `display` reads a resource field, so a column keeps its values when it is reordered.
// `sort` makes the title clickable: it sorts the rows by that field, ascending then descending.
const INITIAL_COLUMNS: SuperScheduler.RowHeaderColumnData[] = [
{ text: 'Technician', width: 150, sort: 'name' },
{ text: 'Skill', display: 'skill', width: 110, sort: 'skill' },
{ text: 'Shift', display: 'shift', width: 70, nonresizable: true },
{ text: 'Status', display: 'status', width: 100 },
]
const STATUS_LABEL: Record<Status, string> = { available: 'Available', off: 'Day off' }
// Runs with `this` = the control, whose rowHeaderColumns are always in the current order.
function decorateStatus(
this: SuperScheduler.SchedulerApi,
args: SuperScheduler.SchedulerBeforeRowHeaderRenderArgs,
): void {
const index = this.rowHeaderColumns?.findIndex((column) => column.display === 'status') ?? -1
const cell = args.row.columns[index]
const status = args.row.data.status
if (cell === undefined || (status !== 'available' && status !== 'off')) return
// With rowHeaderColumns, row.html is ignored: write the column's own html (trusted markup).
cell.html = `<span class="tech-status tech-status--${status}">${STATUS_LABEL[status]}</span>`
}
export function TechnicianColumns({ events }: { events: SuperScheduler.EventData[] }) {
// Kept in state so a parent re-render never hands the control the original order again.
const [columns, setColumns] = useState(INITIAL_COLUMNS)
return (
<SuperSchedulerComponent
startDate="2026-10-05"
days={14}
scale="Day"
cellWidth={56}
resources={TECHNICIANS}
events={events}
rowHeaderColumns={columns}
rowHeaderColumnsReorderable
onRowHeaderColumnsChange={(args) => setColumns([...args.columns])}
onBeforeRowHeaderRender={decorateStatus}
/>
)
}Pour décorer une cellule, utilisez onBeforeRowHeaderRender. Avec des colonnes, le html propre à la ligne est ignoré : écrivez plutôt args.row.columns[i].html. Ce balisage est du HTML de confiance : échappez donc le texte saisi par les utilisateurs avec SuperScheduler.Util.escapeHtml. Pour du contenu React dans l’en-tête de ligne, voir Slots de rendu React.
Redimensionner et réordonner les colonnes
Les colonnes sont redimensionnables par défaut (rowHeaderColumnsResizable: true) ; nonresizable: true le désactive pour une colonne. Après un redimensionnement, le contrôle écrit la nouvelle largeur dans l’objet colonne correspondant de votre tableau rowHeaderColumns et appelle onRowHeaderColumnResized({ column }).
Le réordonnancement est désactivé par défaut. Avec rowHeaderColumnsReorderable, les utilisateurs font glisser les titres de colonnes ; les bords de redimensionnement restent prioritaires sur le glisser. En keyboardMode: 'Full', Alt+Maj+Flèche gauche et Alt+Maj+Flèche droite déplacent le titre qui a le focus et annoncent le changement. Depuis le code, control.moveRowHeaderColumn(from, to) déplace une colonne d’après son index dans l’ordre actuel.
Chaque redimensionnement et chaque réordonnancement appellent onRowHeaderColumnsChange avec :
columns: les colonnes dans leur nouvel ordre, avec leurs largeurs ;order:order[i]est l’index d’origine (dans la prop que vous avez passée) de la colonne désormais en positioni;widthsetreason('resize'ou'reorder').
Gardez les colonnes dans le state React, comme le fait le snippet, et adoptez args.columns. Sinon, un parent qui refait son rendu avec un nouveau littéral de tableau redonnerait l’ordre d’origine au contrôle.
Filtrer lignes et événements
Un filtre, c’est un paramètre plus un callback. control.rows.filter(param) stocke le paramètre et appelle onRowFilter pour chaque ligne ; définissez args.visible = false pour la masquer. control.events.filter(param) et onEventFilter fonctionnent de la même façon pour les événements. Un paramètre falsy ('', null, 0) efface le filtre.
import { useEffect, useMemo, useState } from 'react'
import { SuperSchedulerComponent, useSchedulerControl } from 'super-scheduler'
import type { SchedulerProps, SuperScheduler } from 'super-scheduler'
type JobStatus = 'scheduled' | 'done' | 'cancelled'
type Job = { status: JobStatus }
// Module-level handlers keep their identity: a new onRowFilter or onEventFilter
// function makes the control evaluate the active filter again.
const FILTERS: Pick<SchedulerProps, 'onRowFilter' | 'onEventFilter'> = {
onRowFilter: (args) => {
const query = String(args.filterParam).toLowerCase()
args.visible = args.row.name.toLowerCase().includes(query)
},
onEventFilter: (args) => {
const job = args.e.data as SuperScheduler.EventData<Job>
args.visible = job.status === args.filterParam
},
}
export function FilteredJobs(props: {
resources: SuperScheduler.ResourceData[]
jobs: SuperScheduler.EventData<Job>[]
}) {
const { controlRef, control } = useSchedulerControl()
const [query, setQuery] = useState('')
const [status, setStatus] = useState<JobStatus | ''>('')
const events = useMemo(() => props.jobs.slice(), [props.jobs])
// A falsy parameter ('' or null) clears the filter.
useEffect(() => control?.rows.filter(query.trim() || null), [control, query])
useEffect(() => control?.events.filter(status || null), [control, status])
return (
<>
<label>
Technician <input type="search" value={query} onChange={(e) => setQuery(e.target.value)} />
</label>
<label>
Status
<select value={status} onChange={(e) => setStatus(e.target.value as JobStatus | '')}>
<option value="">All</option>
<option value="scheduled">Scheduled</option>
<option value="done">Done</option>
<option value="cancelled">Cancelled</option>
</select>
</label>
<SuperSchedulerComponent
{...FILTERS}
controlRef={controlRef}
startDate="2026-10-05"
days={14}
scale="Day"
treeEnabled
// Keeps the region row of every matching technician (default true).
rowFilterParentsAlwaysVisible
resources={props.resources}
events={events}
/>
</>
)
}Saisir « ben » laisse « North region » et « Ben Ortiz » : rowFilterParentsAlwaysVisible conserve les ancêtres de chaque ligne correspondante. Choisir un statut masque les autres événements sans toucher aux lignes.
Les filtres s’exécutent dans le navigateur, sur les données que détient le contrôle. Une recherche portant sur des données non chargées (un autre mois, un autre site) est une requête à votre backend, suivie de nouvelles resources ou de nouveaux events.
Sélectionner des événements
Par défaut, un clic sur un événement exécute seulement onEventClick. La sélection nécessite eventClickHandling: 'Select' :
- un clic sélectionne ou désélectionne l’événement et désélectionne les autres ;
- Ctrl+clic (Cmd+clic sur macOS) le sélectionne ou le désélectionne en conservant les autres, tant que
allowMultiSelectvauttrue(par défaut) ; - avec
eventMultiSelectRange, Maj+clic sélectionne les événements situés entre le dernier événement cliqué et celui-ci, à travers les lignes visibles ; Ctrl/Cmd+Maj les ajoute.
onEventSelect s’exécute avant le changement et peut l’annuler avec args.preventDefault() ; onEventSelected s’exécute après.
L’API control.multiselect lit et modifie la sélection : get(), add(e), remove(e), clear(), isSelected(e) et selectAll({ scope, filter }). selectAll sélectionne par défaut les événements visibles ; scope: 'all' parcourt tous les événements chargés, pour un coût proportionnel à la taille du stockage.
import { useEffect, useMemo, useState } from 'react'
import { SuperSchedulerComponent, useSchedulerControl } from 'super-scheduler'
import type { SchedulerProps, SuperScheduler } from 'super-scheduler'
import { subscribeScheduler } from 'super-scheduler/hooks'
type Job = { status: 'scheduled' | 'done' }
export function SelectableJobs(props: {
resources: SuperScheduler.ResourceData[]
jobs: SuperScheduler.EventData<Job>[]
onReschedule: (ids: SuperScheduler.EventId[]) => void
}) {
const { controlRef, control } = useSchedulerControl()
const [count, setCount] = useState(0)
const events = useMemo(() => props.jobs.slice(), [props.jobs])
// Rectangle selections and multiselect calls (selectAll, clear) publish the
// 'selection' topic; clicks are reported by onEventSelected below.
useEffect(() => {
if (control === null) return
return subscribeScheduler(control, 'selection', (selected) => setCount(selected.length))
}, [control])
const config = useMemo<SchedulerProps>(
() => ({
// A click toggles the event; Ctrl+click (Cmd+click on macOS) keeps the others.
eventClickHandling: 'Select',
// Shift+click selects every event between the last clicked one and this one.
eventMultiSelectRange: true,
// Shift+drag over the grid selects the events inside the rectangle.
rectangleSelectHandling: 'EventSelect',
// With the rectangle: Shift adds to the selection, Alt removes from it.
rectangleSelectModifiers: true,
onEventSelected() {
setCount(this.multiselect.get().length)
},
// Marks selected events for CSS. The hook runs again for every event whose
// selection changes.
onBeforeEventRender: (args) => {
const e = args.control.events.find(args.data.id)
if (e !== null && args.control.multiselect.isSelected(e)) {
args.data.cssClass = [args.data.cssClass, 'is-selected'].filter(Boolean).join(' ')
}
},
}),
[],
)
const selectScheduled = () =>
control?.multiselect.selectAll({
scope: 'view',
filter: (e) => (e.data as SuperScheduler.EventData<Job>).status === 'scheduled',
})
const reschedule = () => {
const ids = control?.multiselect.get().map((e) => e.id()) ?? []
if (ids.length > 0) props.onReschedule(ids)
}
return (
<>
<div role="toolbar" aria-label="Selection">
<button type="button" onClick={selectScheduled}>
Select scheduled in view
</button>
<button type="button" onClick={() => control?.multiselect.clear()}>
Clear
</button>
<button type="button" disabled={count === 0} onClick={reschedule}>
Reschedule {count} {count === 1 ? 'job' : 'jobs'}
</button>
</div>
<SuperSchedulerComponent
{...config}
controlRef={controlRef}
startDate="2026-10-05"
days={14}
scale="Day"
resources={props.resources}
events={events}
/>
</>
)
}Le compteur du bouton devrait suivre les clics, les rectangles et les actions de la barre d’outils, et les interventions sélectionnées devraient être entourées d’un contour grâce à ce CSS :
.super-scheduler__event.is-selected {
z-index: 2;
box-shadow: 0 0 0 2px var(--super-scheduler-accent), var(--super-scheduler-shadow-2);
}Avec allowMultiMove, faire glisser un événement sélectionné déplace toute la sélection ; chaque événement passe par les mêmes règles de déplacement (voir glisser, redimensionner et règles métier). En mode clavier complet, Ctrl/Cmd+A sélectionne tous les événements visibles.
Sélection rectangulaire
rectangleSelectHandling détermine ce que fait un rectangle tracé au glisser :
'EventSelect'sélectionne les événements qu’il contient (et exécute les callbacks) ;'Enabled'exécute seulement les callbacks, pour les applications qui agissent elles-mêmes sur le rectangle ;'Disabled'(par défaut) le désactive.
Les utilisateurs tracent un rectangle avec Maj+glisser sur la grille. Pour en démarrer un sans Maj, définissez args.action = 'RectangleSelect' dans onGridMouseDown, ou appelez control.multiselect.startRectangle() avant l’appui suivant. rectangleSelectMode: 'Row' maintient le rectangle dans la ligne où l’appui a commencé. Avec rectangleSelectModifiers, Maj ajoute à la sélection courante et Alt en retire ; sans l’une ni l’autre touche, le rectangle remplace la sélection.
Les callbacks sont onRectangleSelecting (à chaque frame ; args.visible = false masque le rectangle), onRectangleSelect (annulable, avec events, start, end et resources) et onRectangleSelected.
Sélectionner des lignes
rowClickHandling: 'Select' fait qu’un clic sur un en-tête de ligne sélectionne la ligne. Ctrl/Cmd sélectionne ou désélectionne une ligne, et Maj sélectionne une plage depuis la dernière ligne cliquée. Les lignes sélectionnées portent aria-selected="true".
import { useMemo, useState } from 'react'
import { SuperSchedulerComponent, useSchedulerControl } from 'super-scheduler'
import type { SchedulerProps, SuperScheduler } from 'super-scheduler'
export function RosterRows(props: {
resources: SuperScheduler.ResourceData[]
events: SuperScheduler.EventData[]
onNotify: (resourceIds: SuperScheduler.ResourceId[]) => void
}) {
const { controlRef, control } = useSchedulerControl()
const [selected, setSelected] = useState<readonly SuperScheduler.ResourceId[]>([])
const events = useMemo(() => props.events.slice(), [props.events])
const config = useMemo<SchedulerProps>(
() => ({
// A click on a row header selects the row; Ctrl/Cmd toggles, Shift selects a range.
rowClickHandling: 'Select',
onRowSelected() {
setSelected(this.rows.selection.get().map((row) => row.id))
},
}),
[],
)
return (
<>
<button
type="button"
disabled={selected.length === 0}
onClick={() => props.onNotify([...selected])}
>
Notify {selected.length} technicians
</button>
<button
type="button"
onClick={() => {
control?.rows.selection.clear()
setSelected([])
}}
>
Clear rows
</button>
<SuperSchedulerComponent
{...config}
controlRef={controlRef}
startDate="2026-10-05"
days={14}
scale="Day"
resources={props.resources}
events={events}
/>
</>
)
}control.rows.selection propose get(), add(row), remove(row), isSelected(row) et clear() ; ces appels mettent les lignes en évidence immédiatement et ne déclenchent aucun callback, c’est pourquoi le snippet réinitialise son propre state après clear(). onRowSelect (annulable) et onRowSelected s’exécutent autour de chaque sélection faite par un clic.
La prop selectedRows contient les ids des ressources sélectionnées. Passez-la pour démarrer avec une sélection ; le contrôle met à jour ce même tableau quand la sélection change.
Sélectionner des plages de temps
Faire glisser sur des cellules vides sélectionne une plage de temps et appelle onTimeRangeSelected une seule fois, au relâchement, avec start, end (exclusif), resource et origin: 'drag'. Autres origines :
'click': un simple clic sur une cellule vide sélectionne cette cellule ;'keyboard': avec le clavier activé, Entrée sur une cellule qui a le focus, ou Maj+Flèche gauche/droite pour étendre une plage ;'api':control.selectTimeRange(start, end, resource).
Vérifiez args.origin quand seul un glisser doit créer quelque chose. La sélection reste dessinée après le relâchement, jusqu’à la sélection suivante ou jusqu’à control.clearSelection(). Un clic ailleurs l’efface aussi, sauf avec timeRangeSelectedHandling: 'HoldForever' ; 'Disabled' désactive la sélection de plages.
onTimeRangeSelecting s’exécute à chaque changement pendant le glisser. Définissez args.allowed = false pour refuser une plage (l’ombre est marquée et le relâchement ne sélectionne rien), ou réécrivez args.start et args.end pour la borner. Les cellules désactivées et les parents soumis à treePreventParentUsage refusent les sélections sans aucun code.
Plusieurs plages à la fois
Avec allowMultiRange, un nouveau glisser peut ajouter une plage au lieu de remplacer la plage courante : tant que Ctrl/Cmd est maintenu (multiRangeMode: 'CtrlOrMeta', par défaut) ou toujours ('Always'). args.multirange liste toutes les plages sélectionnées, et control.multirange les lit ou les modifie : get(), clear() et add(new SuperScheduler.Selection(start, end, resource)).
import { useMemo, useState } from 'react'
import { SuperScheduler, SuperSchedulerComponent, useSchedulerControl } from 'super-scheduler'
import type { SchedulerProps } from 'super-scheduler'
export interface BlockRequest {
readonly resource: SuperScheduler.ResourceId
/** ISO wall-clock values; `end` is exclusive. */
readonly start: string
readonly end: string
}
export function RangeBlocker(props: {
resources: SuperScheduler.ResourceData[]
events: SuperScheduler.EventData[]
onBlock: (requests: BlockRequest[]) => void
}) {
const { controlRef, control } = useSchedulerControl()
const [ranges, setRanges] = useState<readonly SuperScheduler.SchedulerRange[]>([])
const events = useMemo(() => props.events.slice(), [props.events])
const config = useMemo<SchedulerProps>(
() => ({
// Ctrl/Cmd + drag adds a range instead of replacing the current one.
allowMultiRange: true,
multiRangeMode: 'CtrlOrMeta',
onTimeRangeSelecting: (args) => {
// Past days cannot be blocked: the shadow turns red and the drop is refused.
args.allowed = args.start.getTime() >= SuperScheduler.Date.today().getTime()
},
onTimeRangeSelected: (args) => {
// A plain click on an empty cell also selects it (origin 'click').
setRanges([...args.multirange])
},
}),
[],
)
const block = () => {
props.onBlock(
ranges.map((range) => ({
resource: range.resource,
start: range.start.value,
end: range.end.value,
})),
)
control?.clearSelection()
setRanges([])
}
return (
<>
<button type="button" disabled={ranges.length === 0} onClick={block}>
Block {ranges.length} selected {ranges.length === 1 ? 'range' : 'ranges'}
</button>
<SuperSchedulerComponent
{...config}
controlRef={controlRef}
startDate="2026-10-05"
days={14}
scale="Day"
resources={props.resources}
events={events}
/>
</>
)
}Faites glisser sur des jours libres d’une chambre, puis d’une autre en maintenant Ctrl (Cmd sur macOS) : le bouton indique « Block 2 selected ranges ». Les plages parviennent à onBlock sous forme de valeurs ISO en heure locale avec fins exclusives, prêtes à être envoyées à votre backend.
Ce que possède votre application
La bibliothèque conserve arborescences, filtres et sélections dans le navigateur, pour la session en cours. Votre application décide :
- d’où vient l’arborescence : les lignes sont les
resourcesque vous passez, et le contrôle ne demande jamais d’enfants de lui-même (onLoadNodeest réservé) ; - ce que signifie un filtre quand les données ne sont pas chargées, et comment la recherche atteint le backend ;
- ce que fait une sélection : actions groupées, permissions pour les exécuter et persistance ;
- si les lignes dépliées et la disposition des colonnes survivent à un rechargement (voir vues enregistrées).
Les sélections ne sont enregistrées nulle part. L’état déplié vit dans les objets ressources que vous avez passés, jusqu’à ce que vous les remplaciez.
Voir aussi
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. Planification de salles de formationLes inscriptions dépassent la salle. Sélectionnez les deux sessions, voyez ce qui est libre pour les deux, déplacez-les ensemble et gardez la vue. Répartition des interventions techniquesUne intervention urgente arrive. Trouvez l’équipe qui peut la prendre avant l’échéance.
- Clavier, accessibilité et tactile liste les touches de sélection.
- Événements contrôlés et callbacks explique comment les événements sélectionnés parviennent à votre state.
- Volets coordonnés et vues enregistrées conserve les lignes repliées et les largeurs de colonnes.
Exemples liés
- 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.
- MorrowPlanification de salles de formationLes inscriptions dépassent la salle. Sélectionnez les deux sessions, voyez ce qui est libre pour les deux, déplacez-les ensemble et gardez la vue.
- FieldworkRépartition des interventions techniquesUne intervention urgente arrive. Trouvez l’équipe qui peut la prendre avant l’échéance.
- FramehousePlanification de production audiovisuelleUn tournage déborde. Déplacez le montage qui en dépendait, comprenez pourquoi, puis revenez en arrière.