Search

Support

Discord

English

Search

Support

Discord

English

Widgets

Calendar Timeline (Beta)

Custom Calendar Timeline

AI Defaults (read first)

  • uniqueId: Required, "timeline-" + Nr.

  • height: Use "700px" or "100%" when embedded.

  • timeZoneBalance: Set -1 for German timezone.

  • header: dateSettings.header.days (Tages-Labels, Uhrzeit: gleiches Objekt statt title); optional header.months / header.weeks (übergeordnete Zeilen). Optional alignX, sticky: "left", stickyPadding.

  • columns: Label-Spalten links (Titel im Header, optional width); mindestens eine Spalte.

  • items: Ressourcen-Zeilen; jedes Item braucht id (eindeutig pro Zeile) und timeEntries. Verschachtelung über items (max. eine Ebene unterhalb des Parents).

  • dateFrom/dateTo: Actual date values in timeEntries, not strings.

  • dayWidth: Default "120px", increase for detailed timelines.

  • dragAction: recordId + optional resourceField für vertikal; nur gleiche Verschachtelungstiefe (_depth, z. B. Mitarbeiter ↔ Mitarbeiter auch über Teams hinweg). Optional enabled: false schaltet Drag für diesen Balken aus.

  • Uhrzeit mode: Activated by timeSettings, uses columnWidth instead of dayWidth. timeFrom/timeTo in milliseconds.

  • Zellen-Hintergrund: dateSettings.highlights[] mit optional applyToRows: true; optional items[].highlights pro Zeile (date, dateFrom/dateTo bzw. timeFrom/timeTo).

  • Ghost (Neueinträge): Optional pro Zeile items[].ghost mit title, actions, optional position (Default bottomRight: kleiner Plus-Button unten rechts in der Spalte), optional styles. Klick führt ghost.actions aus. Positionen außer timeEntry: Plus erscheint erst bei Maus über der Spalte (Spalte wird per Zeilen-mousemove erkannt); Touch: Ghost bleibt sichtbar. timeEntry: siehe Abschnitt ghost (Idle nur in leeren Spalten). Details im Abschnitt ghost.

Mit dem Widget Custom Calendar Timeline bringst du eine ressourcenbasierte Kalenderansicht in deine Ninox-Datenbank. Ressourcen (z. B. Mitarbeiter, Räume, Fahrzeuge) werden als Zeilen dargestellt, die Tage oder Uhrzeiten als Spalten. Termine erscheinen als horizontale Balken – ideal für Einsatzplanung, Kapazitätsübersicht oder Ressourcenplanung.

💡 Was du mit Custom Calendar Timeline machen kannst:

  • Ressourcen als Zeilen: Jede Zeile steht für eine Ressource (Person, Raum, Team, Fahrzeug).

  • Termine als Balken: Einträge werden horizontal über die Tage gespannt – mit oder ohne Uhrzeit.

  • Uhrzeitmodus: Zeige statt Tagen Uhrzeitspalten an – ideal für Flottendiagramme und Tageseinsatzplanung.

  • Hierarchische Einträge: items mit Unter-items (eine Ebene), flache Zeilen mit Einrückung – Datenmodell wie Gantt.

  • Drag & Drop: Verschiebe Termine horizontal (Datum/Uhrzeit ändern) oder vertikal (Ressource/Zeile wechseln).

  • Mehrtägige Einträge: Zeige Projekte, Urlaub oder längere Blöcke über mehrere Tage.

  • Hervorhebung: Markiere Wochenenden, bestimmte Stunden oder besondere Tage farblich.

  • Individuelles Styling: Passe Farben, Breiten und Höhen an dein Design an.

📅 Custom Calendar Timeline eignet sich besonders für Einsatzplanung, Schichtpläne, Kapazitätsübersichten und Flottendiagramme – überall dort, wo du Ressourcen und Termine im Zeitverlauf im Blick behalten willst.

Anwendungscode

let current := this;
arcCustomCalendarTimeline({
	uniqueId: "timeline-1",
	timeZoneBalance: 0,
	lang: "de",
	dateSettings: {
		startDate: today(),
		duration: 30,
		highlights: [
			{ weekday: 6, color: "rgba(59,130,246,0.06)" },
			{ weekday: 0, color: "rgba(59,130,246,0.06)" }
		],
		header: {
			days: {
				label: "##weekDayShort## ##day##.",
				fontSize: "12px",
				fontColor: "#333",
				fontWeight: "600",
				backgroundColor: "#f9f9f9"
			}
		}
	},
	dayWidth: "120px",
	rowHeight: "auto",
	currentTime: "rgba(59,130,246,0.5)",
	height: "700px",
	styles: {
		backgroundColor: "#ffffff"
	},
	columns: [
		{ title: "Name", width: "200px" },
		{ title: "Abteilung", width: "130px" }
	],
	items: (select Mitarbeiter).[{
		id: Nr,
		title: Name,
		subtitle: Abteilung,
		columns: [
			{ title: text(Name) },
			{ title: text(Abteilung) }
		],
		timeEntries: (select Termine where Bearbeiter = Nr).[{
			title: Bezeichnung,
			subtitle: Kunde,
			dateFrom: Datum_von,
			dateTo: Datum_bis,
			timeFrom: Zeit_von,
			timeTo: Zeit_bis,
			styles: {
				backgroundColor: text(Farbe),
				color: "#fff",
				borderRadius: "6px",
				padding: "4px 8px"
			},
			dragAction: {
				recordId: Nr,
				dateFrom: fieldId(Nr, "Datum_von"),
				dateTo: fieldId(Nr, "Datum_bis"),
				resourceField: fieldId(Nr, "Bearbeiter")
			},
			actions: [{
				type: "popup",
				recordId: Nr
			}]
		}]
	}]
})

Allgemeine Settings

uniqueId

Eindeutige Kennung des Widgets. Wichtig, wenn mehrere Timeline-Widgets auf einer Seite verwendet werden.

uniqueId: "timeline-1",

timeZoneBalance

Zeitzonenkorrektur in Stunden. Für Deutschland oft -1, um die Anzeige an MEZ/MESZ anzupassen.

timeZoneBalance: 0,
timeZoneBalance: -1,

lang

Sprache für Datumsformatierung (z. B. Wochentagsnamen).

lang: "de",
lang: clientLang(),

height

Höhe des Widget-Containers.

height: "700px",
height: "100%",

styles

Individuelle Styles für den äußeren Container (z. B. Hintergrundfarbe).

styles: {
	backgroundColor: "#ffffff"
},

dateSettings – Zeitraum & Darstellung

startDate

Startdatum der Timeline. Ab diesem Tag werden die Spalten angezeigt.

startDate: today(),
startDate: date(2025, 2, 1),

duration

Anzahl der angezeigten Tage. Wird ignoriert, wenn endDate gesetzt ist.

duration: 30,
duration: 14,

endDate

(Optional) Enddatum der Timeline. Hat Vorrang vor duration.

dateSettings: {
    startDate: date(2025, 2, 1),
    endDate: date(2025, 2, 28)
}

highlights

Farbliche Hervorhebung bestimmter Tage – z. B. Wochenenden oder Feiertage.

highlights: [
	{ weekday: 6, color: "rgba(59,130,246,0.06)" },
	{ weekday: 0, color: "rgba(59,130,246,0.06)" },
	{ date: date(2025, 12, 24), color: "rgba(239,68,68,0.1)" }
],
  • weekday: 0 = Sonntag, 6 = Samstag

  • date: Konkretes Datum

  • dateFrom / dateTo: Datumsbereich (optional statt einzelnem date)

  • color: CSS-Farbe (HEX oder rgba)

  • applyToRows (optional): true – dieselbe Hervorhebung wird in der Zeilenfläche (hinter den Termin-Balken) für alle Ressourcen-Zeilen gezeichnet, nicht nur in der Kopfzeile. Ohne dieses Flag bleibt das Verhalten wie bisher (nur Header).

Im Uhrzeitmodus zusätzlich:

  • hour: Stunde (0–23) für Hervorhebung bestimmter Stunden-Spalten

  • timeFrom / timeTo: Zeitbereich in Millisekunden (wie bei timeEntries), z. B. Mittagspause

highlights: [
    { hour: 12, color: "rgba(239,68,68,0.1)" }
]
highlights: [
	{ weekday: 6, color: "rgba(59,130,246,0.06)", applyToRows: true },
	{ date: date(2025, 12, 24), color: "rgba(239,68,68,0.1)", applyToRows: true },
	{ timeFrom: time(12, 0), timeTo: time(13, 0), color: "rgba(156,163,175,0.15)", applyToRows: true }
],

highlights pro Zeile (items[].highlights)

Neben den globalen dateSettings.highlights kann jede Ressourcen-Zeile ein eigenes Array highlights haben – z. B. Urlaub oder Krankenstand nur für einen Mitarbeiter. Die gleichen Felder wie oben gelten; applyToRows ist hier nicht nötig (gilt immer nur für diese Zeile).

items: (select Mitarbeiter).[{
	id: Nr,
	title: Name,
	highlights: [
		{ dateFrom: Urlaub_von, dateTo: Urlaub_bis, color: "rgba(34,197,94,0.15)" },
		{ date: Krank_Tag, color: "rgba(239,68,68,0.12)" }
	],
	timeEntries: [...]
}]

Uhrzeitmodus: z. B. Sperr- oder Wartungsfenster mit timeFrom / timeTo.

header

Der header-Block steuert alle Kopfzeilen der Timeline. Er besteht aus drei optionalen Unterblöcken: months, weeks und days. Alle drei haben dieselbe Struktur und können unabhängig voneinander aktiviert werden. Die Reihenfolge im Widget ist immer: Monate → Kalenderwochen → Tage (von oben nach unten).

header: {
	months: {
		visible: true,
		label: "##monthLong## ##year##",
		fontSize: "11px",
		fontColor: "#555",
		fontWeight: "600",
		backgroundColor: "#ececec",
		highlights: [
			{ month: 5, color: "rgba(59,130,246,0.12)" }
		]
	},
	weeks: {
		visible: true,
		label: "KW ##week##",
		fontSize: "11px",
		fontColor: "#555",
		backgroundColor: "#f2f2f2",
		highlights: [
			{ week: 18, color: "rgba(239,68,68,0.1)" }
		]
	},
	days: {
		visible: true,
		label: "##weekDayShort## ##day##.",
		fontSize: "12px",
		fontColor: "#333",
		fontWeight: "600",
		backgroundColor: "#f9f9f9"
	}
}

Alle drei Unterblöcke sind optional. Wird ein Block weggelassen, ist er nicht aktiv. days entspricht dem bisherigen header.title-Verhalten (Backward-Kompatibilität: header.title wird weiterhin unterstützt).

Gemeinsame Properties (je Unterblock):

Property

Beschreibung

visible

false blendet die Zeile aus (Standard: true)

label

Anzeigetext mit Platzhaltern (s. u.)

alignX

"left" | "center" | "right" – horizontale Ausrichtung in der Zelle bzw. Gruppe. Standard: "left" (Monat/KW-Gruppen und Tage).

fontSize

Schriftgröße, z. B. "12px"

fontColor

Schriftfarbe

fontWeight

Schriftstärke, z. B. "600"

backgroundColor

Hintergrund der gesamten Zeile bzw. Monats-/KW-Gruppe. days: färbt u. a. die Tages-Headerzeile; bei days.sticky: "left" hat der Beschriftungs-span kein eigenes undurchsichtiges backgroundColor, damit dateSettings.highlights (Farb-Overlay pro Tages-div) hinter dem Text sichtbar bleibt.

highlights

Hervorhebungen pro Monats- bzw. KW-Gruppe (s. u.) – nur für months / weeks

sticky

Optional für months, weeks und days: sticky: "left" hält die Bezeichnung bei horizontalem Scrollen sichtbar (CSS position: sticky); der horizontale Abstand setzt an den linken sichtbaren Rand des Zeitbereichs, berechnet als Summe aller linken columns-Breiten + stickyPadding. Ohne sticky steuert nur alignX die Ausrichtung.

stickyPadding

Zusätzlicher Abstand in px (Zahl) oder z. B. "12px", addiert auf die Labelspalten-Breite für sticky: "left". Wenn weggelassen, wird +8 px addiert.

Hinweis: dateSettings.highlights färben pro Tag die Tages-Headerzelle (div, optional mit applyToRows auch die Zeilenfläche). In Kombination mit header.days.sticky: "left" bitte backgroundColor-Hinweis in der Tabelle beachten.

Platzhalter für days.label:

  • ##weekDayShort## – Kurzer Wochentag (Mo, Di, …)

  • ##weekDayLong## – Langer Wochentag (Montag, Dienstag, …)

  • ##day## – Tag (1–31)

  • ##monthShort## – Kurzer Monat (Jan, Feb, …)

  • ##monthLong## – Langer Monat (Januar, Februar, …)

  • ##year## – Jahr

Platzhalter für weeks.label:

  • ##week## – Kalenderwochennummer (01–53, zweistellig mit führender Null)

  • ##year## – Jahr

Platzhalter für months.label:

  • ##monthLong## – Langer Monatsname (Januar, Februar, …)

  • ##monthShort## – Kurzer Monatsname (Jan, Feb, …)

  • ##year## – Jahr

highlights pro Unterblock:

months: {
	highlights: [
		{ month: 5, color: "rgba(59,130,246,0.12)" }   // Mai hervorheben (1 = Januar)
	]
},
weeks: {
	highlights: [
		{ week: 18, color: "rgba(239,68,68,0.1)" }     // KW 18 hervorheben
	]
}
  • months.highlights: { month: N } – N = 1 (Januar) bis 12 (Dezember)

  • weeks.highlights: { week: N } – N = ISO-Kalenderwochennummer

Layout-Parameter

dayWidth

Breite einer Tages-Spalte.

dayWidth: "120px",
dayWidth: "90px",

rowHeight

Höhe einer Ressourcen-Zeile. "auto" passt sich dem Inhalt an.

rowHeight: "auto",
rowHeight: "36px",

labelWidth

Standard-Breite für eine Label-Spalte, wenn columns fehlt oder wenn in columns[i] kein width gesetzt ist. Standard: "120px".

labelWidth: "150px",

currentTime

Farbe des Balkens, der die aktuelle Uhrzeit markiert. Leer oder false blendet ihn aus.

currentTime: "rgba(59,130,246,0.5)",
currentTime: "",

items – Ressourcen und Termine

items ist ein Array von Ressourcen-Zeilen. Jede Zeile hat timeEntries (Balken). Optional: items (Kinder, eine Ebene) und columns (Zelltexte pro Label-Spalte).

Struktur einer Zeile

{
	id: Nr,
	title: Name,
	subtitle: Abteilung,
	fontSize: "13px",
	fontWeight: "normal",
	columns: [
		{ title: text(Name) },
		{ title: text(Abteilung) }
	],
	styles: {},
	highlights: [],
	timeEntries: [...],
	items: [...]
}
  • id (Pflicht): Eindeutige ID (z. B. Record-Nr). Wird für Drag & Drop (resourceField) und collapsible benötigt.

  • title / subtitle: Fallback für die erste Label-Spalte, wenn columns fehlt oder die erste Zelle leer ist.

  • fontSize / fontWeight (optional, Item-Ebene): Standard-Typo für alle Label-Zellen dieser Zeile, sofern weder Top-Level-Spalte noch columns[i] etwas setzen. Reihenfolge der Priorität (höher zuerst): columns[i]Item fontSize/fontWeightstyles.fontSize/styles.fontWeight.

  • columns (pro Zeile): Ein Objekt pro Label-Spalte (gleiche Anzahl wie Top-Level columns): title, subtitle, optional customLayout (HTML für diese Zelle), actions, optional alignX, alignY, fontSize, fontWeight.

  • styles: Optional (z. B. Zeilen-Hintergrund; fontSize/fontWeight hier wirken als letzte Typo-Fallbacks).

  • highlights (optional): Zellen-Hintergrund für diese Zeile (Abwesenheiten, Sperrzeiten); siehe Abschnitt highlights pro Zeile.

  • timeEntries: Termine/Balken in dieser Zeile. Horizontaler Abstand: fest 4px zur Spaltenkante (wie das Zeilen-Padding oben/unten); bei mehrspaltigen Balken nur am äußeren Anfang und Ende, nicht zwischen den Tagen (nicht über timeEntries[].styles änderbar).

  • items: Optional. Unterzeilen (eine Ebene).

Struktur eines timeEntry (Termin/Balken)

{
	title: Bezeichnung,
	subtitle: Kunde,
	dateFrom: Datum_von,
	dateTo: Datum_bis,
	timeFrom: Zeit_von,
	timeTo: Zeit_bis,
	styles: {},
	dragAction: {},
	actions: [],
	customLayout: "",
	value: ""
}
  • title: Text im Balken, oder Objekt { value: …, width: … } (siehe unten). Bei Zeitangaben kann ##entrytime## verwendet werden.

  • subtitle: Optional. Zweiter Textblock im Balken (String oder Objekt wie bei title).

  • direction (optional): "horizontal" (Standard) oder "vertical" – legt fest, ob Titel- und Subtitle-Block nebeneinander oder untereinander stehen.

  • dateFrom, dateTo: Start- und Enddatum (Pflicht).

  • timeFrom, timeTo: Optional. Uhrzeit in Millisekunden (0 = Mitternacht). Ohne Angabe wird der Eintrag als ganztägig dargestellt.

  • styles: CSS-ähnliche Eigenschaften (backgroundColor, color, borderRadius, padding).

  • dragAction: Konfiguration für Drag & Drop.

  • actions: Array von Aktionen beim Klick auf den Balken (wie bei anderen Widgets). Das frühere einzelne Feld clickAction wird weiterhin unterstützt und wie ein Eintrag in actions ausgeführt.

  • customLayout: Optional. Eigenes Layout statt title/subtitle/value.

  • value: Optional. Zusätzlicher HTML-Inhalt im Balken (nach dem Titel/Subtitle-Block).

Objekt-Form für title / subtitle: { value: TextOderHtml, width: "fraction" | "auto" | CSS-Länge }

  • value: Anzuzeigender Text oder HTML (wie bisher); ##entrytime## möglich.

  • width *(optional, Default "fraction")**:

    • "fraction" – nutzt den verbleibenden Platz in der Zeile; langer Text wird mit Ellipsis gekürzt.

    • "auto"Inhaltsbreite; schrumpft nicht zugunsten des Partners (z. B. für Uhrzeiten, die voll sichtbar bleiben sollen). Sehr langer Text kann am Balkenrand trotzdem mit Ellipsis enden.

    • CSS-Länge (z. B. "72px", "4.5rem") – feste Basisbreite (flex-basis) für diesen Block.

Sind title und subtitle nur Strings (ohne Objekt) und ist direction nicht gesetzt, entspricht die Darstellung dem bisherigen Verhalten (inkl. Ellipsis). Sobald mindestens ein Feld die Objekt-Form nutzt oder direction: "vertical" gesetzt ist, steuert das Widget die Aufteilung per Flex.

title: { value: text(Bezeichnung), width: "fraction" },
subtitle: { value: "##entrytime##", width: "auto" },

styles – Styling der Termin-Balken

styles: {
	backgroundColor: "#3b82f6",
	color: "#fff",
	borderRadius: "6px",
	padding: "4px 8px"
},

dragAction – Verschieben von Terminen

Definiert, welche Felder beim Ziehen aktualisiert werden.

dragAction: {
	recordId: Nr,
	dateFrom: fieldId(Nr, "Datum_von"),
	dateTo: fieldId(Nr, "Datum_bis"),
	resourceField: fieldId(Nr, "Bearbeiter"),
	enabled: true
},
  • recordId: Record-ID des Termins (meist Nr).

  • dateFrom, dateTo: Feld-IDs für Start- und Enddatum. Werden beim horizontalen Verschieben aktualisiert.

  • resourceField: Optional. Feld-ID für die Ressource (z. B. Bearbeiter). Wird beim vertikalen Verschieben (Zeilenwechsel) mit der id der Ziel-Zeile aktualisiert – nur wenn Start- und Zielzeile dieselbe Tiefe in der items-Hierarchie haben (_depth gleich). Erforderlich z. B., damit kein Termin von einer Team-Zeile auf eine Mitarbeiter-Zeile (oder umgekehrt) gezogen wird; Mitarbeiter unter Team A → Mitarbeiter unter Team B ist erlaubt.

  • enabled: Optional. false: kein Drag für diesen Balken (z. B. Parent-Summen). Wenn weggelassen, ist Drag aktiv, sobald dragAction gesetzt ist.

⚠️ Hinweis: Ohne dragAction ist Drag & Drop deaktiviert. Vertikales Ziehen zwischen verschiedenen Tiefen (z. B. Team-Zeile ↔ Mitarbeiter-Zeile) ist nicht möglich; innerhalb derselben Tiefe (z. B. nur Mitarbeiter-Zeilen) auch über verschiedene Parents (Teams).

actions – Klick auf Termin

actions: [{
	type: "popup",
	recordId: Nr
}],
  • type: "popup" öffnet den Datensatz in einem Popup.

  • recordId: Record-ID des angeklickten Termins.

Mehrere Einträge im Array werden nacheinander ausgeführt. Legacy: Ein einzelnes clickAction-Objekt (ohne Array) funktioniert weiterhin.

actions – Klick auf Label-Zelle

In items[].columns[i] kann optional actions gesetzt werden. Ist das Array nicht leer, löst ein Klick auf die Zelle dieselben Ninox-Aktionen aus. Hinweis: In der ersten Label-Spalte hat bei eingeklappten Gruppen (collapsible) der Klick Vorrang zum Auf-/Zuklappen; dort werden keine zusätzlichen actions auf der Zelle ausgeführt.

items: (select Fahrzeuge).[{
	id: Nr,
	columns: [
		{ title: text(Bezeichnung), subtitle: "Fahrzeug" },
		{
			title: "⌂",
			actions: [{
				type: "popup",
				recordId: Nr
			}]
		}
	],
	timeEntries: [...]
}]

columns gehört zur jeweiligen Zeile unter items. Ein Klick auf die zweite Spalte (hier ) öffnet den Datensatz der Zeile (Nr). Beliebig viele Einträge im actions-Array werden nacheinander ausgeführt.

ghost – Zeilen-Balken für Neueinträge (Wrapper)

Auf Ebene jedes Eintrags in items[] (nicht in columns[]) kann optional ein Objekt ghost gesetzt werden – unabhängig pro Zeile (Ressource). Eine Zeile ohne ghost hat keinen Anlege-Streifen.

Felder:

  • title – optional, wird als Browser-Tooltip (title-Attribut) auf dem Plus-Button genutzt (z. B. „Termin anlegen“).

  • position – optional, steuert Anzeige und Layout des Ghost-Buttons pro Spalte:

    • bottomRight (Default, kann weggelassen werden) – kleiner quadratischer Button (28×28 px), absolut in der jeweiligen Spalte unten rechts; nimmt keine volle Spaltenbreite ein. Der Button sitzt außerhalb des CSS-Grids der Zeile (absolut zur Zeile), damit timeEntries in derselben Grid-Zeile bleiben wie ohne Ghost. Stacking: der Button liegt knapp über normalen Balken (z-index 11), unter Balken-Hover (10) und aktivem/Drag-Balken (100).

    • bottomLeft, topRight, topLeft – gleicher Button, andere Ecke der Spalte.

    • timeEntryklassisches Verhalten: volle Spaltenbreite in der ersten Grid-Zeile (mit 4px horizontalem Abstand wie die Termin-Balken), schiebt sichtbare Termin-Balken in die nächste Zeile (align-content: start auf der Zeile).

  • actions – Pflicht, mindestens eine Ninox-Aktion; typischerweise type: "create" mit vorbefüllten Feldern. Ein Klick auf den Plus-Button führt diese Aktionen mit dem Wert dieser Spalte aus (siehe Platzhalter).

  • styles – optional, Objekt mit CSS-Eigenschaften in camelCase (wie bei timeEntries[].styles), z. B. backgroundColor, fontColor (Textfarbe inkl. Plus-Icon über currentColor), fontSize, fontWeight, borderRadius, paddingX / paddingY (werden zu padding zusammengesetzt) oder padding (Shorthand), border, boxShadow. Legacy-color wird wie fontColor auf die Schriftfarbe gemappt; ist fontColor gesetzt, hat es Vorrang. Werden auf den Button gelegt; gridColumn / gridRow setzt das Widget für die Rasterplatzierung (überschreibt ggf. eigene Werte in styles).

Darstellung (Default bottomRight u. a.): Pro Spalte ein absolut positionierter, klick-durchlässiger Bereich über der Zeile (kein Grid-Item – verhindert Verschiebung der Balken). Der Plus-Button ist weiß, mit leichtem Schatten, Ecke per position. Spalten-Hover (Maus in der Spalte): Button wird sichtbar mit halbtransparentem weißem Hintergrund und backdrop-filter-Weichzeichnung (Frosted-Glass). Direkt über dem Button: undurchsichtiges Weiß, kein Blur – volle Lesbarkeit. Über styles kannst du z. B. backgroundColor setzen (überschreibt die Standardfarben).

Darstellung (timeEntry): Wie ein schmaler Balken über die Spaltenbreite in Zeile 1 (mit 4px Rand links/rechts wie die Termin-Balken); timeEntries der Zeile beginnen darunter.

Sichtbarkeit / Hover:

  • Alle Positionen außer timeEntry: Der Plus-Button ist standardmäßig unsichtbar und erscheint erst, wenn die Maus irgendwo in dieser Tag-/Zeitspalte ist (auch über einem Balken); die Spalte wird anhand der Mausposition auf der Zeile ermittelt. Auf Touch-Geräten (kein präzises Hover) bleiben die Ghost-Buttons sichtbar.

  • timeEntry: Für jede Spalte wird getrennt geprüft, ob ein sichtbarer Termin-Balken in die Spalte reicht. Ohne Balken: bei @media (hover: hover) ist der Ghost-Balken zunächst per Opacity verborgen, bis die Maus über die Ghost-Zelle fährt. Mit Balken in der Spalte: der Ghost-Balken bleibt sichtbar. Auf Touch bleiben Ghost-Elemente sichtbar.

Platzhalter (in beliebigen String-Werten innerhalb der Action, rekursiv):

  • ##columnValue## – Millisekundenwert der Spalte unter dem Klick: im Tag-Modus Tagesbeginn (startDate + Spaltenindex), im Uhrzeit-Modus Beginn des Zeitslots (startTime + Index × amount).

  • ##clickedDate## – Alias mit gleicher Semantik (Kompatibilität zu Calendar Grid / Week).

items: (select Mitarbeiter).[{
	id: Nr,
	ghost: {
		position: "bottomRight",
		title: "Termin anlegen",
		styles: {
			backgroundColor: "rgba(0, 0, 0, 0.08)",
			fontColor: "#333333",
			borderRadius: "6px",
			paddingY: "4px",
			paddingX: "8px"
		},
		actions: [{
			type: "create",
			tableId: "B",
			popup: true,
			changeFieldValues: [
				{ fieldId: fieldId(Nr, "Datum"), value: "##columnValue##" },
				{ fieldId: fieldId(Nr, "Bearbeiter"), value: number(Nr) }
			]
		}]
	},
	timeEntries: [...]
}]

Klicks auf Termin-Balken (timeEntries) lösen weiterhin nur die actions des jeweiligen Balkens aus, nicht ghost.actions.

Beispiel – Uhrzeitmodus: ##columnValue## ist der Beginn des geklickten Zeitslots in Millisekunden (wie bei timeFrom in den Balken).

arcCustomCalendarTimeline({
	uniqueId: "timeline-fleet-neu",
	mode: "time",
	timeSettings: {
		startTime: 6,
		duration: 12,
		amount: 0.25
	},
	columnWidth: "60px",
	rowHeight: "36px",
	height: "700px",
	dateSettings: {
		header: {
			title: {
				label: "##hour##:##minute##",
				fontSize: "11px",
				fontColor: "#333"
			}
		}
	},
	columns: [
		{ title: "Fahrzeug", width: "160px" },
		{ title: "Info", width: "80px" }
	],
	items: (select Fahrzeuge).[{
		id: Nr,
		ghost: {
			title: "Neue Fahrt",
			actions: [{
				type: "create",
				tableId: "B",
				popup: true,
				changeFieldValues: [
					{ fieldId: fieldId(Nr, "Startzeit"), value: "##columnValue##" },
					{ fieldId: fieldId(Nr, "Fahrzeug"), value: number(Nr) }
				]
			}]
		},
		timeEntries: [...]
	}]
})

Alternativ kannst du ##clickedDate## statt ##columnValue## verwenden – beide werden gleich aufgelöst.

weekStart

Starttag der Woche (0 = Sonntag, 1 = Montag, …). Beeinflusst die Sortierung der Tage.

weekStart: 1,

timeSettings – Uhrzeitmodus (Time Mode)

Wenn du statt Tagen Uhrzeiten als Spalten anzeigen möchtest, kannst du den Uhrzeitmodus aktivieren. Das ist ideal für Flottendiagramme, Schichtpläne oder Tageseinsatzplanung, bei denen die X-Achse einen einzelnen Tag darstellt.

Der Uhrzeitmodus wird automatisch aktiviert, sobald timeSettings gesetzt ist – oder explizit mit mode: "time".

arcCustomCalendarTimeline({
	uniqueId: "fleet-timeline",
	mode: "time",
	timeSettings: {
		startTime: 6,
		duration: 12,
		amount: 0.25
	},
	columnWidth: "60px",
	rowHeight: "36px",
	height: "700px",
	dateSettings: {
		header: {
			title: {
				label: "##hour##:##minute##",
				fontSize: "11px",
				fontColor: "#333"
			}
		}
	},
	columns: [
		{ title: "Fahrzeug / Tag", width: "160px" },
		{ title: "Info", width: "80px" }
	],
	items: (select Fahrzeuge).[{
		id: Nr,
		title: Bezeichnung,
		fontSize: "12px",
		fontWeight: "600",
		columns: [
			{ title: text(Bezeichnung), subtitle: "Fahrzeug" },
			{ title: "⌂" }
		],
		items: (select Einsatztage where Fahrzeug = Nr).[{
			id: Nr,
			title: Wochentag,
			fontSize: "11px",
			columns: [
				{ title: text(Wochentag) },
				{ title: "" }
			],
			timeEntries: (select Fahrten where Einsatztag = Nr).[{
				title: Route,
				timeFrom: Startzeit,
				timeTo: Endzeit,
				styles: {
					backgroundColor: text(Farbe),
					color: "#fff"
				}
			}]
		}]
	}]
})

startTime

Ab welcher Stunde die Zeitleiste beginnt (z. B. 6 = 06:00 Uhr).

startTime: 6,
startTime: 8,

duration

Wie viele Stunden angezeigt werden (z. B. 12 = 12 Stunden ab startTime).

duration: 12,
duration: 8,

amount

Spaltenintervall in Stunden. Bestimmt die Feinheit der Zeiteinteilung.

amount: 1,
amount: 0.5,
amount: 0.25,
  • 1 = Stundenspalten (08:00, 09:00, …)

  • 0.5 = 30-Minuten-Spalten (08:00, 08:30, 09:00, …)

  • 0.25 = 15-Minuten-Spalten (08:00, 08:15, 08:30, …)

Header-Platzhalter im Uhrzeitmodus

Im Uhrzeitmodus stehen zusätzliche Platzhalter für dateSettings.header.title.label zur Verfügung:

  • ##hour## – Stunde mit führender Null (06, 07, …)

  • ##minute## – Minute mit führender Null (00, 15, 30, …)

  • ##time## – Vollständige Uhrzeit (06:00, 08:15, …)

columnWidth

Breite einer Zeitspalte (Alias für dayWidth im Uhrzeitmodus).

columnWidth: "60px",
columnWidth: "80px",

timeEntries im Uhrzeitmodus

Im Uhrzeitmodus verwenden timeEntries statt dateFrom/dateTo die Felder timeFrom/timeTo (in Millisekunden ab Mitternacht):

timeEntries: [{
	title: "RVS GZ-NU-KE",
	timeFrom: 27000000,
	timeTo: 41400000,
	styles: {
		backgroundColor: "#0066cc"
	}
}]

💡 Umrechnung: Stunden × 3600000 (z. B. 07:30 = 7.5 × 3600000 = 27000000)

Drag & Drop im Uhrzeitmodus

Im Uhrzeitmodus werden beim Drag & Drop timeFrom/timeTo statt dateFrom/dateTo aktualisiert:

dragAction: {
	recordId: Nr,
	timeFrom: fieldId(Nr, "Startzeit"),
	timeTo: fieldId(Nr, "Endzeit"),
	resourceField: fieldId(Nr, "Einsatztag")
}

columns (Top-Level)

Definiert die linken Label-Spalten (neben dem Zeitbereich). Pro Eintrag im Array: title (erscheint im Header links oben), optional subtitle, optional width (CSS-Breite, z. B. "200px"). Fehlt width, gilt labelWidth (Standard 120px).

Optional: alignX: "left" "center" "right" (Standard left), alignY: "top" "center" "bottom" (Standard center) – Ausrichtung von Header und Zellinhalt in dieser Spalte. fontSize und fontWeight (CSS-Werte, z. B. "13px", "600" oder "bold") gelten für Header und Zellen dieser Spalte, sofern weder items[].columns[i] noch Item-fontSize/fontWeight gesetzt sind. Pro Zelle kann in items[].columns[i] jeweils alignX, alignY, fontSize, fontWeight überschrieben werden.

Ohne columns oder leeres Array: eine Spalte mit labelWidth.

items – Datenzeilen und Verschachtelung

Alle Ressourcen werden einheitlich items genannt. Flache Liste: items: [{ id, title, subtitle, timeEntries, … }].

Verschachtelung (eine Ebene): Ein Item kann items: [...] enthalten. Parent und Kinder werden als eigene Zeilen untereinander gerendert; die erste Label-Spalte ist bei Kindern eingerückt. Tiefer als eine Ebene ist in der Timeline aktuell nicht vorgesehen (Datenstruktur kann später erweitert werden).

items[].columns (pro Zeile): Array mit einem Eintrag pro Label-Spalte (gleiche Länge wie Top-Level columns). Felder pro Zelle: title, subtitle, optional customLayout, optional actions, optional alignX / alignY, optional fontSize / fontWeight (höchste Priorität für Typo). Danach greifen Item-fontSize/fontWeight, dann Top-Level-Spalte, dann styles. Fehlende Einträge → leere Zelle. Fallback: Fehlt columns oder die erste Zelle hat keinen Inhalt, werden title/subtitle des Items nur für die erste Spalte verwendet.

Parent mit Unterzeilen: Die Parent-Zeile hat timeEntries wie jede andere Zeile (z. B. Summen-Balken). Kinder stehen in items und haben eigene columns-Werte.

columns: [
	{ title: "Fahrzeug / Tag", width: "160px" },
	{ title: "Info", width: "80px" }
],
items: [{
	id: "v1",
	title: "A-MS 419",
	subtitle: "GPS",
	columns: [
		{ title: "A-MS 419", subtitle: "GPS" },
		{ title: "⌂", subtitle: "Fzg." }
	],
	timeEntries: [{ title: "Tagesblock", timeFrom: ... }],
	items: [{
		id: "v1-mo",
		title: "Mo",
		columns: [{ title: "Mo" }, { title: "" }],
		timeEntries: [...]
	}]
}]

collapsible – Gruppen ein- und ausklappen

Wenn du hierarchische Einträge (items mit Unter-items) nutzt, kannst du die Parent-Zeile (z. B. Team- oder Fahrzeugname) per Widget-Prop ein- und ausklappen. Der Zustand wird pro uniqueId im Browser persistiert (localStorage).

Konfiguration

arcCustomCalendarTimeline({
	uniqueId: "timeline-teams-" + Nr,
	collapsible: {
		enabled: true,
		iconPosition: "left",
		iconCollapse: "",
		iconExpand: ""
	},
	columns: [ { title: "Team / Person", width: "200px" }, { title: "Termine", width: "120px" } ],
	items: [ /* Parent mit items */ ]
})

Property

Bedeutung

enabled

true: Toggle-Icon in der ersten Label-Spalte der Parent-Zeile; Klick klappt alle Unterzeilen ein/aus.

iconPosition

(Optional) "left" oder "right" – Icon vor/neben dem Text (Standard: left).

iconCollapse

(Optional) HTML/SVG für aufgeklappt (Standard: Chevron unten).

iconExpand

(Optional) HTML/SVG für eingeklappt (Standard: Chevron rechts).

  • Gilt für Zeilen mit items (Kinder). id des Parent-Items muss eindeutig sein (Klappzustand).

  • styles am Parent/Kind wirken auf die gesamte Label-Zeile (Hintergrund der Zellen).

Mit dem Custom Calendar Timeline bringst du ressourcenbasierte Planung und volle Flexibilität in deine Ninox-Datenbank – ideal für Einsatzplanung, Kapazitätsübersicht, Ressourcenmanagement und Flottendiagramme.

Happy Widgeting 🥳

Arc Rider Ventures GmbH

© 2025