# Ressources, événements et intervalles

> Le modèle de données : ids de ressources et d’événements, intervalles semi-ouverts, dates ISO avec secondes, heure civile sans fuseau et champs typés EventData<T>.

Source: https://superscheduler.org/fr/docs/resources-events-intervals/
Reviewed: 2026-10-07

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.

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](https://superscheduler.org/fr/docs/quick-start-lite/#event-fields).

## 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 :

| Champ | Rôle |
|---|---|
| `id` | Chaîne ou nombre ; unique parmi les ressources |
| `name` | Texte de l’en-tête de ligne |
| `backColor`, `cssClass`, `html`, `toolTip` | Apparence de l’en-tête de ligne (`html` est du balisage de confiance) |
| `minHeight`, `eventHeight` | Géométrie de cette ligne uniquement |
| `cellsDisabled` | Toutes les cellules de la ligne refusent les dépôts et les sélections |
| `columns` | Cellules des colonnes supplémentaires d’en-tête de ligne (avec `rowHeaderColumns`) |
| `children`, `expanded` | Une 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](https://superscheduler.org/fr/docs/trees-columns-selection/). 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 :

| Champ | Rôle |
|---|---|
| `backColor`, `fontColor`, `borderColor`, `barColor` | Couleurs de la barre, de son texte, de sa bordure et de sa barre de durée |
| `cssClass` | Classes pour votre propre CSS |
| `html` | Contenu en HTML de confiance (échappez les données utilisateur avec `SuperScheduler.Util.escapeHtml`) |
| `toolTip`, `bubbleHtml` | Infobulle native, ou contenu de la bulle de survol |
| `moveDisabled`, `resizeDisabled` | Verrouille cet événement contre le déplacement ou le redimensionnement |
| `moveHDisabled`, `moveVDisabled` | Autorise le déplacement uniquement entre lignes, ou uniquement dans le temps |
| `clickDisabled`, `deleteDisabled` | Exclut cet événement des clics ou de la suppression |
| `tags` | Toute 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.

> **Behavior:**
> Dans Pro, `control.events.add()` lève une `SuperScheduler.Exception` quand l’id existe déjà, et `control.events.update()` avec un id inconnu ne fait rien (il n’ajoute pas). Utilisez `add` pour les nouveaux événements et `update` pour les existants.

## 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ée | Acceptée | Interprétée comme |
|---|---|---|
| `'2026-10-02'` | oui | Minuit au début du 2 octobre |
| `'2026-10-02T14:00:00'` | oui | 14:00 |
| `'2026-10-02 14:00:00'` | oui | 14:00 (une espace au lieu de `T`) |
| `'2026-10-02T14:00:00.250'` | oui | Avec millisecondes |
| `'2026-10-02T14:00'` | **non, lève une erreur** | Les secondes sont obligatoires |
| `'2026-10-02T14:00:00+02:00'` | oui, avec prudence | 12:00 : un décalage convertit la valeur en heure UTC |
| `new Date()` | non | Une `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.

> **Limitation:**
> La bibliothèque ne convertit pas les fuseaux horaires, n’applique pas les règles de changement d’heure et ne développe pas les règles de récurrence. Voir [Langues, dates et fuseaux horaires](https://superscheduler.org/fr/docs/locales-dates-timezones/) pour les stratégies possibles.

## Champs personnalisés avec EventData&lt;T&gt;
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>` :

```ts
// src/bookings.ts
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 :

```tsx
// src/TypedPlanning.tsx
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 :

```ts
// src/event-dates.ts
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.

→ https://superscheduler.org/fr/examples/fleet-rentals/
→ https://superscheduler.org/fr/examples/lab-instruments/
## Étapes suivantes
- Gardez les événements dans le state React et enregistrez les modifications : [Événements contrôlés et callbacks](https://superscheduler.org/fr/docs/controlled-state/).
- Affichez des heures et des minutes au lieu des jours : [Heures, minutes, jours et zoom](https://superscheduler.org/fr/docs/time-scales-zoom/).
- Personnalisez les barres avec vos champs : [Slots de rendu React](https://superscheduler.org/fr/docs/react-render-slots/) et [Thèmes](https://superscheduler.org/fr/docs/theming/).
