# React Resource Scheduler: Räume und Autos per Drag buchen

> So baust du einen React Resource Scheduler für Räume, Autos und Personen: per Drag buchen und verschieben, Doppelbuchungen in Postgres sperren, Agenda am Handy.

- Autor: [Serban Rusu](https://wingo-ui.com/blog/authors/serban), Founder of Wingo UI
- Veröffentlicht: 16. Okt. 2026
- Kategorie: [Komponenten-Guides](https://wingo-ui.com/de/blog/category/components)
- Lesezeit: 10 Min.
- Kanonische URL: https://wingo-ui.com/de/blog/react-resource-scheduler
- Englische Fassung: https://wingo-ui.com/blog/react-resource-scheduler

## Kurz gesagt

Ein React Resource Scheduler zeigt die Dinge, die du buchst (Räume, Autos, Personen), als Zeilen und die Zeit als Spalten: Ein freier Slot ist eine sichtbare Lücke, über die du ziehst, um zu buchen, und eine Buchung ist ein Balken, den du verschiebst oder in die Länge ziehst. Er muss Überschneidungen und gesperrte Zeit schon beim Ziehen ablehnen, jede Buchung auf dem Server noch einmal prüfen und auf dem Handy, wo ein Raster über 12 Stunden nicht passt, zu einer Agenda mit einem Buchen-Button werden. Der Scheduler von Wingo UI erledigt das in einer Komponente, die du mit resources, events und ein paar Callbacks fütterst.

Ein React Resource Scheduler ist der Screen hinter Raumbuchung, Fuhrparkplanung und Schichtplänen: Ressourcen an der Seite, Stunden oben, Buchungen als Balken, die du ziehen kannst. Er wirkt einfach, bis jemand ein Meeting auf einen belegten Raum zieht, ein Auto während seiner Wartung bucht oder zwei Leute gleichzeitig den Hudson-Raum um 9 Uhr nehmen. Auf dem Handy wird das Raster über 12 Stunden dann zu einem Streifen, den niemand antippen kann. Dieser Artikel zeigt, was eine Resource Timeline braucht, welche Props im [Scheduler](https://wingo-ui.com/components/scheduler) von Wingo UI zählen, wie du Buchungen auf dem Server absicherst und was du auf dem Handy zeigst.

## Was ist ein Resource Scheduler, und was unterscheidet ihn von einem Kalender?

Ein Kalender beantwortet „Was steht heute bei mir an?“. Ein Resource Scheduler beantwortet „Welcher Raum, welches Auto oder welche Person ist um 14 Uhr frei?“. Das ändert das Layout:

- **Zeilen sind Ressourcen.** Jeder Besprechungsraum, jedes Fahrzeug und jedes Teammitglied bekommt eine Spur. Spuren lassen sich gruppieren (Räume, Team, Autos) und einklappen.
- **Spalten sind Zeitslots.** Ein Tag in Slots von 15, 30 oder 60 Minuten oder eine Woche in Tagen.
- **Verfügbarkeit gehört zur Zeile.** Zeit außerhalb der Arbeitszeit ist schattiert, in Wartung oder Urlaub sind keine Buchungen möglich.
- **Eine Buchung gehört zu genau einer Ressource.** Ziehst du sie in eine andere Zeile, weist du sie neu zu.

Jedes Raumbuchungssystem braucht diese vier Punkte. Wenn deine Nutzer vor allem ihre eigenen Termine anschauen, passt ein Kalender mit Monats- und Wochenansicht wie [Calendar View](https://wingo-ui.com/components/calendar-view) besser. Für die Verfügbarkeit vieler Dinge brauchst du eine React Resource Timeline.

## Welche React Resource Scheduler gibt es 2026?

Die wichtigsten Optionen sind FullCalendar Premium, Bryntum Scheduler, Syncfusion, das Open-Source-Paket react-big-schedule und der Scheduler von Wingo UI. Das steht Stand Oktober 2026 auf ihren eigenen Seiten:

| Option | Resource Timeline | Preis und Lizenz | Quelle |
|---|---|---|---|
| FullCalendar | Timeline View und Vertical Resource View sind Premium | Standard kostenlos, Premium ab $480 (1 bis 10 Entwickler) | [Preise](https://fullcalendar.io/pricing) |
| Bryntum Scheduler | ja, das ist das Produkt | ab $680 pro Entwickler (Preis bei 3 oder mehr Lizenzen) | [Shop](https://www.bryntum.com/store/) |
| Syncfusion React Scheduler | Timeline- und Ressourcenansichten | kommerziell; kostenlose Community-Lizenz bei unter $1M Jahresumsatz, höchstens 5 Entwicklern, höchstens 10 Beschäftigten und nicht mehr als $3M externem Kapital | [Community-Lizenz](https://www.syncfusion.com/products/communitylicense) |
| react-big-schedule | Ressourcenzeilen, gebaut auf Ant Design | Open Source, MIT | [README](https://cdn.jsdelivr.net/npm/react-big-schedule@5.5.1/README.md) |
| Wingo UI Scheduler | Tages- und Wochen-Timeline plus Agenda fürs Handy | Pro, der Quellcode wird in dein Projekt kopiert | [Doku](https://wingo-ui.com/components/scheduler) |

Nimm Bryntum oder Syncfusion, wenn ein großes Team eine komplette Planungssuite mit Support vom Hersteller braucht, FullCalendar Premium, wenn du FullCalendar schon für Monatsansichten nutzt, und react-big-schedule, wenn deine App auf Ant Design läuft. Der Scheduler von Wingo UI ist für eine Tailwind-App gedacht, in der die Timeline zum Rest der Oberfläche passen, auf dem Handy funktionieren und als bearbeitbarer Quellcode im Repository liegen soll.

## Wie sieht eine Resource Timeline in der Praxis aus?

Sie ist ein einziges Raster, in dem die Tage aller Ressourcen nebeneinander liegen. Ein freier Slot ist also eine Lücke, die du sofort siehst. Die Demo zeigt neun Ressourcen in drei Gruppen an einem Donnerstag im September. Die rote Linie markiert die aktuelle Uhrzeit, der Balken unter jedem Namen zeigt, wie viel seiner Arbeitszeit gebucht ist, und der Potomac-Raum ist von 14 bis 16 Uhr in Wartung.

> Live-Demo (Scheduler): Zieh über freie Zeit in einer Zeile, um sie zu buchen, zieh eine Buchung in einen anderen Raum oder auf ein anderes Auto, oder zieh an ihrer Kante. Legst du sie während der Wartung auf den Potomac-Raum, wird das abgelehnt, und der Balken federt zurück. Probier sie unter [Scheduler](https://wingo-ui.com/components/scheduler) aus und installier sie mit `npx wingo-ui@latest add scheduler`.

Vorläufige Buchungen sind gestrichelt. Stornierte sind durchgestrichen und blockieren nie einen Slot.

## Wie richtest du Ressourcen und Buchungen ein?

Du übergibst zwei Arrays, `resources` für die Zeilen und `events` für die Buchungen, dazu Callbacks, die speichern, was deine Nutzer ändern. Installier ihn mit `npx wingo-ui@latest add scheduler`. Eine Ressource ist `{ id, name }` plus optional `avatar`, `icon`, `description`, `group`, `capacity`, `tone`, `color`, `unavailable` und `workingHours`. Eine Buchung ist ein Event aus Calendar View (`id`, `title`, `start`, `end` und optionale Extras) plus `resourceId` und optional `status`.

```tsx
"use client"

import { useState } from "react"
import { Car, Presentation } from "lucide-react"
import { Scheduler, type SchedulerEvent, type SchedulerResource } from "@/components/ui/scheduler"

const resources: SchedulerResource[] = [
  {
    id: "hudson",
    name: "Raum Hudson",
    description: "8 Plätze, Bildschirm",
    group: "Räume",
    capacity: 8,
    icon: <Presentation />,
    tone: "info",
    unavailable: [{ start: new Date(2026, 9, 16, 14), end: new Date(2026, 9, 16, 16), label: "Wartung" }],
  },
  { id: "emma", name: "Emma Carter", description: "Vertrieb", group: "Team", workingHours: { start: 9, end: 17 } },
  { id: "corolla", name: "Toyota Corolla", description: "NY 12 ACM", group: "Autos", icon: <Car /> },
]

async function send(url: string, method: "POST" | "PATCH", body: unknown) {
  const res = await fetch(url, {
    method,
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(body),
  })
  if (!res.ok) throw new Error(await res.text())
  return res.json()
}

export function RoomBookings({ initial }: { initial: SchedulerEvent[] }) {
  const [events, setEvents] = useState(initial)

  return (
    <Scheduler
      resources={resources}
      events={events}
      slotMinutes={15}
      dayStartHour={7}
      dayEndHour={19}
      onEventChange={async (event, change) => {
        await send(`/api/bookings/${event.id}`, "PATCH", change)
        setEvents((list) => list.map((e) => (e.id === event.id ? { ...e, ...change } : e)))
      }}
      onCreateEvent={async (input) => {
        const { id } = await send("/api/bookings", "POST", input)
        setEvents((list) => [...list, { id, ...input }])
      }}
      onEventCancel={async (event) => {
        await send(`/api/bookings/${event.id}`, "PATCH", { status: "cancelled" })
        setEvents((list) => list.map((e) => (e.id === event.id ? { ...e, status: "cancelled" } : e)))
      }}
    />
  )
}
```

Die Props, die das Raster formen, sind `slotMinutes` (darauf rastet alles ein), `dayStartHour` und `dayEndHour` (standardmäßig 8 und 20), `slotWidth`, `rowHeight` und `resourceWidth` in Pixeln sowie `height` (640px). Ab dieser Höhe scrollt das Raster, Zeitleiste und Namen bleiben dabei fixiert.

## Wie funktionieren Anlegen, Verschieben und Größenänderung per Drag?

React Scheduler Drag and Drop läuft auf drei Gesten hinaus:

1. **Über freie Slots ziehen, um zu buchen.** Der Bereich rastet auf die Slots ein, stoppt an der nächsten Buchung und öffnet darunter ein Schnellformular, in dem der Titel schon fokussiert ist. Enter speichert, Escape bricht ab. Speichern ruft `onCreateEvent` mit `{ start, end, resourceId, title }` auf.
2. **Eine Buchung ziehen, um sie zu verschieben.** Sie folgt dem Zeiger zu einer anderen Zeit oder in eine andere Zeile. Eine Vorschau zeigt, wo sie landet, und wird rot, wenn die Zeit belegt ist. Beim Loslassen rastet sie in den Slot ein.
3. **Eine Kante ziehen, um die Größe zu ändern.** Die Buchung stoppt an ihren Nachbarn, statt über sie hinwegzulaufen.

Das Raster scrollt von selbst, wenn du in die Nähe seiner Ränder kommst, und Escape bricht jedes Ziehen ab. Verschieben und Größenänderung rufen `onEventChange(event, change)` mit `{ start, end, resourceId }` auf. Die Buchung bewegt sich sofort; gibst du `false` oder ein abgelehntes Promise zurück, springt sie zurück, und der Fehler wird angesagt.

Wenn du schon ein eigenes Buchungsformular hast, übergib `onSlotSelect` statt `onCreateEvent`: Ein Ziehen, ein Klick oder Enter auf einem freien Slot gibt dir `{ start, end, resourceId }`. `onEventClick` ersetzt auf dieselbe Weise das Popover mit den Details.

## Wie verhinderst du Doppelbuchungen?

Lehne Überschneidungen in der Oberfläche ab und mach sie in der Datenbank unmöglich. In der Oberfläche ist `allowOverlap` standardmäßig `false`. Ein Drop, eine Größenänderung, eine Auswahl oder ein gespeichertes Formular, das sich mit einer anderen Buchung derselben Ressource überschneidet, wird abgelehnt. Die Buchung wackelt und federt zurück, und Screenreader hören den Standardtext „Hudson room is already booked then“. Gesperrte Zeit lehnt Buchungen auch dann ab, wenn `allowOverlap` an ist. Schalte es für Personen ein, die doppelt gebucht werden dürfen; überlappende Balken stapeln sich dann in Spuren, und die Zeile wächst.

Die Prüfung in der Oberfläche reicht nicht: Zwei Personen können sich denselben freien Slot in derselben Sekunde schnappen. Die Datei exportiert `findConflicts(events, { start, end, resourceId, id? })`. Die Funktion gibt die Buchungen dieser Ressource zurück, die sich mit dem Kandidaten überschneiden, und überspringt stornierte. Nutz sie in deinen eigenen Formularen auf dem Client und übergib beim Prüfen einer Änderung die `id` der Buchung, sonst kollidiert die Buchung mit sich selbst:

```ts
import { findConflicts, type SchedulerEvent } from "@/components/ui/scheduler"

export function clashMessage(
  events: SchedulerEvent[],
  candidate: { start: Date; end: Date; resourceId: string; id?: string },
) {
  const [first] = findConflicts(events, candidate)
  return first ? `Diese Zeit ist schon durch ${first.title} belegt` : null
}
```

`scheduler.tsx` ist ein Client-Modul (`"use client"`), also importier es nicht in eine Server Action oder einen Route Handler. Wiederhol dieselbe Regel stattdessen in der Datenbank. In Postgres macht ein Exclusion Constraint die Überschneidung unmöglich, egal was der Client schickt:

```sql
create extension if not exists btree_gist;

alter table bookings add constraint no_overlap
  exclude using gist (resource_id with =, tstzrange(start_at, end_at) with &&)
  where (status <> 'cancelled');
```

Ein Schreibvorgang, der den Constraint verletzt, scheitert mit `exclusion_violation` (SQLSTATE `23P01`). Fang diesen Code ab und antworte mit einem 409. `send` wirft bei jedem Status außerhalb von 200 bis 299. Eine verschobene Buchung federt dann zurück, und das Schnellformular zeigt „Could not save“ plus den Titel (das Label `failed`), egal welchen Text der Server geschickt hat.

## Wie sollte ein React Buchungskalender auf dem Handy funktionieren?

Auf dem Handy sollte er eine Liste der Buchungen des Tages mit einem Button zum Buchen sein, kein Raster. Ein Tag von 8 bis 20 Uhr in Slots von 30 Minuten ergibt 24 Spalten zu je 48px, also etwa 1.150px Raster, die du auf einem 390px breiten Handy seitlich scrollen müsstest. Deshalb wird der Scheduler zu einer Agenda, sobald seine eigene Breite unter 576px fällt. Das ist eine Container Query: Auch ein schmales Seitenpanel bekommt die Agenda, und schon das HTML vom Server ist richtig. Die Agenda zeigt:

- einen Streifen mit den Tagen der Woche, mit Punkten an den belegten Tagen,
- die Buchungen des Tages, gruppiert nach Ressource, mit ihren Zeiten,
- die Ressourcen, die den ganzen Tag frei sind, als Chips, mit denen du sie buchst,
- einen Buchen-Button über die volle Breite, der den nächsten freien Slot vorschlägt.

Buchungen und das Buchungsformular öffnen sich als Bottom Sheets. Probier es im erzwungenen Handy-Layout aus:

> Live-Demo (Scheduler): Tipp auf einen Tag im Streifen, tipp auf eine Buchung, um ihr Sheet zu öffnen, oder drück Book, um eine Buchung im nächsten freien Slot zu starten. Probier sie unter [Scheduler](https://wingo-ui.com/components/scheduler) aus und installier sie mit `npx wingo-ui@latest add scheduler`.

Für Tablets im Hochformat behält `mobileView="day"` das Tagesraster, das seitlich scrollt, während die Namen fixiert bleiben. Per Touch öffnet ein Tipp auf einen freien Slot das Buchungs-Sheet, und langes Drücken nimmt eine Buchung zum Verschieben auf, damit normales Wischen weiter scrollt. Unser Artikel über [dnd kit auf Mobilgeräten](https://wingo-ui.com/de/blog/dnd-kit-mobile-touch-drag) erklärt denselben Kompromiss in einer sortierbaren Liste. Spuren sind auf Touchscreens mindestens 56px hoch, damit jede Buchung eine Touch-Fläche von 44px hat, und `layout="mobile"` oder `"desktop"` erzwingt eines der beiden Layouts. Warum Sheets auf dem Handy besser funktionieren als Popovers, liest du in [Bottom Sheets fürs Web](https://wingo-ui.com/de/blog/react-bottom-sheet-for-the-web).

## Wie machst du einen Resource Scheduler per Tastatur bedienbar?

Mach das ganze Raster zu einem einzigen Tab-Stopp und navigier darin mit den Pfeiltasten, damit niemand mit der Tastatur durch Hunderte Slots tabben muss. Im Scheduler von Wingo UI wandern Links und Rechts durch die freien Slots und Buchungen einer Zeile, Hoch und Runter zwischen den Ressourcen, Bild auf und Bild ab um einen Tag oder eine Woche. Umschalt mit den Pfeiltasten wählt Slots aus, und Enter bucht sie; Alt mit den Pfeiltasten verschiebt die fokussierte Buchung, und Alt mit Umschalt ändert ihre Größe. Verschieben, Größenänderungen, neue Buchungen und abgelehnte Drops werden über eine Live Region mit `aria-live="polite"` angesagt. Mit reduzierter Bewegung passieren Einrasten und Wackeln ohne Animation.

## Was geht kaputt, wenn ein Resource Scheduler in Produktion geht?

Zeitzonen, die aktuelle Uhrzeit und Daten, die nicht zu den Zeilen passen, verursachen die meisten Fehler:

- **Zeitzonen.** `start` und `end` sind `Date`-Objekte, und die Stunden werden in der Zeitzone der Umgebung formatiert, die gerade rendert. Ein Server auf UTC und ein Browser in New York schreiben unterschiedliche Stunden in dasselbe HTML, und React meldet einen Hydration Mismatch. Speicher Zeitpunkte (`timestamptz`), lass den Server mit `TZ` auf der Zeitzone deiner Nutzer laufen, oder lad den Scheduler mit `next/dynamic` und `ssr: false` aus einer Client-Komponente, wenn deine Nutzer in mehreren Zeitzonen sitzen.
- **Die aktuelle Uhrzeit.** Ohne `defaultDate` rendert der Server den Tag der ersten Buchung, und der Browser springt danach auf heute. So passt die Hydration. Setz `now` in Tests auf einen festen Wert.
- **Fehlende Ressourcen.** Eine Buchung, deren `resourceId` nicht in `resources` steht, landet in einer Zeile „No resource“. Behandle diese Zeile wie einen Bug-Report.
- **Arbeitszeiten sind volle Stunden.** `workingHours: { start: 9, end: 17 }` schattiert den Rest des Tages und das Wochenende. Mittagspausen und einmalige Schließungen gehören in `unavailable`, mit einem `label`.
- **Labels.** Jeder Text ist eine Prop. `labels={{ book: "Reservieren", newBooking: "Neue Reservierung" }}` ändert die Texte, `conflict` nimmt eine Funktion des Ressourcennamens, und `DEFAULT_SCHEDULER_LABELS` enthält alle Texte, die du per Spread als Basis unter deine Übersetzung legst.

## Wann ist ein normaler Terminkalender die bessere Wahl?

Ein normaler Kalender ist besser, wenn deine Nutzer vor allem ihre eigenen Meetings anschauen. Dafür nimmst du [Calendar View](https://wingo-ui.com/components/calendar-view): Monats-, Wochen-, Tages- und Agendaansicht mit Verschieben per Drag. Eine Buchung im Scheduler ist dessen Event plus `resourceId`, also speisen dieselben Daten beide Komponenten. Unser Guide zum [React Kalender mit Terminen](https://wingo-ui.com/de/blog/react-calendar-component-with-events) behandelt diesen Kalender genauer, und wenn die Zeilen Aufgaben mit Abhängigkeiten statt Buchungen sind, passt der Guide zum [React Gantt Chart](https://wingo-ui.com/de/blog/react-gantt-chart) besser.

> Live-Demo (Calendar View): Wechsel zwischen Month, Week, Day und Agenda und zieh dann ein Event auf eine andere Zeit, um es zu verschieben. Probier sie unter [Calendar View](https://wingo-ui.com/components/calendar-view) aus und installier sie mit `npx wingo-ui@latest add calendar-view`.

Um in einem Formular einen einzelnen Slot zu wählen, ist ein [Time Picker](https://wingo-ui.com/components/time-picker) neben einem Datumsfeld schlanker als beide. Der [Guide zu React-Formularkomponenten](https://wingo-ui.com/de/blog/react-form-components-guide) zeigt, wie diese Felder zusammenpassen, und in der [Kategorie Komponenten-Guides](https://wingo-ui.com/de/blog/category/components) findest du weitere Deep Dives zu einzelnen Komponenten.

## Wie fügst du den Scheduler in eine Next.js-App ein?

Führ `npx wingo-ui@latest add scheduler` aus. Der Befehl kopiert `components/ui/scheduler.tsx` und die 21 Wingo-UI-Items, die die Datei direkt importiert (plus deren eigene Abhängigkeiten), darunter Calendar View (für den Event-Typ), Popover, Select, Time Picker, Tooltip sowie die Helfer für Datum, Motion und Tones, und installiert `lucide-react` und `motion`. Der Scheduler gehört zu [Wingo UI Pro](https://wingo-ui.com/de/pricing), also melde dich vorher mit `npx wingo-ui@latest login` an.

## Komponenten in diesem Artikel

- [Scheduler](https://wingo-ui.com/components/scheduler) (Pro): A React resource scheduler to book rooms, cars and people on a timeline: drag to book, move and resize, conflicts refused, agenda on phones. Installation: `npx wingo-ui@latest add scheduler`
- [Calendar View](https://wingo-ui.com/components/calendar-view) (Pro): A React event calendar for appointments and meetings: month, week, day and agenda views, a now line and drag to reschedule. Installation: `npx wingo-ui@latest add calendar-view`

## Häufige Fragen

### Was ist ein Resource Scheduler in React?

Eine Zeitleiste, in der jede Zeile eine Ressource ist, etwa ein Besprechungsraum, ein Fahrzeug oder ein Teammitglied, und die Zeit über die Spalten läuft. Buchungen sind Balken in diesen Zeilen. So siehst du die Verfügbarkeit jeder Ressource auf einen Blick statt in einem gemeinsamen Kalender.

### Ist die Resource Timeline von FullCalendar kostenlos?

Nein. Stand Oktober 2026 führt die Preisseite von FullCalendar Timeline View und Vertical Resource View als Premium-Funktionen, und Premium beginnt bei $480 für 1 bis 10 Entwickler. Die Standard-Edition mit Monats-, Wochen- und Tagesansicht ist kostenlos.

### Wie verhindere ich Doppelbuchungen in einem React Scheduler?

Lehne überlappende Drops in der Oberfläche ab und prüf auf dem Server noch einmal, denn zwei Personen können denselben Slot im selben Moment buchen. Im Scheduler von Wingo UI ist allowOverlap standardmäßig false, gesperrte Zeit lehnt Buchungen immer ab, und ein abgelehntes Promise aus onEventChange setzt die Buchung zurück.

### Wie sollte ein Buchungskalender auf dem Handy funktionieren?

Zeig einen Tag als Liste von Buchungen, gruppiert nach Ressource, und einem Buchen-Button über die volle Breite, statt eines breiten Rasters, das du seitlich scrollen musst. Das Verschieben einer Buchung sollte per Touch erst nach langem Drücken starten, damit normales Wischen weiter die Seite scrollt.

### Brauche ich einen Resource Scheduler oder einen normalen Kalender?

Nimm einen Resource Scheduler, wenn die Frage lautet, welcher Raum, welches Auto oder welche Person zu einer bestimmten Zeit frei ist. Nimm einen normalen Terminkalender mit Monats- und Wochenansicht, wenn jede Person vor allem ihre eigenen Termine anschaut.

---

Quelle: https://wingo-ui.com/de/blog/react-resource-scheduler
