Aller au contenu
SuperScheduler

Concepts clésS’applique àLite et Pro

Ressources, événements et intervalles

Les lignes sont des objets ResourceData avec un id et un name ; les barres sont des objets EventData avec id, text, start, end et l’id de la ressource à laquelle elles appartiennent. Les ids sont des chaînes ou des nombres comparés strictement : 1 et '1' sont différents. Les intervalles sont semi-ouverts, [start, end), et les dates sont des valeurs civiles en heure locale, écrites comme chaînes ISO avec secondes ; la bibliothèque ne convertit jamais les fuseaux horaires. Ajoutez vos propres champs avec EventData<VosChamps> et affinez leur type quand ils reviennent du contrôle.

Vérifié avec la v0.1.0 · relu le 7 octobre 2026.md

SuperScheduler dessine deux tableaux : les ressources, qui sont les lignes, et les événements, qui sont les barres sur ces lignes. Ce sont des objets simples que vous créez à partir de vos propres données. Respecter quatre règles évite presque tous les problèmes du type « mon événement n’apparaît pas » : les ids sont strictement typés, les intervalles excluent leur fin, les chaînes de date incluent les secondes, et les heures sont des heures locales sans fuseau horaire.

Les règles de cette page s’appliquent aux deux éditions, sauf mention contraire dans une section. Lite accepte un sous-ensemble des champs ; voir Démarrage rapide avec Lite.

Ressources

Une ressource a besoin d’un id et d’un name. Dans Pro, ResourceData est ouvert : vous pouvez garder vos propres champs (floor, kind, capacity) sur le même objet et les relire dans les callbacks et les hooks de rendu.

Les champs que vous utiliserez le plus dans Pro :

ChampRôle
idChaîne ou nombre ; unique parmi les ressources
nameTexte de l’en-tête de ligne
backColor, cssClass, html, toolTipApparence de l’en-tête de ligne (html est du balisage de confiance)
minHeight, eventHeightGéométrie de cette ligne uniquement
cellsDisabledToutes les cellules de la ligne refusent les dépôts et les sélections
columnsCellules des colonnes supplémentaires d’en-tête de ligne (avec rowHeaderColumns)
children, expandedUne arborescence de ressources (avec treeEnabled)
frozen'top' ou 'bottom' : la ligne reste visible pendant le défilement

Arborescences de ressources

Pour grouper des lignes, imbriquez des ressources dans children et activez treeEnabled sur le planificateur. Sans treeEnabled, les enfants sont ignorés et la liste reste plate. Un parent démarre replié, sauf si son champ expanded vaut true. Les parents peuvent porter des événements comme n’importe quelle ligne ; activez treePreventParentUsage pour en faire de simples en-têtes de groupe. Les arborescences, les colonnes de ligne et la sélection de lignes sont traitées dans Arborescences, colonnes et sélection. Lite n’accepte que des listes plates.

Événements

Un événement a besoin de id, text, start, end et, pour apparaître sur une ligne, de resource. Des champs facultatifs modifient son apparence et son comportement :

ChampRôle
backColor, fontColor, borderColor, barColorCouleurs de la barre, de son texte, de sa bordure et de sa barre de durée
cssClassClasses pour votre propre CSS
htmlContenu en HTML de confiance (échappez les données utilisateur avec SuperScheduler.Util.escapeHtml)
toolTip, bubbleHtmlInfobulle native, ou contenu de la bulle de survol
moveDisabled, resizeDisabledVerrouille cet événement contre le déplacement ou le redimensionnement
moveHDisabled, moveVDisabledAutorise le déplacement uniquement entre lignes, ou uniquement dans le temps
clickDisabled, deleteDisabledExclut cet événement des clics ou de la suppression
tagsToute valeur pour votre propre usage

Tous les champs de ce tableau sont réservés à Pro, sauf backColor, fontColor, cssClass, toolTip et tags, que Lite accepte aussi.

Les ids sont des chaînes ou des nombres, comparés strictement

ResourceId et EventId sont de type string | number, et les comparaisons portent sur la valeur et sur son type. Le nombre 101 et la chaîne '101' sont des ids différents. Un événement avec resource: '101' n’est pas dessiné sur une ligne dont l’id est 101, et control.events.find('7') ne trouve pas l’événement d’id 7.

C’est surtout important quand les données viennent de plusieurs sources : un driver de base de données peut renvoyer des ids de chambre numériques alors qu’un formulaire ou une URL fournit des chaînes. Normalisez les ids à la frontière où les données entrent dans votre application, et gardez un seul type par sorte d’id.

Les intervalles sont semi-ouverts

Un événement occupe [start, end) : l’instant de début lui appartient, l’instant de fin non. Trois conséquences :

  • Des événements bout à bout ne se chevauchent pas. Un séjour qui se termine à 11:00 et le suivant qui commence à 11:00 dans la même chambre sont compatibles, y compris quand les chevauchements sont refusés.
  • Une fin sans heure désigne le premier jour libre. start: '2026-10-02', end: '2026-10-05' couvre les 2, 3 et 4 octobre. Pour afficher aussi le 5, la fin est '2026-10-06'.
  • Les durées sont de simples différences. end - start est la durée, sans ajustement « plus un jour ».

Votre backend devrait appliquer la même règle. Deux intervalles se chevauchent quand a.start < b.end && b.start < a.end ; une requête par plage de dates pour ce qui est visible entre from et to s’écrit start < to AND end > from.

Dans Pro, eventEndSpec: 'Date' bascule vers des fins inclusives sans heure, pour les plannings à la journée : un événement qui se termine le '2026-10-05' couvre alors le 5. La bibliothèque convertit la valeur en interne et la restitue dans la même convention. Utilisez une seule convention par planificateur.

Chaînes de date

start, end et toutes les options de date acceptent une SuperScheduler.Date ou une chaîne ISO 8601 :

EntréeAcceptéeInterprétée comme
'2026-10-02'ouiMinuit au début du 2 octobre
'2026-10-02T14:00:00'oui14:00
'2026-10-02 14:00:00'oui14:00 (une espace au lieu de T)
'2026-10-02T14:00:00.250'ouiAvec millisecondes
'2026-10-02T14:00'non, lève une erreurLes secondes sont obligatoires
'2026-10-02T14:00:00+02:00'oui, avec prudence12:00 : un décalage convertit la valeur en heure UTC
new Date()nonUne Date native n’est pas acceptée par le typage de start ou end

Deux règles évitent la plupart des surprises : incluez toujours les secondes, et n’envoyez ni décalage ni Z, sauf si vous voulez l’heure UTC. Formatez les valeurs en yyyy-MM-ddTHH:mm:ss dans le fuseau horaire du lieu planifié.

Heure civile, sans fuseau horaire

SuperScheduler travaille avec des dates et heures civiles (l’heure affichée par l’horloge murale) : 2026-10-25T02:30:00 signifie « deux heures et demie le 25 », exactement comme c’est écrit, sans fuseau horaire et sans changement d’heure. Le planificateur affiche ce que vous lui donnez et restitue les valeurs sous la même forme.

C’est ce dont a besoin un planning : un hôtel à Madrid affiche l’arrivée à 14:00 heure locale pour chaque utilisateur, où que se trouve son navigateur. Cela signifie aussi que les conversions vous reviennent :

  • Si votre backend stocke des instants (horodatages UTC), convertissez-les en heure locale du lieu de la ressource avant de les transmettre, puis en instants au moment d’enregistrer.
  • Si des ressources se trouvent dans des fuseaux différents, décidez quelle heure locale la vue affiche ; le planificateur n’a qu’un seul axe du temps.
  • Les événements récurrents (chaque lundi à 9:00) doivent arriver au planificateur sous forme d’occurrences concrètes, développées par votre application.

Champs personnalisés avec EventData<T>

Vos événements portent généralement plus qu’un libellé : un code client, un statut, un prix. Dans Pro, EventData n’a pas de signature d’index : un littéral d’objet avec des propriétés supplémentaires échoue donc à la vérification des propriétés en excès de TypeScript. Déclarez vos champs une fois et utilisez le générique, SuperScheduler.EventData<YourFields> :

src/bookings.tsts
import type { SuperScheduler } from 'super-scheduler'

/** Fields your application adds to every booking. */
export interface BookingFields {
  guestCode: string
  status: 'tentative' | 'confirmed' | 'checkedIn'
  adults: number
}

export type BookingEvent = SuperScheduler.EventData<BookingFields>

// ResourceData accepts extra properties: keep your own row fields next to id and name.
export const rooms: SuperScheduler.ResourceData[] = [
  { id: 101, name: 'Room 101', floor: 1, kind: 'double' },
  { id: 102, name: 'Room 102', floor: 1, kind: 'suite' },
]

// EventData has no index signature: type the array so literals may carry custom fields.
export const bookings: BookingEvent[] = [
  {
    id: 'bk-1042',
    resource: 101, // the same type as the room id: 101 and '101' are different ids
    start: '2026-10-02T14:00:00',
    end: '2026-10-05T11:00:00',
    text: 'Booking 1042',
    guestCode: 'G-1042',
    status: 'confirmed',
    adults: 2,
  },
]

const STATUSES: ReadonlySet<string> = new Set(['tentative', 'confirmed', 'checkedIn'])

/**
 * Objects that come back from the control (handler arguments, onEventsChange) are typed as plain
 * EventData. Narrow them instead of casting, so a malformed object is caught where it appears.
 */
export function isBooking(data: SuperScheduler.EventData): data is BookingEvent {
  return (
    'guestCode' in data &&
    typeof data.guestCode === 'string' &&
    'status' in data &&
    typeof data.status === 'string' &&
    STATUSES.has(data.status) &&
    'adults' in data &&
    typeof data.adults === 'number'
  )
}

Les données qui reviennent du contrôle sont typées comme un simple EventData : arguments des handlers, onEventsChange, control.events.list. La bibliothèque conserve vos champs, mais TypeScript ne peut pas savoir qu’ils sont là. Affinez le type avec une garde comme isBooking plutôt qu’avec un cast ; une garde détecte aussi les objets que votre propre code a mal construits. Les hooks de rendu reçoivent une copie ouverte (args.data dans onBeforeEventRender) : vous pouvez y lire les champs directement, mais une garde garde les types exacts :

src/TypedPlanning.tsxtsx
import { useMemo } from 'react'
import { SuperScheduler, SuperSchedulerComponent } from 'super-scheduler'
import type { SchedulerProps } from 'super-scheduler'
import { bookings, isBooking, rooms } from './typed-data'

export function TypedPlanning({ onOpen }: { onOpen: (guestCode: string) => void }) {
  const owned = useMemo(() => bookings.slice(), [])

  const config = useMemo<SchedulerProps>(
    () => ({
      startDate: '2026-10-01',
      days: 31,
      scale: 'Day',
      onBeforeEventRender: (args) => {
        // args.data is a per-render copy: start and end are always SuperScheduler.Date here.
        if (!isBooking(args.data)) return
        const nights = Math.round(
          (args.data.end.getTime() - args.data.start.getTime()) / 86_400_000,
        )
        // html is trusted markup: escape every value that came from users.
        const code = SuperScheduler.Util.escapeHtml(args.data.guestCode)
        args.data.html = `${code} · ${nights} night${nights === 1 ? '' : 's'}`
        args.data.cssClass = `booking booking--${args.data.status}`
      },
      onEventClick: (args) => {
        // args.e is a wrapper: id(), start(), text() are methods; data is the raw object.
        const data = args.e.data
        if (isBooking(data)) onOpen(data.guestCode)
      },
    }),
    [onOpen],
  )

  return <SuperSchedulerComponent {...config} resources={rooms} events={owned} />
}

Chaque réservation devrait afficher son code client et son nombre de nuits, et onOpen devrait être appelé avec le code client quand vous cliquez dessus.

Dans Lite, EventData n’est pas générique et n’accepte aucun champ supplémentaire en TypeScript : gardez les données de l’application dans tags, que onEventClick vous restitue dans e.data.tags.

Valeurs après un glisser ou un redimensionnement

Quand un utilisateur déplace ou redimensionne un événement dans Pro, la bibliothèque ne modifie pas votre objet. Elle le remplace par un nouvel objet, { ...old, start, end, resource }, dans lequel start et end sont des instances de SuperScheduler.Date. Les événements que personne n’a touchés conservent les chaînes que vous avez passées. Le code qui lit les événements doit donc accepter les deux formes :

src/event-dates.tsts
import { SuperScheduler } from 'super-scheduler'

/**
 * After a drag or a resize, the committed event holds SuperScheduler.Date values in start and end;
 * events nobody touched keep the strings you passed. Normalize both to one canonical string.
 */
export function toIso(value: SuperScheduler.DateInput): string {
  // `value` is `yyyy-MM-ddTHH:mm:ss` (plus `.fff` when milliseconds are not zero).
  return typeof value === 'string' ? new SuperScheduler.Date(value).value : value.value
}

/** The part of an event your backend stores. */
export function toSavePayload(event: SuperScheduler.EventData) {
  if (event.resource === undefined) throw new Error(`Event ${String(event.id)} has no resource`)
  return {
    id: String(event.id),
    resource: String(event.resource),
    start: toIso(event.start),
    end: toIso(event.end),
  }
}

/** Half-open overlap, the rule the scheduler applies: touching intervals do not overlap. */
export function overlaps(a: SuperScheduler.EventData, b: SuperScheduler.EventData): boolean {
  const date = (value: SuperScheduler.DateInput) => new SuperScheduler.Date(value)
  return SuperScheduler.Util.overlaps(date(a.start), date(a.end), date(b.start), date(b.end))
}

Quelques détails utiles pour enregistrer ou comparer ces valeurs :

  • JSON.stringify écrit une SuperScheduler.Date au format yyyy-MM-ddTHH:mm:ss : un objet événement entier se sérialise donc en chaînes ISO valides.
  • String(date) et date.value donnent le même texte ; date.toString('d MMM HH:mm', 'en-us') formate selon un motif et une locale.
  • Comparez les dates avec a.equals(b) ou via getTime(). === compare l’identité des objets.
  • getDay() renvoie le jour du mois (1 à 31), contrairement à la Date native. Le jour de la semaine s’obtient avec getDayOfWeek() (0 correspond au dimanche) ou dayOfWeekISO() (1 correspond au lundi).
  • Testez le type avec value instanceof SuperScheduler.Date, jamais avec le nom du constructeur. Dans Lite, utilisez la SuperScheduler.Date propre à Lite ; quand les deux éditions sont installées, échangez des chaînes ISO entre elles.

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. Réservation d’instruments de laboratoireRéservez un instrument, l’étalonnage est compris. Sortez une séance d’une visite de maintenance, puis allongez votre mesure.

Étapes suivantes