Planungstafel
Die Planungstafel stellt Zeilen und Zeit gegenüber: links Mitarbeiter, Räume, Maschinen oder Projekte, oben eine Zeitachse, dazwischen Einträge als Balken, Punkte oder Zellen. Sie zeigt, wer wann da ist, was wann belegt ist und wo eine Mindestbesetzung reißt. Alle Daten kommen aus SQL-Abfragen, alle Reaktionen auf Klicks sind konfigurierte Aktionen.
Typische Einsatzbereiche
| Einsatz | Zeilen | Einträge |
|---|---|---|
| Abwesenheitsplaner | Mitarbeiter nach Team | Urlaub, Krankheit, Fortbildung, Homeoffice |
| Teilzeit- und Schichtplan | Mitarbeiter | Schichten, freie Tage |
| Raum-, Fahrzeug-, Maschinenbelegung | Räume, Fahrzeuge, Maschinen | Buchungen, Wartungsfenster |
| Projekt- und Phasenplan | Projekte | Phasen, Meilensteine |
| Prüffristen | Anlagen, Verträge | Fälligkeiten als Punkte |
Erste Schritte
Die Tafel braucht mindestens eine Ebene: eine SQL-Abfrage, die je Balken eine Zeile liefert, mit Spalten für die Zeile (wer oder was), den Start und das Ende. Die Zeilen der Tafel kommen entweder aus einer eigenen Abfrage oder werden aus den Einträgen abgeleitet. Danach sagst du im Dialog nur noch, welche Spalte was ist.
Schnellster Weg mit der App „Urlaubsantrag“ (aus dem Marketplace):
- Widget „Planungstafel“ in ein Dashboard ziehen und Konfigurieren klicken.
- Reiter Zeilen: Herkunft „Aus den Einträgen ableiten“ wählen.
- Reiter Ebenen: Ebene hinzufügen, dann Beispiel einsetzen → Urlaubsanträge aus der App „Urlaubsantrag“. Abfrage, Einstellungen der Ebene, Spaltenzuordnung und passende Stilregeln sind damit gesetzt.
- Testlauf klicken, Übernehmen, Dashboard speichern.
Mit eigenen Stammdaten: Reiter Zeilen, Musterschema „Mitarbeiter“ anlegen. Reiter Ebenen, Musterschema „Abwesenheiten“ anlegen. Beide Dialoge tragen nach dem Anlegen gleich die passende Abfrage und Zuordnung ein. Danach im Data Lake Mitarbeiter und Abwesenheiten erfassen. Die Felder der Musterschemas stehen unter Musterschemas.
Jeder Reiter des Dialogs hat einen Info-Button, der den passenden Abschnitt dieser Seite öffnet.
Bausteine
- Zeilen: eine SQL-Abfrage mit Id, Anzeigename, optional Untertitel, Gruppe, Untergruppe, Kapazität und Arbeitstagen. Zeilen erscheinen auch ohne Einträge.
- Ebenen: je Ebene eine Abfrage der Einträge. Jede Ebene legt fest, ob sie Zeitspannen oder Zeitpunkte enthält, ob Datum oder Zeitstempel, ob die Ende-Spalte den letzten Tag meint, und was ein leeres Ende bedeutet. Fehlt eine Angabe, zeigt die Tafel den Fehler statt eines geratenen Bildes.
- Verfügbarkeit: Arbeitstage je Zeile (Spalte wie
1,2,3,4,5), Feiertage je Bundesland, optional eine Abfrage für Sperr- und Sonderzeiten. - Stilregeln: Feld plus Wert ergibt Farbe, Muster (voll, schraffiert, gestrichelt, gepunktet, gestreift, Rahmen), Kürzel für den Zellmodus oder Ausblenden. Beispiel: Status „Beantragt“ schraffiert, „Abgelehnt“ ausgeblendet.
- Gruppen: Rahmen um Zeilen mit „anwesend/gesamt“ je Tag oder als Heatmap.
- Ergebnisspalten: Werte aus der Zeilen-Abfrage rechts neben der Tafel, zum Beispiel genommener Urlaub, Resturlaub als Fortschrittsbalken, Status als Badge. Summen rechnet die Datenbank, die Tafel zeigt sie an.
- Regeln: Mindestbesetzung je Gruppe (Personen, Prozent oder Vollzeitäquivalente), Überschneidungen, abwesende Vertretungen. Jeder Hinweis hat einen Schweregrad und einen Satz.
- Markierungen: Daten und Zeiträume ohne Zeile, etwa Messe, Release oder Betriebsferien.
- Aktionen: Klick, Rechtsklick, Auswahl von Zellen, Verschieben oder Schaltfläche führen zu Datensatz öffnen, Prozess starten, Feld setzen, Kanal veröffentlichen oder Adresse öffnen.
Konfiguration
Der Dialog hat acht Reiter. Bei den Abfragen gilt überall: Testlauf führt die Abfrage mit fünf Zeilen aus und zeigt die gefundenen Spalten, danach bieten die Zuordnungsfelder diese Spalten zur Auswahl an. Die Platzhalter funktionieren wie in allen SQL-Widgets.
Zeilen
Herkunft der Zeilen: SQL-Abfrage (eine Zeile je Mitarbeiter, Raum, Maschine) oder aus den Einträgen ableiten (jeder Zeilen-Schlüssel der Ebenen wird eine Zeile, ohne Gruppen und Kapazität).
Spalten der Zeilen-Abfrage:
| Zuordnung | Pflicht | Bedeutung |
|---|---|---|
id |
ja | Zeilen-Schlüssel. Die Ebenen verweisen mit ihrer Spalte „Zeile“ auf genau diesen Wert, zum Beispiel die E-Mail des Benutzers. |
label |
ja | Anzeigename |
sublabel |
Untertitel, etwa Rolle oder E-Mail | |
groupId, groupLabel |
Gruppe (Team, Abteilung, Gebäude) | |
group2Id, group2Label |
Untergruppe | |
sort |
Sortierung innerhalb der Gruppe | |
capacity |
Stellenanteil 0 bis 1 für Anwesenheit in Vollzeitäquivalenten | |
workdays |
Arbeitstage als Kommaliste, 1 = Montag … 7 = Sonntag; andere Tage werden grau |
Weitere Spalten der Abfrage stehen als Ergebnisspalten zur Verfügung.
Beispiel einsetzen bietet zwei Abfragen. Antragsteller aus der App „Urlaubsantrag“:
SELECT DISTINCT app_beantragt_von AS id,
app_beantragt_von AS label
FROM app_vacation_requests
WHERE app_beantragt_von IS NOT NULL AND app_beantragt_von <> ''
ORDER BY 1
Mitarbeiter aus dem Musterschema (Tabelle mitarbeiter):
SELECT COALESCE(email, id) AS id,
name AS label,
email AS sublabel,
team AS group_id,
team AS group_label,
arbeitstage AS workdays,
fte AS capacity
FROM mitarbeiter
WHERE COALESCE(aktiv, true) = true
ORDER BY team, name
Zuordnung: id → id, label → label, sublabel → sublabel, groupId → group_id, groupLabel → group_label, workdays → workdays, capacity → capacity.
Verfügbarkeit im selben Reiter: Feiertage je Bundesland per Auswahl, dazu eine optionale Abfrage für Sperr- und Sonderzeiten mit den Spalten row (leer = alle Zeilen), start, end (letzter Tag), state (unavailable, partial, available), capacity (0 bis 1 bei partial) und label. Beispiel Betriebsferien für alle Zeilen:
SELECT CAST(NULL AS varchar) AS row_key,
CAST(concat(CAST(year(current_date) AS varchar), '-12-24') AS date) AS start_day,
CAST(concat(CAST(year(current_date) AS varchar), '-12-31') AS date) AS end_day,
'unavailable' AS state,
'Betriebsferien' AS label
Ebenen
Jede Ebene hat vier Pflichtangaben, die nichts erraten lassen:
| Einstellung | Werte | Bedeutung |
|---|---|---|
| Art | Zeitspanne, Zeitpunkt | Balken mit Start und Ende oder ein Punkt |
| Zeitmodus | Datum, Zeitstempel | YYYY-MM-DD oder Zeitstempel mit Uhrzeit |
| Ende inklusiv | an, aus | Bei Datum: die Ende-Spalte nennt den letzten Tag (an) oder den Tag danach (aus) |
| Leeres Ende | Fehler, bis jetzt, bis zum Rand | Was gilt, wenn die Ende-Spalte leer ist |
Dazu die Farbe (Kategorie aus der Palette, feste Farbe der Ebene oder nur Stilregeln), die Kapazitätswirkung (1 = der Eintrag macht die Zeile abwesend, 0 = keine Wirkung, etwa Arbeitszeit) und die Einheit der Menge.
Spalten der Ebenen-Abfrage:
| Zuordnung | Pflicht | Bedeutung |
|---|---|---|
row |
ja | Zeilen-Schlüssel, muss zum id der Zeilen passen |
start |
ja | Datum oder Zeitstempel |
end |
bei Zeitspannen | Datum oder Zeitstempel; bei „Ende inklusiv“ der letzte Tag |
dayPart |
am, pm oder full für Halbtage (nur Datum) |
|
quantity |
Menge in der Einheit der Ebene, etwa angerechnete Tage oder Stunden | |
category |
Kategorie für Farbe und Legende | |
status |
Status für Stilregeln, Sperren und Bedingungen | |
label |
Text auf dem Balken | |
linkRow |
Zeilen-Schlüssel der Vertretung; die Regel „Vertretung prüfen“ warnt, wenn sie selbst abwesend ist | |
id, dataSchemaId |
für Aktionen | Datensatz-Id und Datenschema-Id, damit „Datensatz öffnen“, „Feld setzen“ und Verschieben funktionieren |
updatedAt |
Änderungszeitpunkt für den Abgleich nach Schreibvorgängen |
Zeitstempel kommen als ISO-Text mit Zeitzone an (2026-10-09T08:00:00+02:00) oder als Athena-Zeitstempel, der als UTC gelesen wird.
Beispiel einsetzen bietet drei Abfragen. Urlaubsanträge aus der App „Urlaubsantrag“ (Zeitspanne, Datum, Ende inklusiv):
SELECT id,
data_schema_id,
updated_at,
app_beantragt_von AS row_key,
app_erster_tag AS start_day,
COALESCE(app_letzter_tag, app_erster_tag) AS end_day,
CASE WHEN app_erster_tag = COALESCE(app_letzter_tag, app_erster_tag)
AND app_erster_tag_vollhalb = 'Halb' THEN 'am' ELSE 'full' END AS day_part,
app_urlaubskategorie AS category,
CASE app_freigabe WHEN 'Genehmigen' THEN 'Genehmigt'
WHEN 'Ablehnen' THEN 'Abgelehnt'
ELSE 'Beantragt' END AS status,
app_anmerkung AS label
FROM app_vacation_requests
WHERE app_beantragt_von IS NOT NULL
AND app_erster_tag < CAST(@periodEnd AS date)
AND COALESCE(app_letzter_tag, app_erster_tag) >= CAST(@periodStart AS date)
Zuordnung: row → row_key, start → start_day, end → end_day, dayPart → day_part, category, status, label, id, dataSchemaId → data_schema_id, updatedAt → updated_at. Das Beispiel setzt Stilregeln für die Kategorien sowie „Beantragt“ schraffiert und „Abgelehnt“ ausgeblendet, falls noch keine Stilregeln vorhanden sind.
Abwesenheiten aus dem Musterschema (Tabellen abwesenheiten und mitarbeiter; die Referenzfelder enthalten die Id des Mitarbeiter-Datensatzes):
SELECT a.id,
a.data_schema_id,
a.updated_at,
COALESCE(m.email, m.id) AS row_key,
a.erster_tag AS start_day,
a.letzter_tag AS end_day,
CASE WHEN a.erster_tag = a.letzter_tag AND a.erster_tag_vollhalb = 'Halb' THEN 'am' ELSE 'full' END AS day_part,
a.kategorie AS category,
a.status AS status,
a.tage AS quantity,
a.bemerkung AS label,
COALESCE(v.email, v.id) AS link_row
FROM abwesenheiten a
LEFT JOIN mitarbeiter m ON m.id = a.mitarbeiter
LEFT JOIN mitarbeiter v ON v.id = a.vertretung
WHERE a.erster_tag < CAST(@periodEnd AS date)
AND a.letzter_tag >= CAST(@periodStart AS date)
Zeiteinträge aus der App „Zeiterfassung“ (Zeitspanne, Zeitstempel, Kapazitätswirkung 0, Einheit Stunden). Datum und Uhrzeit werden zu einem Zeitstempel in Europe/Berlin zusammengesetzt:
SELECT id,
data_schema_id,
updated_at,
app_mitarbeiter AS row_key,
to_iso8601(with_timezone(date_parse(concat(CAST(app_datum AS varchar), ' ', app_arbeitsbeginn), '%Y-%m-%d %H:%i'), 'Europe/Berlin')) AS start_at,
to_iso8601(with_timezone(date_parse(concat(CAST(app_datum AS varchar), ' ', app_arbeitsende), '%Y-%m-%d %H:%i'), 'Europe/Berlin')) AS end_at,
app_kategorie AS category,
app_arbeitsstunden AS quantity,
app_bemerkung AS label
FROM app_time_entries
WHERE app_datum < CAST(@periodEnd AS date)
AND app_datum >= CAST(@periodStart AS date)
Der Zeitraumfilter der Tafel lädt nur den sichtbaren Ausschnitt, deshalb gehört die Bedingung mit @periodStart und @periodEnd in jede Ebenen-Abfrage.
Darstellung
Balken oder Zellen (Zellmodus zeigt im Tagesmaßstab je Tag ein Kürzel), Dichte, Theme (hell, weich, Mitternacht, Kontrast, Pantarey oder automatisch nach Systemeinstellung), Legende, erlaubte Maßstäbe und Standard, Kalenderwochen, Jetzt-Linie, Minimap, Gesamtzeile mit Köpfen oder Vollzeitäquivalenten, Zebrastreifen, Suchfeld, Kontextmenü, Unterzeilen je Zeile, Breite der Namensspalte, Zeitraumfilter mit angebotenen Zeiträumen, Bearbeiten per Maus (Verschieben, Verlängern, Einrasten, gesperrte Status).
Gruppen und Regeln
Gruppierung nach der Gruppe aus der Zeilen-Abfrage mit Gruppenzeile je Tag (anwesend/gesamt oder Heatmap) und Reihenfolge. Arbeitstage der Tafel. Regeln mit Schweregrad Info, Warnung oder Blockierend: Mindestbesetzung je Gruppe oder für die ganze Tafel in Personen, Prozent oder Vollzeitäquivalenten; Überschneidungen innerhalb einer Zeile, wahlweise nur für bestimmte Ebenen; Vertretung abwesend.
Ergebnisspalten
Je Spalte: Spalte der Zeilen-Abfrage, Überschrift, Format (Zahl, Fortschritt mit Maximum-Spalte, Badge, Text), Einheit, Nachkommastellen, Summen (keine oder Zeilen summieren), Breite. Werte wie „Urlaub genommen im Jahr“ rechnet die Zeilen-Abfrage, zum Beispiel mit SUM(...) FILTER (WHERE ...) und einem LEFT JOIN auf die Abwesenheiten.
Stilregeln
Regeln in Reihenfolge, spätere übersteuern frühere. Feld ist category, status, layerId, label oder eine Spalte der Abfrage; Wert eine Kommaliste oder *. Wirkung: Farbe, Muster, Kürzel für den Zellmodus, Deckkraft, Ausblenden. Optional nur für bestimmte Ebenen.
Markierungen
Zeitpunkte (Datum) und Zeiträume (Start, Ende exklusiv) mit Beschriftung und Farbe, die ohne Zeile über die ganze Tafel gezeichnet werden.
Aktionen
Je Aktion: Ziel (Eintrag, Zeile, Auswahl, Tafel), Auslöser (Klick, Kontextmenü, Schaltfläche, Verschieben, Verlängern), Beschriftung und Symbol, optional nur für bestimmte Ebenen, eine Bedingung als JSONata über entry, row, range und user (true erlaubt, false blendet aus, ein Text deaktiviert mit Begründung), eine Rückfrage. Arten:
| Art | Wirkung |
|---|---|
| Datensatz öffnen | öffnet den Datensatz im Data Lake; braucht id und dataSchemaId in der Ebene |
| Prozess starten | öffnet das Startformular des gewählten Prozesses im Dialog oder in einem neuen Tab |
| Feld setzen | schreibt einen festen Wert in ein Feld des Datensatzes, etwa Status „Genehmigt“ |
| Eintrag verschieben | schreibt Start- und Endfeld nach dem Ziehen; bei „Ende inklusiv“ wieder als letzter Tag |
| Auf Kanal veröffentlichen | sendet Eintrag oder Auswahl an andere Widgets des Dashboards |
| Adresse öffnen | öffnet eine URL mit Platzhaltern wie $(entry.id), $(row.id), $(range.start) |
| Aktualisieren | lädt die Tafel neu |
Die Standardkonfiguration enthält „Öffnen“ bei Klick auf einen Eintrag.
Platzhalter
In allen Abfragen der Tafel:
| Platzhalter | Wird zu |
|---|---|
@periodStart, @periodEnd |
Grenzen des gewählten Zeitraums als Zeitstempel-Ausdruck; mit CAST(@periodEnd AS date) mit Datumsspalten vergleichen. @periodEnd ist der erste Tag nach dem Zeitraum. |
@currentUserEmail, @currentUserSub |
angemeldeter Benutzer, zum Beispiel für „nur meine Zeile“ |
@currentDate, @currentYear, @currentMonth, @currentTimeStamp |
heutiges Datum, Jahr, Monat, Zeitpunkt |
$(parameter) |
Wert aus dem umgebenden Filtercontainer |
Musterschemas
Die beiden Dialoge „Musterschema anlegen“ erzeugen normale Datenschemas, die du im Data Designer beliebig erweitern kannst. Der technische Name ist zugleich der Tabellenname in SQL.
Mitarbeiter (Vorschlag mitarbeiter):
| Feld | Typ | Verwendung auf der Tafel |
|---|---|---|
email |
Pantarey-Benutzer | Zeilen-Schlüssel; passt zu app_beantragt_von der App „Urlaubsantrag“ |
name |
Text, Pflicht | Anzeigename |
team |
Text | Gruppe |
arbeitstage |
Text, Standard 1,2,3,4,5 |
Arbeitstage, Teilzeit zum Beispiel 1,2,4 |
fte |
Zahl, Standard 1 | Stellenanteil für Vollzeitäquivalente |
bundesland |
Auswahl | Feiertage |
aktiv |
Ja/Nein, Standard ja | ausgeschiedene Mitarbeiter ausblenden |
Abwesenheiten (Vorschlag abwesenheiten):
| Feld | Typ | Verwendung auf der Tafel |
|---|---|---|
mitarbeiter |
Referenz auf Mitarbeiter, Pflicht | Zeile |
erster_tag, letzter_tag |
Datum, Pflicht | Start und Ende (inklusiv) |
erster_tag_vollhalb, letzter_tag_vollhalb |
Voll/Halb | Halbtage |
kategorie |
Jahresurlaub, Krank, Fortbildung, Homeoffice | Farbe und Legende |
status |
Beantragt, Genehmigt, Abgelehnt | Stilregeln, Sperren, Bedingungen |
tage |
Zahl | Menge |
vertretung |
Referenz auf Mitarbeiter | Vertretungsprüfung |
bemerkung |
Text | Text auf dem Balken |
Referenzfelder speichern die Id des verknüpften Datensatzes. In SQL wird deshalb über m.id = a.mitarbeiter verknüpft.
Bedienung
- Maßstab oben rechts umschalten, Heute springt zur Jetzt-Linie, die Minimap unter der Toolbar zeigt das ganze Jahr und springt per Klick.
- Tooltip mit Zeitraum, Status, Menge und Hinweisen beim Überfahren eines Balkens.
- Legende unten: Klick blendet eine Kategorie aus.
- Gruppen auf- und zuklappen; die Gruppenzeile zählt je Tag, Unterschreitungen der Mindestbesetzung sind farbig.
- Ziehen: Balken verschieben, an den Kanten verlängern. Während des Ziehens zeigt ein Hinweis, wie sich die Anwesenheit des Teams am kritischsten Tag ändert. Gesperrte Status verweigern mit Begründung.
- Auswahl: über leere Zellen ziehen, dann erscheinen die konfigurierten Aktionen mit Anzahl Tage und Arbeitstage.
- Suche filtert Zeilen und Einträge; Escape hebt die Auswahl auf; Tab wandert durch die Balken.
Hinweise
- Die Tafel ändert nie selbst Daten. Beim Verschieben oder bei „Feld setzen“ schreibt das Widget die konfigurierten Felder und zeigt den Eintrag gestreift, bis die Datenbank den neuen Stand liefert.
- Geladen wird der gewählte Zeitraum. Ergebnisspalten wie „Urlaub genommen im Jahr“ gehören deshalb in die Zeilen-Abfrage, nicht in die Ebene.
- Wer welche Zeilen sieht, entscheidet die Abfrage:
WHERE m.email = @currentUserEmailzeigt nur die eigene Zeile, eine Bedingung auf das Team nur die Kollegen. - Einträge, deren Zeilen-Schlüssel keine Zeile trifft, werden nicht gezeichnet. Im Designer zeigt die Vorschau ihre Anzahl.