# Was ist ein Widget?
Source: https://www.arc-rider.com/docs/intro/was-sind-widgets
# Was ist ein Widget in React?
Ein arcWidget ist eine konfigurierbare Komponente für eine Geschäftsanwendung: zum Beispiel eine Tabelle, ein Kalender oder ein Kanban-Board. Du übergibst die Darstellung als `data` und verbindest Ereignisse über Callback-Props mit deiner Anwendung.
## Ein vollständiges Beispiel
```tsx
import {
ArcWidgetBadge,
ArcWidgetTable,
type ArcWidgetTableRow,
} from '@arcrider/arcwidgets-react';
import { useState } from 'react';
const lang = document.documentElement.lang.startsWith('en')
? 'en'
: document.documentElement.lang.startsWith('es')
? 'es'
: 'de';
const requested = new URLSearchParams(location.search).get('data');
const pack = requested === 'empty' || requested === 'busy' ? requested : 'demo';
const t = (de: string, en: string, es: string) =>
lang === 'en' ? en : lang === 'es' ? es : de;
const statusTone = {
open: { color: '#d97706', label: t('Offen', 'Open', 'Abierto') },
work: { color: '#0f766e', label: t('In Arbeit', 'In progress', 'En curso') },
done: { color: '#15803d', label: t('Erledigt', 'Done', 'Hecho') },
wait: { color: '#be123c', label: t('Wartet', 'Waiting', 'En espera') },
};
type Status = keyof typeof statusTone;
type Order = {
id: string;
title: string;
amount: number;
status: Status;
items?: Order[];
};
const demoOrders: Order[] = [
{
id: 'a',
title: t('Auftrag Nordhafen', 'North Harbor order', 'Pedido Puerto Norte'),
amount: 240,
status: 'work',
items: [
{
id: 'a-1',
title: t('Vorbereitung', 'Preparation', 'Preparación'),
amount: 80,
status: 'done',
},
{
id: 'a-2',
title: t('Umsetzung', 'Delivery', 'Entrega'),
amount: 160,
status: 'work',
},
],
},
{
id: 'b',
title: t('Auftrag Westpark', 'Westpark order', 'Pedido Westpark'),
amount: 80,
status: 'open',
},
{
id: 'c',
title: t('Auftrag Innenstadt', 'Downtown order', 'Pedido centro'),
amount: 420,
status: 'wait',
},
{
id: 'd',
title: t('Auftrag Werkstatt', 'Workshop order', 'Pedido taller'),
amount: 150,
status: 'done',
},
];
const SITES = [
'Nordhafen',
'Westpark',
'Innenstadt',
'Werkstatt',
'Messe',
'Logistik',
'Campus',
'Hafen Süd',
];
const STATUSES: Status[] = ['open', 'work', 'done', 'wait'];
const busyOrders: Order[] = Array.from({ length: 800 }, (_, index) => ({
id: `busy-${index + 1}`,
title: `${t('Auftrag', 'Order', 'Pedido')} ${SITES[index % SITES.length]} ${String(index + 1).padStart(3, '0')}`,
amount: 50 + ((index * 47) % 9950),
status: STATUSES[index % STATUSES.length],
}));
const orders =
pack === 'empty' ? [] : pack === 'busy' ? busyOrders : demoOrders;
export default function Example() {
const [selected, setSelected] = useState(
t('Keine Auswahl', 'Nothing selected', 'Sin selección'),
);
const statusMark = (status: Status) =>
pack === 'busy' ? (
{statusTone[status].label}
) : (
{t(
'Leerer Zustand — über Daten auf Voll oder Demo wechseln.',
'Empty state — switch Data to Full or Demo.',
'Estado vacío — cambia Datos a Completo o Demo.',
)}
{selected} {selected} {selected} {selected}`timeSlotWidth: "250px" ,`
→ Jeder Wochentag wird mit **250 Pixeln Breite** angezeigt.
📌 **Hinweis:**
- Der Standardwert liegt bei ca. **100 px**, kann aber je nach Layout-Anforderungen angepasst werden.
- Eine höhere Zahl sorgt für **mehr Platz pro Tag**, besonders hilfreich bei vielen Einträgen.
### timeSlotHeight – Höhe der Stundenzeilen festlegen
Mit dem Parameter `**timeSlotHeight**` bestimmst du, **wie hoch jede einzelne Stunde im Kalender dargestellt wird**.
`timeSlotHeight: "80px",`
→ Jede Stunde erhält eine **Höhe von 50 Pixeln** in der vertikalen Zeitachse.
📌 **Hinweis:**
- Der Standardwert liegt bei etwa **40 px**.
- Ein höherer Wert sorgt für **mehr Raum pro Stunde.**
### allDayHeight – Höhe der Zeile für Ganztagseinträge
Mit dem Parameter `allDayHeight` legst du fest, wie hoch die obere Zeile für Ganztagseinträge dargestellt wird.
→ Die Zeile für Ganztagseinträge wird mit 60 Pixeln Höhe angezeigt.
📌 Hinweis:
- Standardmäßig liegt die Höhe bei ca. 40 px.
- Ein höherer Wert schafft mehr Platz für mehrere Ganztagseinträge oder größere Textdarstellungen.
### styles – individuelles Styling für deinen Kalender
Mit `**styles**` kannst du das **Aussehen deines Kalenders anpassen** – hier kannst du aktuell die Hintergrundfarbe deines Kalenders individualisieren.
### createNew – neue Termine durch Ziehen erstellen
Mit dem Parameter `**createNew**` aktivierst du die Möglichkeit, **neue Termine direkt durch Aufziehen eines Zeitbereichs im Kalender zu erstellen**.
🖱️ **So funktioniert’s:**
- Du klickst in einen freien Bereich des Kalenders, **ziehst mit gedrückter Maustaste**, und lässt wieder los.
- Dabei wird automatisch ein neuer Termin erstellt – inklusive **Startzeit und Dauer**.
📌 **Hinweis:**
- Alle Feldbezüge (`dateFrom`, `dateTo`, `timeFrom`, `timeTo`) müssen mit den **Field IDs** der entsprechenden Felder aus deiner **Kalendereintrags-Tabelle (**`**tableId**`**)** übereinstimmen.
- Du kannst die Field IDs ganz einfach mit der Ninox-Funktion `fieldId()` herausfinden und direkt in die Konfiguration übernehmen.
**💡 **Tipp: Ideal für eine schnelle und intuitive Terminplanung – z. B. bei Schichten, Buchungen oder Raumvergabe.
Optional kannst du mit **`changeFieldValues`** Zusatzfelder im neuen Datensatz setzen – z. B. Cache- oder Trigger-Felder:
```javascript
createNew: {
tableId: "B",
dateFrom: fieldId(Nr, "Datum von"),
timeFrom: fieldId(Nr, "Von"),
dateTo: fieldId(Nr, "Datum bis"),
timeTo: fieldId(Nr, "Bis"),
changeFieldValues: [{
fieldId: fieldId(Nr, "CacheTrigger"),
value: now()
}]
}
```
Optional **`dataBag`**: nach erfolgreichem Anlegen denselben JSON-State-Topf patchen wie bei Button-Create. Läuft nur, wenn der Datensatz wirklich angelegt wurde. `##newRecordId##` wird durch die ID des neuen Termins ersetzt. Typisch: Auswahl leeren, damit der Ghost verschwindet.
```javascript
createNew: {
tableId: "B",
dateFrom: fieldId(Nr, "Datum von"),
timeFrom: fieldId(Nr, "Von"),
dateTo: fieldId(Nr, "Datum bis"),
timeTo: fieldId(Nr, "Bis"),
item: if Auswahl then {
/* Ghost */
},
dataBag: {
recordId: Nr,
fieldId: fieldId(Nr, "helper_uiState"),
base: helper_uiState,
patch: { auswahlId: null }
}
}
```
`null` im Patch löscht den Key. Die neue Termin-ID mit ablegen: `patch: { auswahlId: null, lastTerminId: "##newRecordId##" }`. Syntax: [`data-bag-actions`](../patterns/data-bag-actions.md).
📌 **Persistenz / React:** Create läuft über `{ type: "create", tableId, changeFieldValues, setNewRecordId?, dataBag?, popup?, fullscreen? }`. React: dieselbe Action über `onAction` verarbeiten.
### createNew.item – vorgebauten Termin per Klick setzen
Mit **`createNew.item`** legst du einen **fertigen Termin** in den Kalender, statt einen Zeitraum aufzuziehen. Typischer Fall: Auswahl in einer Seitenleiste (Typ, Dauer, Phasen) schreibt ins Formelfeld — der Kalender zeigt einen **Ghost**, der der Maus folgt. Ein Klick setzt ihn.
Ohne `item` bleibt das heutige Aufziehen. Mit `item` (und gesetzter `duration` oder Segment-Dauern) ist Aufziehen aus — außer **`active`** ist `false`.
Den Platzieren-Modus schaltest du **außerhalb** des Kalenders: ein Ja/Nein-Feld, eine Checkbox in der Seitenleiste oder die Auswahl selbst.
```javascript
createNew: {
tableId: "B",
dateFrom: fieldId(first(Termine), "Datum von"),
timeFrom: fieldId(first(Termine), "Von"),
dateTo: fieldId(first(Termine), "Datum bis"),
timeTo: fieldId(first(Termine), "Bis"),
changeFieldValues: if Auswahl then [{
fieldId: fieldId(first(Termine), "Typ"),
value: number(Auswahl)
}] else [{
fieldId: fieldId(first(Termine), "CacheTrigger"),
value: now()
}],
item: if Auswahl then {
active: Platzieren,
title: Auswahl.Name,
styles: {
backgroundColor: color(Auswahl),
color: "#fff",
borderRadius: "8px"
},
segments: [{
title: "Anfahrt",
linked: false,
offset: { days: -1 },
timeFrom: time(16, 0),
timeTo: time(17, 0)
}, {
title: "Kernarbeit",
timeFrom: time(11, 0),
timeTo: time(14, 0),
splits: [{
title: "Pause",
timeFrom: time(12, 30),
timeTo: time(13, 0)
}]
}, {
title: "Abfahrt",
linked: false,
offset: { days: 1 },
timeFrom: time(8, 0),
timeTo: time(9, 0)
}]
}
}
```
**Felder in `item`:**
- **`active`** — optional. `true` oder weggelassen: Ghost an, Klick setzt. `false` / leer: Ghost aus, Aufziehen wieder an. Bindet an ein Feld außerhalb des Widgets (z. B. `Platzieren`).
- **`duration`** — relative Länge. Reine Duration-Phasen hängen sich an die vorherige Follow-Phase (erste: an die Maus).
- **`timeFrom` / `timeTo`** — Uhr auf dem Anker-Tag (Tag unter der Maus). Pinnt **diese** Phase. Mit `linked: false` bleibt sie Satellit — der Follow-Strang folgt weiter der Maus. Ohne `linked: false` hängt die Kette an dieser Uhr.
- **`linked`** — wie bei gespeicherten Segmenten. `false` = Satellit (Anfahrt/Abfahrt). Standard `true`.
- **`offset`** — `{ days, time }`, beide optional und signed. `days: -1` Vortag, `days: 1` Folgetag. `time: -time(1, 0)` eine Stunde früher (Maus / vorige Phase). Kombinierbar: `{ days: -1, time: -time(1, 0) }`. Mit fester Uhr wählt `days` den Kalendertag; `time` schiebt die Uhr mit. `days: 0` hält die Phase auf dem Anker-Tag, wenn die Phase davor auf einem anderen Tag lag. Nicht verwechseln mit dem flachen `offset: time(…)` in **`splits`**.
- **`dateFrom` / `dateTo`** — echte Datumswerte (wie bei `timeEntries`), wenn die Phase fest auf einem Kalendertag liegt.
- **`splits`** — eine Ebene Unterbrechungen in dieser Phase (typisch Pause). Beide Kern-Stücke behalten den Phasen-Titel. Siehe unten.
- **`title` / `subtitle` / `value` / `customLayout` / `styles` / `insetBorder`** — wie ein `timeEntry`.
Auflösung pro Phase, erste passende Regel: festes Datum + Uhr → Uhr (`timeFrom`/`timeTo`, optional `offset.days` / `offset.time`) → `offset` + `duration` → nur `duration`. `linked: false` + Uhr aktualisiert den Follow-Cursor nicht.
**Umschalttaste** beim Ghost: ganze Kette inkl. Satelliten mitverschieben. Loslassen: Satelliten zurück auf ihre Uhr. Klick speichert das, was du siehst.
Kern unter die Maus, Satellit mit fester Uhr:
```javascript
segments: [{
title: "Anfahrt",
linked: false,
offset: { days: -1 },
timeFrom: time(16, 0),
timeTo: time(17, 0)
}, {
title: "Vorarbeit",
offset: { days: 0 },
duration: time(0, 30)
}, {
title: "Kern",
duration: time(3, 0)
}, {
title: "Abfahrt",
offset: { days: 1 },
duration: time(1, 0)
}]
```
Ohne feste Uhren (folgt der Maus):
```javascript
segments: [{
title: "Anfahrt",
offset: { days: -1, time: -time(1, 0) },
duration: time(1, 0)
}, {
title: "Kernarbeit",
offset: { days: 0 },
duration: time(3, 0),
splits: [{
title: "Pause",
offset: time(1, 30),
duration: time(0, 30)
}]
}, {
title: "Abfahrt",
offset: { days: 1 },
duration: time(1, 0)
}]
```
Sollen die Stücke **anders heißen** (Teil 1 / Pause / Teil 2): drei `segments` mit `duration`, kein `splits`.
Der Ghost darf über mehrere Tage gehen. Pro Phase läuft die Uhrzeit mit (`##entrytime##`).
Der Ghost nimmt eine **eigene Spur** ein — bestehende Termine zur gleichen Zeit rücken zur Seite, wie bei zwei echten Einträgen. Maus weg: die anderen gehen wieder auf volle Breite.
Ausblenden: **Auge** in der Toolbar oder **Esc**. Wieder einblenden nur über das Auge. Die Seitenleisten-Auswahl bleibt.
Gespeichert wird **ein** neuer Datensatz auf dieselben vier `createNew`-Felder: Von der ersten Phase bis Bis der letzten. `changeFieldValues` und `dataBag` wie bei Aufziehen.
Leere Auswahl: `item` weglassen (kein `else ""`). Dann gilt wieder Aufziehen.
## timeEntries – deine Kalendereinträge im Detail
Der `timeEntries`-Block legt fest, welche konkreten Zeiteinträge im Kalender angezeigt werden.
Hier bestimmst du, wie jeder einzelne Eintrag dargestellt wird – von Titel über Farbe bis hin zu eigenen Layouts. So kannst du deine Kalenderkarten ganz individuell gestalten und an deinen Anwendungsfall anpassen. Dabei wird automatisch ein neuer Termin erstellt – inklusive Startzeit und Dauer.
📌 **Hinweis:**
Für die Darstellung der **Echtzeit-Veränderung** der Termindaten (beim Erstellen oder auch beim Verschieben eines Termins) kann der Platzhalter **"##entrytime##"** überall in `timeEntries` eingesetzt werden, wo die **Terminzeit** angezeigt werden soll, also im title, subtitle, value, aber auch in allen Widgets, die hier eingesetzt werden. Wie zB. wenn ein `customLayout` für das Design des timeEntries genutzt wird oder in Buttons, Inputs usw.
Zum Beispiel innerhalb eines Layout-Blocks im Text Widget:
```javascript
...
value: arcCustomText({
alignY: "top",
fontSize: typo.paragraph3.fontSize,
fontWeight: "400",
fontColor: appointmentType.colorShade,
value: "##entrytime##"
}),
...
```
➡️ Alle Parameter für die timeEntries findest du im **folgenden Abschnitt**.
### title – Titel des Kalendereintrags
Mit dem Parameter `title` legst du fest, was in der ersten Zeile des Kalendereintrags angezeigt wird.
🔧 Zwei Optionen:
- Eigener Titel:
Gib ein Feld oder einen Text an, der als Titel im Eintrag angezeigt werden soll.
- Keine Angabe (`"`):
Lässt du das Feld leer (`"`), wird stattdessen die Uhrzeit des Termins angezeigt.
styles: {
backgroundColor: "rgba(0,0,255,0.01)"
},
📌 Hinweis:
- Wenn du keinen Titel vergibst und stattdessen die Uhrzeit angezeigt wird, wird diese Uhrzeit bei Änderungen live aktualisiert, z. B. beim Ziehen oder Verlängern eines Eintrags.
- **Leerer Wert:** Wenn `title` leer oder nur Leerzeichen enthält, wird die Titel-Zeile ausgeblendet – der Platz geht an `subtitle` oder `##entrytime##`.
### subtitle – Beschreibung unter dem Titel
Mit dem Parameter `subtitle` kannst du eine zusätzliche Zeile unter dem Titel im Kalendereintrag anzeigen lassen.
Typischerweise wird hier eine kurze Beschreibung oder ein ergänzender Hinweis wie z. B. der Grund des Termins oder ein Status hinterlegt.
title: Kalendertitel, // Ninox Feld
title: "", // Default: Gibt die Uhrzeit des Kalendereintrages an
→ Zeigt z. B. den Inhalt des Feldes *„Description“* unter dem Titel im Kalendereintrag an.
📌 **Hinweis:**
- Der `subtitle` ist optional und wird **nur angezeigt, wenn ein entsprechender Feldwert vorhanden ist**.
- **Leerer Wert:** Wenn `subtitle` leer oder nur Leerzeichen enthält, wird die Subtitle-Zeile ausgeblendet – der Platz geht an den Titel. Titel und Subtitle werden symmetrisch behandelt: Wenn beide leer sind, wird nur `##entrytime##` angezeigt.
### value – Zusatzinformationen oder interaktive Felder
Der Parameter `value` steht für die dritte Zeile deines Kalendereintrags. Hier kannst du weitere Informationen anzeigen oder sogar interaktive Elemente wie Eingabefelder oder Auswahllisten integrieren.
🔧 Einsatzmöglichkeiten:
- Zusätzliche Informationen wie z. B. Ort, Status, Verantwortlicher
- Interaktive Widgets direkt im Kalendereintrag – z. B.:subtitle: Description, // Ninox Feld
subtitle: "" // Textfeld
Ein Select-Feld, um den zuständigen Mitarbeiter auszuwählen - Ein Input-Feld für kurze Notizen oder Rückmeldungen
value: "",
value: Kommentar, → Zeigt den Inhalt des Feldes *„Kommentar“* in der dritten Zeile an.
📌 **Hinweis:**
- `value` ist **optional**, erweitert aber die Darstellung deiner Einträge um eine zusätzliche Ebene.
- In Verbindung mit eingebetteten Widgets entsteht ein **interaktives Kalendererlebnis direkt im Eintrag**.
💡 **Tipp: **Nutze `value`, um wichtige Zusatzinfos sofort sichtbar zu machen – oder um deine Nutzer direkt im Kalender arbeiten zu lassen.
### customLayout – vollständiges individuelles Layout für Kalendereinträge
Mit dem Parameter `**customLayout**` kannst du ein **komplett eigenes Layout** für deine Kalendereinträge definieren.
Wenn du diesen Parameter nutzt, werden die Standardfelder `**title**`, `**subtitle**` und `**value**` **nicht mehr angezeigt**. Stattdessen kannst du frei gestalten, wie dein Kalendereintrag aussieht – mit beliebigen Widgets.
`customLayout: "",`💡 Tipp: Nutze `customLayout`, wenn du eine komplett individuelle Darstellung brauchst, z. B. mit eigenen Farben, Icons, Struktur oder logischen Bedingungen. ### dateFrom, dateTo, timeFrom, timeTo – Start- und Endzeit je Eintrag Mit diesen vier Parametern gibst du an, **welche Felder aus deinem Zeiteintrags-Datensatz** für **Datum und Uhrzeit** verwendet werden sollen. ```javascript dateFrom: 'Date From', dateTo: 'Date To', timeFrom: 'Time From', timeTo: 'Time To', ```  📌 Hinweis: - Es müssen echte Datums- und Zeitfelder aus Ninox angegeben werden. - Die Kombination aus Datum + Uhrzeit bestimmt die Position und Länge des Kalendereintrags. ⚠️ Wichtig zu wissen: - Fehlt `dateTo`, wird automatisch der Tag aus `dateFrom` verwendet (und umgekehrt). - Fehlen beide Datumsfelder, wird der Eintrag nicht angezeigt. - Fehlt `timeTo`, wird automatisch `timeFrom + 5 Minuten` verwendet. - Fehlt `timeFrom`, wird `timeTo - 5 Minuten` angenommen. → So ist eine minimale Anzeige trotzdem möglich. 💡 Tipp: Wenn du nur Tages-Einträge ohne genaue Uhrzeiten darstellen willst, kannst du auch nur mit Datumsfeldern arbeiten – der Kalender stellt den Eintrag dann als Ganztagstermin dar. ### styles – individuelles Styling für Kalendereinträge Mit dem Parameter `**styles**` kannst du das **Aussehen einzelner Kalendereinträge** anpassen – z. B. Farben, Innenabstand (Padding) oder abgerundete Ecken (Border-Radius).
styles: {
backgroundColor: color(Person),
color: "#fff",
borderRadius: "8px",
padding: ""
},📌 **Unterstützte Eigenschaften:**
- `backgroundColor`: Hintergrundfarbe der Karte
- `color`: Schriftfarbe
- `padding`: Innenabstand (z. B. `"6px"`)
- `borderRadius`: Abrundung der Ecken (z. B. `"8px"`)
💡 Tipp: Mit `styles` kannst du deine Einträge **visuell voneinander abheben**, etwa nach Status, Typ oder Priorität – auch dynamisch, wenn du Felder deiner Datenquelle als Wert verwendest.
### dragAction – Felder für das Verschieben von Einträgen festlegen
Mit dem Parameter `**dragAction**` definierst du, **welche Datums- und Zeitfelder beim Ziehen oder Verschieben eines Eintrags aktualisiert werden sollen**.
So wird sichergestellt, dass der neue Zeitpunkt im **Datensatz gespeichert** wird, wenn ein Eintrag im Kalender bewegt wird.
```javascript
dragAction: {
recordId: Nr,
dateFrom: fieldId(Nr, "Datum von"),
timeFrom: fieldId(Nr, "Von"),
dateTo: fieldId(Nr, "Datum bis"),
timeTo: fieldId(Nr, "Bis"),
changeFieldValues: [{
fieldId: fieldId(Nr, "CacheTrigger"),
value: now()
}]
}
```
📌 Hinweis:
- Gib hier die korrekten Feld-IDs der einzelnen Felder in der Zeiteintrags-Tabelle an.
- Du kannst hier die Ninox Funktion `fieldId()` benutzen.
- **changeFieldValues**: Optional. Array von `{ fieldId, value }` – wird **nach** einem erfolgreichen Drag geschrieben (Datum/Zeit haben sich geändert). Typischer Einsatz: Cache- oder Trigger-Felder aktualisieren. Aufbau wie bei `createNew.changeFieldValues` und wie in der Timeline.
⚠️ Wichtig: `recordId` und die Datums-/Zeitfelder sind zwingend erforderlich, wenn du möchtest, dass Einträge per Drag & Drop verschoben oder in ihrer Dauer verändert werden können.
📌 **Persistenz / React:** Beim Speichern nach Drag sendet das Widget Action-Performer-Actions `{ type: "update", recordId, field, value }` (ein Action pro geändertes Feld). In Ninox mappt der Adapter das auf `database.update`. Im React-Adapter landet das in `onAction` — ohne globales `database`.
### clickAction – Einträge per Klick in Ninox öffnen
Mit dem Parameter `**clickAction**` ermöglichst du es, dass der Nutzer durch einen Klick auf einen Kalendereintrag direkt den **zugehörigen Datensatz in Ninox öffnen** kann.
clickAction: {
type: "popup",
recordId: Nr
}📌 Bedeutung der Parameter:
- `type: "popup"` – Öffnet den Datensatz in einem Popup-Fenster (weitere Typen können ggf. folgen).
- `recordId`: Das Feld im Kalendereintrag, das den Primärschlüssel (Record ID) enthält – meist `"Nr"`.
⚠️ Wichtig:
- Das angegebene Feld bei `recordId` muss die echte Ninox-Datensatz-ID enthalten.
- Wird kein gültiger Wert gefunden, passiert beim Klick nichts.
### insetBorder – vertikale Inset-Linie im Kalendereintrag
Mit `**insetBorder**` kannst du eine **dezente vertikale Linie** am linken (oder rechten) Rand jedes Kalendereintrags anzeigen lassen – ähnlich dem Stil des Apple Kalenders. Das Feature ist **standardmäßig aktiv** und muss nur deaktiviert werden, wenn du es nicht möchtest.
```javascript
insetBorder: {
enabled: true, // Standard: true – false blendet die Linie aus
position: "left", // "left" (Standard) oder "right"
width: "2px", // Breite der Linie
stroke: "dotted", // "solid", "dashed" oder "dotted"
color: "#ffffff" // Farbe (Standard: Schriftfarbe des Eintrags)
}
```
📌 **Hinweis:**
- Die Linie reagiert automatisch auf den `borderRadius` des Eintrags und wird kürzer, je höher der Radius ist.
- Bei zusammenhängenden Segmenten (Kette) wird sie an Übergängen nahtlos fortgesetzt.
- Du kannst `insetBorder` sowohl auf Ebene des `timeEntry` als auch pro Segment definieren – das Segment-Level überschreibt das Entry-Level.
### segments – Termine in Phasen unterteilen
Mit dem Parameter `**segments**` kannst du einen Termin in mehrere **zusammengehörige Zeitphasen** aufteilen – z. B. Anfahrt, Vorarbeit, Kernarbeit, Pause, Nacharbeit und Abfahrt.
Jedes Segment ist ein eigenständiger Eintrag mit eigener Zeit und eigenem Design, kann aber mit anderen Segmenten **verknüpft** werden, sodass sie beim Verschieben gemeinsam bewegt werden.
```javascript
timeEntries: (select Termine).[{
title: Bezeichnung,
dateFrom: 'Datum von',
dateTo: 'Datum bis',
timeFrom: Von,
timeTo: Bis,
styles: {
backgroundColor: "#5b6af0",
color: "#fff",
borderRadius: "8px"
},
dragAction: {
recordId: Nr,
dateFrom: fieldId(Nr, "Datum von"),
timeFrom: fieldId(Nr, "Von"),
dateTo: fieldId(Nr, "Datum bis"),
timeTo: fieldId(Nr, "Bis")
},
segmentSettings: {
dayJump: {
threshold: time(22, 0),
startTime: time(7, 0)
}
},
segments: [{
title: "Anfahrt",
dateFrom: 'Seg Datum von',
dateTo: 'Seg Datum bis',
timeFrom: SegVon,
timeTo: SegBis,
styles: {
backgroundColor: "#3b4fd0",
color: "#fff"
},
linked: false,
segmentType: "travel",
dragAction: {
recordId: Nr,
dateFrom: fieldId(Nr, "Seg Datum von"),
timeFrom: fieldId(Nr, "SegVon"),
dateTo: fieldId(Nr, "Seg Datum bis"),
timeTo: fieldId(Nr, "SegBis")
}
}, {
title: "Kernarbeit",
dateFrom: 'Kern Datum von',
dateTo: 'Kern Datum bis',
timeFrom: KernVon,
timeTo: KernBis,
styles: {
backgroundColor: "#5b6af0",
color: "#fff"
},
linked: true
}]
}]
```
#### Felder je Segment
| Parameter | Typ | Beschreibung |
|---|---|---|
| `title` | Text | Titel des Segments |
| `subtitle` | Text | Untertitel (optional) |
| `value` | Text/Widget | Zusatzzeile oder eingebettetes Widget |
| `dateFrom`, `dateTo` | Datum | Start- und Enddatum |
| `timeFrom`, `timeTo` | Zeit | Start- und Endzeit |
| `styles` | Objekt | Wie beim `timeEntry` – überschreibt Parent-Styles |
| `linked` | Boolean | `true` = Segment wird mit anderen `linked`-Segmenten gemeinsam verschoben (Standard: `true`) |
| `segmentType` | Text | Frei wählbarer Typ-Bezeichner (z. B. `"travel"`, `"core"`) |
| `insetBorder` | Objekt | Inset-Linie für dieses Segment (überschreibt Entry-Level) |
| `dragAction` | Objekt | Felder für Drag & Drop – Aufbau identisch mit `timeEntry.dragAction` |
| `segmentSettings` | Objekt | Segment-Einstellungen für dieses einzelne Segment (z. B. `dayJump`) |
| `splits` | Array | Eine Ebene Unterbrechungen in dieser Phase (z. B. Pause). Das Widget macht daraus: Teil 1 → Pause → Teil 2. |
💡 **Tipp:** Felder die nicht im Segment gesetzt werden, erben den Wert des übergeordneten `timeEntry`. So reicht es, nur die Unterschiede pro Segment zu definieren.
#### splits – Pause in einer Phase
`splits` teilt **eine** Phase (meist Kernarbeit), ohne dass du in Ninox „Kern Teil 1 / Kern Teil 2“ anlegen musst. Keine weitere Verschachtelung — nur diese eine Ebene.
```javascript
{
title: "Kernarbeit",
dateFrom: 'Datum von',
dateTo: 'Datum bis',
timeFrom: time(11, 0),
timeTo: time(14, 0),
splits: [{
title: "Pause",
timeFrom: time(12, 30),
timeTo: time(13, 0),
styles: {
backgroundColor: "#94a3b8"
}
}]
}
```
Oder relativ zum Phasen-Start: `offset: time(1, 30)`, `duration: time(0, 30)`.
Mehrere Splits sind erlaubt; sie werden nach Startzeit sortiert und auf den Phasen-Zeitraum begrenzt. Kern-Stücke behalten Look und Titel der Phase, die Pause ihren eigenen.
Unterschiedliche Titel für die Kern-Stücke (Teil 1 / Teil 2): **drei `segments`**, kein `splits` — z. B. `duration: time(1, 30)`, Pause, `duration: time(1, 30)`.
#### linked – Segmente verknüpfen oder entkoppeln
Mit `linked: true` (Standard) wird ein Segment Teil der **verbundenen Kette**. Beim Verschieben im Kalender bewegen sich alle verknüpften Segmente gemeinsam.
Mit `linked: false` wird ein Segment **entkoppelt** – z. B. Anfahrt und Abfahrt, die auf einem anderen Tag liegen können und unabhängig verschoben werden sollen. Im **Placement-Ghost** (`createNew.item`) gilt dasselbe: eine Uhr auf einem `linked: false`-Satelliten nagelt den Kern nicht fest.
#### Toolbar
Unten rechts sitzt eine **kleine Toolbar**. Sie ist **immer sichtbar**, außer `segmentSettings.toolbar` ist `false`.
**Schloss** — Segmente gemeinsam oder einzeln verschieben:
- **Gesperrt** (Schloss zu): Alle Segmente des `timeEntry` werden gemeinsam verschoben
- **Einzeln** (Schloss offen): `linked`-Segmente bilden eine Gruppe, `linked: false`-Segmente können unabhängig verschoben werden
Ohne Segmente ist das Schloss inaktiv. Hover zeigt einen kurzen Hinweis.
**Auge** — nur bei `createNew.item`: Ghost ein- und ausblenden. **Esc** blendet aus. Wieder einblenden nur über das Auge. Ausgeblendet gilt wieder Aufziehen, ohne die Auswahl in der Seitenleiste zu ändern.
💡 **Shift-Taste:** Während eines Drags oder beim **Placement-Ghost** hältst du **Shift**, um den Lock umzukehren: gespeicherte Satelliten bzw. Ghost-Satelliten wandern mit. Loslassen setzt Ghost-Satelliten auf ihre Uhr zurück. Das Schloss-Icon zeigt den Zustand live.
💡 **Shift + Resize:** Wenn du die Shift-Taste hältst und an einem Segment-Rand ziehst (oben/unten), verschieben sich alle angrenzenden Segmente mit – der **Gesamtblock wird größer**, statt dem Folge-Segment Zeit wegzunehmen.
## segmentSettings – Globale Einstellungen für Segmente
Der optionale Parameter `**segmentSettings**` auf Ebene des gesamten Widgets steuert das **Standardverhalten aller Segmente** – z. B. ob Gruppen standardmäßig gesperrt sind oder ob die Toolbar angezeigt wird.
```javascript
arcCustomCalendarWeek({
// ...
segmentSettings: {
lockGroups: true,
edgeMagnetism: true,
toolbar: true,
dayJump: {
threshold: time(22, 0),
startTime: time(7, 0)
}
},
timeEntries: [...]
})
```
📌 **Parameter im Überblick:**
| Parameter | Typ | Beschreibung |
|---|---|---|
| `lockGroups` | Boolean | `true` = Segmente werden standardmäßig gemeinsam verschoben (Standard: `true`) |
| `edgeMagnetism` | Boolean | `true` = Beim Resize bleibt die gemeinsame Grenze angrenzender Segmente synchron (Standard: `true`) |
| `toolbar` | Boolean | `false` = Toolbar ausblenden. Sonst immer sichtbar (Schloss + Auge bei `createNew.item`) |
| `dayJump.threshold` | Zeit | Ab dieser Uhrzeit springt ein Segment beim Drag auf den nächsten Tag |
| `dayJump.startTime` | Zeit | Startzeit am neuen Tag nach dem Sprung |
⚠️ **Priorität:** Einstellungen auf `timeEntry`-Ebene (`segmentSettings` innerhalb eines Eintrags) überschreiben die globalen `segmentSettings`. Einstellungen auf Segment-Ebene überschreiben wiederum die Entry-Ebene.
---
Mit dem Custom Calendar Week bringst du strukturierte Planung und volle Flexibilität in deine Ninox-Datenbank – individuell anpassbar, intuitiv bedienbar und bereit für deinen Alltag.
Happy Widgeting 🥳
---
# Custom Calendar Grid
Source: https://www.arc-rider.com/docs/widgets/custom-calendar-grid
# CalendarGrid in React
Termine in einer Monatsübersicht anzeigen und Tage oder Einträge auswählen.
Referenz für `@arcrider/arcwidgets-react@0.1.0-alpha.5`. [Installation](/docs/react/intro/setup-react) · [Alle Komponenten](/docs/react/intro/react-components).
## Vollständiges Beispiel
```tsx
import { ArcWidgetCalendarGrid } from '@arcrider/arcwidgets-react';
import { useState } from 'react';
const lang = document.documentElement.lang.startsWith('en')
? 'en'
: document.documentElement.lang.startsWith('es')
? 'es'
: 'de';
const requested = new URLSearchParams(location.search).get('data');
const pack = requested === 'empty' || requested === 'busy' ? requested : 'demo';
const t = (de: string, en: string, es: string) =>
lang === 'en' ? en : lang === 'es' ? es : de;
const locale = lang === 'en' ? 'en-US' : lang === 'es' ? 'es-ES' : 'de-DE';
const chrome = {
backgroundColor: '#ffffff',
fontColor: '#18181b',
mutedColor: '#52525b',
subtleColor: '#71717a',
borders: { color: '#e4e4e7', outer: true, columns: true, rows: true },
};
const look = (backgroundColor: string, color: string) => ({
backgroundColor,
color,
fontColor: color,
});
type Entry = {
id: string;
title: string;
dateFrom: number;
dateTo: number;
timeFrom: number;
timeTo: number;
clickable: true;
draggable: true;
styles: ReturnType{selected}
> ); } ``` ## Features ausprobieren ### Monatsnavigation, Tagesauswahl und JSX Wechsle auch über den Jahreswechsel und wähle einen Tag aus. Der Beispieltermin bleibt am 17. September 2026; andere Monate sind bewusst leer. ```tsx import { useState } from 'react'; import { ArcWidgetCalendarGrid, ArcWidgetBadge, } from '@arcrider/arcwidgets-react'; export default function Example() { const [offset, setOffset] = useState(0); const [custom, setCustom] = useState(false); const [selected, setSelected] = useState('Keine Auswahl'); const month = new Date(2026, 8 + offset, 1); return ( <>Auswahl: {selected}
> ); } ``` ## Komponenten-Props ### id Optionale, stabile ID der Widget-Instanz. Ohne Angabe erzeugt React eine ID. ### data Konfiguration vom Typ `ArcWidgetCalendarGridData`. Übergib Änderungen als neue Objekte; verwalte fachliche Daten und Auswahl in React-State. ### onAction Optionaler Fallback `(type: string, ...args: unknown[]) => unknown`. Der konkrete Callback hat Vorrang. Details: [State und Events](/docs/react/patterns/react-state-events). ### onEntryClick Index des Termins, bei Timeline zusätzlich der Ressourcenzeile. ```typescript onEntryClick?: (entry_index: number) => void; ``` ### onDayClick Datum des gewählten Tages als Millisekunden-Zeitstempel. ```typescript onDayClick?: (date_millis: number) => void; ``` ### onEntryDragSave Drag-Ende mit ursprünglichen Daten. Keine vollständigen neuen Zielzeiten; siehe Hinweise zur Persistenz. ```typescript onEntryDragSave?: (entry_index: number, start_data: ArcWidgetCalendarGridDragStart) => void; ``` ### onDailyEntryDragSave Drag-Ende eines ganztägigen Termins mit ursprünglichen Daten. ```typescript onDailyEntryDragSave?: (entry_index: number, start_data: ArcWidgetCalendarGridDragStart) => void; ``` ## Konfiguration Die folgenden Felder gehören in `data`. Größen werden als Strings angegeben, Zahlen und Zeitwerte entsprechend dem jeweiligen Typ. ### height Höhe des Widgets, z. B. `"320px"`. Prozentwerte benötigen einen Elterncontainer mit festgelegter Höhe. Typ: `string`. ### timeZoneBalance Korrektur in Stunden; die Beispiele verwenden `0` und lokal erzeugte Tageswerte. Typ: `number | string`. ### styles Gemeinsame Farben, Abstände und Rahmen; die angebotenen Felder sind widgetspezifisch. Typ: `ArcWidgetCalendarGridStyles | string`. ### dateSettings Datumsbereich, Kopfzeilen und Markierungen. Typ und verschachtelte Felder stehen in `ArcWidgetCalendarGridData` unter Datentypen. ### timeSettings Zeitbereich und Raster; Einheiten sind Millisekunden. Typ: `Record{selected}
> ); } ``` ## Features ausprobieren ### Zeitraum, Zoom, Unterressourcen und Gestaltung Nutze die Zoom-Tasten im Widget, ändere den Zeitraum und blende die Unterressource ein. Die Balken verwenden JSX; die Klick-Callbacks zeigen die tatsächlichen Zeilenindizes. ```tsx import { useState } from 'react'; import { ArcWidgetCalendarTimeline, ArcWidgetBadge, } from '@arcrider/arcwidgets-react'; export default function Example() { const [days, setDays] = useState(14); const [nested, setNested] = useState(true); const [stripes, setStripes] = useState(true); const [selected, setSelected] = useState('Ressource oder Balken anklicken'); const entry = (id: string, title: string, from: number, to: number) => ({ id, title, dateFrom: new Date(2026, 8, from).getTime(), dateTo: new Date(2026, 8, to).getTime(), clickable: true, draggable: false, customLayout: ({selected}
> ); } ``` ## Komponenten-Props ### id Optionale, stabile ID der Widget-Instanz. Ohne Angabe erzeugt React eine ID. ### data Konfiguration vom Typ `ArcWidgetCalendarTimelineData`. Übergib Änderungen als neue Objekte; verwalte fachliche Daten und Auswahl in React-State. ### onAction Optionaler Fallback `(type: string, ...args: unknown[]) => unknown`. Der konkrete Callback hat Vorrang. Details: [State und Events](/docs/react/patterns/react-state-events). ### onEntryClick Index des Termins, bei Timeline zusätzlich der Ressourcenzeile. ```typescript onEntryClick?: (row_index: number, entry_index: number) => void; ``` ### onEntryDragSave Drag-Ende mit ursprünglichen Daten. Keine vollständigen neuen Zielzeiten; siehe Hinweise zur Persistenz. ```typescript onEntryDragSave?: ( row_index: number, entry_index: number, start_data: ArcWidgetCalendarTimelineDragStart, row_change: ArcWidgetCalendarTimelineRowChange ) => void; ``` ### onLabelClick Indizes der geklickten Ressourcenzeile und Beschriftungsspalte. ```typescript onLabelClick?: (row_index: number, column_index: number) => void; ``` ### onGhostClick Position des angeklickten freien Bereichs. ```typescript onGhostClick?: (row_index: number, column_value: number) => void; ``` ## Konfiguration Die folgenden Felder gehören in `data`. Größen werden als Strings angegeben, Zahlen und Zeitwerte entsprechend dem jeweiligen Typ. ### height Höhe des Widgets, z. B. `"320px"`. Prozentwerte benötigen einen Elterncontainer mit festgelegter Höhe. Typ: `string`. ### timeZoneBalance Korrektur in Stunden; die Beispiele verwenden `0` und lokal erzeugte Tageswerte. Typ: `number | string`. ### dateSettings Datumsbereich, Kopfzeilen und Markierungen. Typ und verschachtelte Felder stehen in `ArcWidgetCalendarTimelineData` unter Datentypen. ### columns Linke Beschriftungsspalten der Ressourcen. Typ: `ArcWidgetCalendarTimelineColumn[]`. ### items Einträge der Auswahl oder der Darstellung; siehe den zugehörigen Datentyp. Typ: `ArcWidgetCalendarTimelineItem[]`. ### dayWidth Breite eines Tages auf der Zeitachse. Typ: `string`. ### zoomSettings Zoomgrenzen und Bedienelemente. Typ und verschachtelte Felder stehen in `ArcWidgetCalendarTimelineData` unter Datentypen. ### timeSettings Zeitbereich und Raster; Einheiten sind Millisekunden. Typ: `RecordAktueller Zähler: {count}
> ); } ``` ## Komponenten-Props ### id Optionale, stabile ID der Widget-Instanz. Ohne Angabe erzeugt React eine ID. ### data Konfiguration vom Typ `ArcWidgetDevToolsData`. Übergib Änderungen als neue Objekte; verwalte fachliche Daten und Auswahl in React-State. ### onAction Optionaler Fallback `(type: string, ...args: unknown[]) => unknown`. Der konkrete Callback hat Vorrang. Details: [State und Events](/docs/react/patterns/react-state-events). ### onFooterClick Nullbasierter Index des Footer-Buttons. ```typescript onFooterClick?: (index: number) => void; ``` ## Konfiguration Die folgenden Felder gehören in `data`. Größen werden als Strings angegeben, Zahlen und Zeitwerte entsprechend dem jeweiligen Typ. ### errorPanel Meldungstexte und optionale Footer-Buttons. Typ: `ArcWidgetDevToolsErrorPanel`. ## Verhalten und Grenzen - Das Panel wird am Dokumentkörper angehängt. Fehler-Scanning benötigt einen passenden CodeMirror-Editor im Dokument. Ohne Editor zeigt es keine Anwendungsfehler an; `debug` zeigt die von dir übergebenen Werte. Zum Schließen entfernt das Beispiel die Komponente aus dem React-Baum. - Konfiguriere `errorPanel` mit Meldungen und optionalen Footer-Buttons. Nur als `clickable` markierte Buttons lösen `onFooterClick` aus. - Zeige nur Diagnosedaten, die für die jeweiligen Nutzer bestimmt sind. Schließen wird im Beispiel durch React-State umgesetzt. Unterstützte `ArcWidgetSlot`-Felder erlauben einzelne JSX-Elemente, Text oder Widget-Deskriptoren. Reiche fremden Text als JSX-Text weiter; HTML-Strings werden als HTML interpretiert. [Inhalte kombinieren](/docs/react/patterns/nested-widget-descriptors). ## Datentypen Diese widgetbezogenen Typen werden aus dem Paket exportiert. Gemeinsame Typen wie `ArcWidgetSlot`, `ArcWidgetStyles` und `ArcReactWidgetProps` sind ebenfalls verfügbar. Indexsignaturen erlauben Erweiterungen; sie sind keine Zusage, dass beliebige Felder im Widget ausgewertet werden. ```typescript export interface ArcWidgetDevToolsErrorPanel { visible?: boolean; maxErrors?: number; debug?: Record{t('Formen', 'Shapes', 'Formas')}: {shapes.length} ·{' '} {t('Auswahl', 'Selection', 'Selección')}: {selected}
> ); } ``` ## Features ausprobieren ### Freihand zeichnen und Formen übernehmen Zeichne eigene Formen. Erstellen und Bearbeiten aktualisieren den React-State. ```tsx import { useState } from 'react'; import { ArcWidgetUpload, ArcWidgetDrawing } from '@arcrider/arcwidgets-react'; import type { ArcWidgetDrawingShape } from '@arcrider/arcwidgets-react'; export default function Example() { const [shapes, setShapes] = useState{status}
> ); } ``` ## Komponenten-Props ### id Optionale, stabile ID der Widget-Instanz. Ohne Angabe erzeugt React eine ID. ### data Konfiguration vom Typ `ArcWidgetDrawingData`. Übergib Änderungen als neue Objekte; verwalte fachliche Daten und Auswahl in React-State. ### onAction Optionaler Fallback `(type: string, ...args: unknown[]) => unknown`. Der konkrete Callback hat Vorrang. Details: [State und Events](/docs/react/patterns/react-state-events). ### onShapeCreate Neue Form und ein veränderliches Completion-Objekt. Setze dessen `promise` auf die vergebene ID. ```typescript onShapeCreate?: (payload: ArcWidgetDrawingCreatePayload, completion: { promise?: Promise| Feld | Beschreibung |
|---|---|
| `allowedTypes` | Liste der erlaubten Exportformate. Du kannst `"jpg"`, `"png"`, `"svg"` und `"pdf"` angeben. Beim Format `"pdf"` kannst du zusätzlich `mergeFile: false` oder ein Feldname setzen, um eine bestehende PDF zu ergänzen. |
| `target` | Legt fest, wohin die Datei exportiert werden soll. `"file"` bedeutet, die Datei wird heruntergeladen. `"field"` bedeutet, die Datei wird in ein Datei-Feld in Ninox hochgeladen. |
| `recordId` | Gibt den Datensatz an, in den exportiert werden soll (nur bei `target: "field"` erforderlich). |
| `fieldId` | Definiert das genaue Feld im Datensatz, in das die exportierte Datei geschrieben wird. |
| `mergeFile` | Nur für PDF-Exporte relevant. Wenn angegeben, wird die exportierte Zeichnung mit der PDF aus diesem Feld zusammengeführt. |
| `apiKey` | Wird nur für den PDF-Export mit Zusammenführung benötigt. Hier wird ein API-Schlüssel von Arc Rider verwendet, um das PDF-Rendering serverseitig durchzuführen. |
`header`: Titel (z. B. „Mangel #3“) - `content`: Beschreibung oder weitere Infos - `hide`: `true` oder `false` – blendet das Item in der Sidebar aus, ohne es zu löschen (z. B. für gefilterte Ansichten)
| Feld | Beschreibung |
|---|---|
| `allowedTypes` | Liste der erlaubten Exportformate. Du kannst `"jpg"`, `"png"`, `"svg"` und `"pdf"` angeben. Beim Format `"pdf"` kannst du zusätzlich `mergeFile: false` oder ein Feldname setzen, um eine bestehende PDF zu ergänzen. |
| `target` | Legt fest, wohin die Datei exportiert werden soll. `"file"` bedeutet, die Datei wird heruntergeladen. `"field"` bedeutet, die Datei wird in ein Datei-Feld in Ninox hochgeladen. |
| `recordId` | Gibt den Datensatz an, in den exportiert werden soll (nur bei `target: "field"` erforderlich). |
| `fieldId` | Definiert das genaue Feld im Datensatz, in das die exportierte Datei geschrieben wird. |
| `mergeFile` | Nur für PDF-Exporte relevant. Wenn angegeben, wird die exportierte Zeichnung mit der PDF aus diesem Feld zusammengeführt. |
| `apiKey` | Wird nur für den PDF-Export mit Zusammenführung benötigt. Hier wird ein API-Schlüssel von Arc Rider verwendet, um das PDF-Rendering serverseitig durchzuführen. |
`header`: Titel (z. B. „Mangel #3“) - `content`: Beschreibung oder weitere Infos - `hide`: `true` oder `false` – blendet das Item in der Sidebar aus, ohne es zu löschen (z. B. für gefilterte Ansichten)
{selected}
> ); } ``` ## Features ausprobieren ### Gruppen, JSX-Karten und leere Spalten Schalte die Gruppierung um und klicke auf eine Karte. Das ergänzende Drag-Beispiel darunter zeigt die Speicherung mit Fehlerbehandlung. ```tsx import { useState } from 'react'; import { ArcWidgetKanban, ArcWidgetBadge } from '@arcrider/arcwidgets-react'; export default function Example() { const [grouped, setGrouped] = useState(true); const [selected, setSelected] = useState('Keine Auswahl'); const task = (id: string, title: string) => ({ id, title, subtitle: ({selected}
> ); } ``` ### Drag & Drop, Speichern und Rollback Verschiebe die Karte und aktiviere anschließend den simulierten Speicherfehler. ```tsx import { useState } from 'react'; import { ArcWidgetKanban } from '@arcrider/arcwidgets-react'; const initial = [ { id: 'open', title: 'Offen', items: [ { id: 'a', title: 'Angebot prüfen', clickable: true, draggable: true, }, ], }, { id: 'done', title: 'Erledigt', items: [] as initialItem[], }, ]; type initialItem = { id: string; title: string; clickable: boolean; draggable: boolean; }; export default function Example() { const [lanes, setLanes] = useState(initial); const [fail, setFail] = useState(false); const [status, setStatus] = useState('Karte verschieben'); return ( <>{status}
> ); } ``` ## Komponenten-Props ### id Optionale, stabile ID der Widget-Instanz. Ohne Angabe erzeugt React eine ID. ### data Konfiguration vom Typ `ArcWidgetKanbanData`. Übergib Änderungen als neue Objekte; verwalte fachliche Daten und Auswahl in React-State. ### onAction Optionaler Fallback `(type: string, ...args: unknown[]) => unknown`. Der konkrete Callback hat Vorrang. Details: [State und Events](/docs/react/patterns/react-state-events). ### onItemClick Browser-Event und Position der Karte. ```typescript onItemClick?: (event: Event, payload: ArcWidgetKanbanItemClickPayload) => void; ``` ### onItemDrop Neue Position einer verschobenen Karte. Ein zurückgegebenes Speicher-Promise hält den Vorgang bis zum Abschluss offen; Ablehnung setzt den Wechsel zurück. ```typescript onItemDrop?: (payload: ArcWidgetKanbanItemDropPayload) => void | Promise{t('Auswahl', 'Selection', 'Selección')}: {selected}
> ); } ``` ## Features ausprobieren ### Abgeleitete Kennzahlen und Klick-Filter Die Kennzahlen werden gemeinsam aus einem State berechnet. Klicke auf eine Karte, um ihren Index im Callback zu prüfen. ```tsx import { useState } from 'react'; import { ArcWidgetKpiBar } from '@arcrider/arcwidgets-react'; export default function Example() { const [completed, setCompleted] = useState(7); const [selected, setSelected] = useState('Keine'); const cards = [ { title: 'Gesamt', value: 12 }, { title: 'Erledigt', value: completed }, { title: 'Offen', value: 12 - completed }, ]; return ( <>Ausgewählte Kennzahl: {selected}
> ); } ``` ## Komponenten-Props ### id Optionale, stabile ID der Widget-Instanz. Ohne Angabe erzeugt React eine ID. ### data Konfiguration vom Typ `ArcWidgetKpiBarData`. Übergib Änderungen als neue Objekte; verwalte fachliche Daten und Auswahl in React-State. ### onAction Optionaler Fallback `(type: string, ...args: unknown[]) => unknown`. Der konkrete Callback hat Vorrang. Details: [State und Events](/docs/react/patterns/react-state-events). ### onClick Meldet den Klick. Beachte die widgetspezifischen Argumente unten. ```typescript onClick?: (event: unknown, cardIndex: number) => void; ``` ## Konfiguration Die folgenden Felder gehören in `data`. Größen werden als Strings angegeben, Zahlen und Zeitwerte entsprechend dem jeweiligen Typ. ### settings Gemeinsame Darstellung der Einträge. Typ: `ArcWidgetKpiBarSettings`. ### cards Kennzahlenkarten mit Beschriftung, Wert und optionaler Klickbarkeit. Typ: `ArcWidgetKpiBarCard[]`. ## Verhalten und Grenzen - `cards` enthält Werte und Beschriftungen, `settings` die gemeinsame Darstellung. - Aktiviere `clickable` pro Karte. `onClick(event, cardIndex)` kann einen Filter ändern oder in deiner Anwendung navigieren. Unterstützte `ArcWidgetSlot`-Felder erlauben einzelne JSX-Elemente, Text oder Widget-Deskriptoren. Reiche fremden Text als JSX-Text weiter; HTML-Strings werden als HTML interpretiert. [Inhalte kombinieren](/docs/react/patterns/nested-widget-descriptors). ## Datentypen Diese widgetbezogenen Typen werden aus dem Paket exportiert. Gemeinsame Typen wie `ArcWidgetSlot`, `ArcWidgetStyles` und `ArcReactWidgetProps` sind ebenfalls verfügbar. Indexsignaturen erlauben Erweiterungen; sie sind keine Zusage, dass beliebige Felder im Widget ausgewertet werden. ```typescript export interface ArcWidgetKpiBarSettings { height?: string; colorLabel?: string; fontSizeLabel?: string; colorValue?: string; fontSizeValue?: string; backgroundColor?: string; borderColor?: string; [key: string]: unknown; } export interface ArcWidgetKpiBarCard { card: number; title?: ArcWidgetSlot; value?: ArcWidgetSlot; subtitle?: ArcWidgetSlot; settings?: ArcWidgetKpiBarSettings; clickable?: boolean; } export interface ArcWidgetKpiBarData extends Omitcards: [{
card: 1,
title: "Gesamtumsatz",
value: "€" + shortNumbers(cnt((select Projekte).Umsatz)),
subtitle: "€" +
shortNumbers(cnt((select Projekte where month(Abgeschlossen) = month(today())).Umsatz)) +
" in diesem Monat"
}, {
card: 2,
title: "Abgeschlossen gesamt",
value: shortNumbers(cnt(select Projekte where Abgeschlossen != null)),
subtitle: shortNumbers(cnt((select Projekte where month(Abgeschlossen) = month(today())).Umsatz)) +
" in diesem Monat"
}, {
card: 3,
title: "Conversion Rate",
value: round(cnt(select Projekte where Abgeschlossen != null) / cnt(select Projekte) * 100, 2) +
"%",
subtitle: round(cnt(select Projekte where Abgeschlossen != null and month(Abgeschlossen) = month(today())) /
cnt(select Projekte where month(Abgeschlossen) = month(today())) *
100, 2) + "%" + " in diesem Monat"
}]
## Parameter
In dem Detail-Bereich deiner KPI-Karten kannst du verschiedenste Parameter definieren. Dabei kannst du selbst entscheiden, welche Parameter du definieren möchtest. Lässt du Parameter einfach weg, werden diese nicht ausgegeben. Hier siehst du alle Parameter, die du bei der KPI Bar verwenden kannst:
```javascript
card: "",
settings: {
colorLabel: "",
fontSizeLabel: "",
colorValue: "",
fontSizeValue: "",
backgroundColor: "",
borderColor: ""
},
title: "",
value: "",
subtitle: "",
actions: [{ type: "update", recordId: Nr, field: fieldId(Nr, "Feld"), value: "..." }]
```
## Funktionen
Du kannst deine Parameter mit allen Ninox Funktionen & Schreibweisen definieren. Wir haben dir aber auch einige nützliche arcRider Funktionen erstellt, die dir deine Werte noch übersichtlicher machen.
*shortNumbers() *kürzt lange Zahlen ab 4 Stellen ab und setzt den Zahlennamen (Tsd.,Mio.,Mrd.,Bill.) dahinter.
```javascript
shortNumbers("2000") // Gibt den abgekürzten Wert der statischen Zahl aus (2 Tsd.)
shortNumbers(cnt((select Projekte).Umsatz)) // Gibt den abgekürzten Wert der dynamischen Zahl aus (z.B. 35 Mio.)
```
## Beispiele
Folgend findest du einige Praxisbeispiele, die dir den unterschiedlichen Einsatz der KPI Bar veranschaulichen.
### KPI Card Fallback

```javascript
let data := {
uniqueId: "KPI Bar 1",
settings: {
height: "",
colorLabel: "",
fontSizeLabel: "15",
colorValue: "",
fontSizeValue: "40px",
backgroundColor: "",
borderColor: ""
},
cards: [{
card: 1,
title: "Gesamtumsatz",
value: "€" + shortNumbers(cnt((select Projekte).Umsatz)),
subtitle: "€" +
shortNumbers(cnt((select Projekte where month(Abgeschlossen) = month(today())).Umsatz)) +
" in diesem Monat"
}, {
card: 2,
title: "Abgeschlossen gesamt",
value: shortNumbers(cnt(select Projekte where Abgeschlossen != null)),
subtitle: shortNumbers(cnt((select Projekte where month(Abgeschlossen) = month(today())).Umsatz)) +
" in diesem Monat"
}, {
card: 3,
title: "Conversion Rate",
value: round(cnt(select Projekte where Abgeschlossen != null) / cnt(select Projekte) * 100, 2) +
"%",
subtitle: round(cnt(select Projekte where Abgeschlossen != null and month(Abgeschlossen) = month(today())) /
cnt(select Projekte where month(Abgeschlossen) = month(today())) *
100, 2) +
"%" +
" in diesem Monat"
}]
};
arcKPIBar(data)
```
### KPI Bar individuell

```javascript
let data := {
uniqueId: "KPI Bar 2",
settings: {
height: "",
colorLabel: "",
fontSizeLabel: "15",
colorValue: "",
fontSizeValue: "40px",
backgroundColor: "",
borderColor: ""
},
cards: [{
card: 1,
settings: {
colorLabel: "#F9244B",
fontSizeLabel: "15",
colorValue: "#F9244B",
fontSizeValue: "40px",
backgroundColor: "#FCD9DF",
borderColor: "#EABDC5"
},
title: "Gesamtumsatz",
value: "€" + shortNumbers(cnt((select Projekte).Umsatz)),
subtitle: "€" +
shortNumbers(cnt((select Projekte where month(Abgeschlossen) = month(today())).Umsatz)) +
" in diesem Monat"
}, {
card: 2,
settings: {
colorLabel: "#000",
fontSizeLabel: "15",
colorValue: "#1f96bf",
fontSizeValue: "40px",
backgroundColor: "#FDE8A5",
borderColor: "#D9C78C"
},
title: "Abgeschlossen gesamt",
value: shortNumbers(cnt(select Projekte where Abgeschlossen != null)),
subtitle: shortNumbers(cnt((select Projekte where month(Abgeschlossen) = month(today())).Umsatz)) +
" in diesem Monat"
}, {
card: 3,
settings: {
colorLabel: "#CAECCD",
fontSizeLabel: "15",
colorValue: "#CAECCD",
fontSizeValue: "40px",
backgroundColor: "#56C275",
borderColor: "#9DCDA1"
},
title: "Conversion Rate",
value: round(cnt(select Projekte where Abgeschlossen != null) / cnt(select Projekte) * 100, 2) +
"%",
subtitle: cnt(select Projekte where Abgeschlossen != null and month(Abgeschlossen) = month(today())) /
cnt(select Projekte where month(Abgeschlossen) = month(today())) *
100 +
"%" +
" in diesem Monat"
}]
};
arcKPIBar(data)
```
### KPI Bar Darkmode

```javascript
let data := {
uniqueId: "KPI Bar 3",
settings: {
height: "",
colorLabel: "",
fontSizeLabel: "15",
colorValue: "",
fontSizeValue: "40px",
backgroundColor: "",
borderColor: ""
},
cards: [{
card: 1,
settings: {
colorLabel: "#8e99c4",
fontSizeLabel: "15",
colorValue: "#8e99c4",
fontSizeValue: "40px",
backgroundColor: "#49547e",
borderColor: "#646f9b"
},
title: "Gesamtumsatz",
value: "€" + shortNumbers(cnt((select Projekte).Umsatz)),
subtitle: "€" +
shortNumbers(cnt((select Projekte where month(Abgeschlossen) = month(today())).Umsatz)) +
" in diesem Monat"
}, {
card: 2,
settings: {
colorLabel: "#8e99c4",
fontSizeLabel: "15",
colorValue: "#8e99c4",
fontSizeValue: "40px",
backgroundColor: "#49547e",
borderColor: "#646f9b"
},
title: "Abgeschlossen gesamt",
value: shortNumbers(cnt(select Projekte where Abgeschlossen != null)),
subtitle: shortNumbers(cnt((select Projekte where month(Abgeschlossen) = month(today())).Umsatz)) +
" in diesem Monat"
}, {
card: 3,
settings: {
colorLabel: "#8e99c4",
fontSizeLabel: "15",
colorValue: "#8e99c4",
fontSizeValue: "40px",
backgroundColor: "#49547e",
borderColor: "#646f9b"
},
title: "Conversion Rate",
value: round(cnt(select Projekte where Abgeschlossen != null) / cnt(select Projekte) * 100, 2) +
"%",
subtitle: cnt(select Projekte where Abgeschlossen != null and month(Abgeschlossen) = month(today())) /
cnt(select Projekte where month(Abgeschlossen) = month(today())) *
100 +
"%" +
" in diesem Monat"
}]
};
arcKPIBar(data)
```
**Weiterführende Links: **
[> NX Custom KPI Bar ](https://www.arc-rider.de/custom-kpi-bar)
---
# Custom Layout
Source: https://www.arc-rider.com/docs/widgets/custom-layout
# Layout in React
Widgets horizontal oder vertikal anordnen. Für responsive Raster kannst du das Layout mit CSS Grid oder Flexbox deiner Anwendung kombinieren.
Referenz für `@arcrider/arcwidgets-react@0.1.0-alpha.5`. [Installation](/docs/react/intro/setup-react) · [Alle Komponenten](/docs/react/intro/react-components).
## Vollständiges Beispiel
```tsx
import {
ArcWidgetBadge,
ArcWidgetButton,
ArcWidgetLayout,
ArcWidgetText,
} from '@arcrider/arcwidgets-react';
import { useState } from 'react';
const lang = document.documentElement.lang.startsWith('en')
? 'en'
: document.documentElement.lang.startsWith('es')
? 'es'
: 'de';
const requested = new URLSearchParams(location.search).get('data');
const pack = requested === 'empty' || requested === 'busy' ? requested : 'demo';
const t = (de: string, en: string, es: string) =>
lang === 'en' ? en : lang === 'es' ? es : de;
export default function Example() {
const [count, setCount] = useState(pack === 'busy' ? 12 : 0);
const badges =
pack === 'empty'
? []
: [
{ value: t('Offen', 'Open', 'Abierto'), color: '#d97706' },
{ value: t('Live', 'Live', 'En vivo'), color: '#15803d' },
...(pack === 'busy'
? [
{
value: t('Priorität', 'Priority', 'Prioridad'),
color: '#be123c',
},
{ value: t('Messe', 'Fair', 'Feria'), color: '#7c3aed' },
]
: []),
];
return (
Direkt auf oberster Ebene (`uniqueId`, `embedded`, `hoverActions`) - **Menü-Konfiguration (**`**menu[…]**`**)**
Steuert das Layout der Menüleiste – z. B. Positionierung, Schriftgröße und Farben für normale und aktive Zustände - **Menüpunkte (**`**menu[…].menuItems[…]**`**)**
Einzelne Buttons mit Titel, Icon, Aktivitätszustand und Aktionen (z. B. Feldwert ändern, Popup öffnen)
```javascript
let current := this;
arcCustomMenu({
uniqueId: "menu " + Nr,
embedded: false,
borderRadius: "4px",
hoverActions: {
backgroundColor: "#192e65",
fontColor: "#fff",
borderColor: "#192e65",
iconColor: "#fff",
animation: "0.2s"
},
menu: [{
tableId: tableId(current),
embedded: false,
settings: {
menuBar: {
width: "",
position: "",
backgroundColor: "",
borderColor: "",
fontSize: "20px"
},
stateNormal: {
backgroundColor: "",
borderColor: "",
fontColor: ""
},
stateActive: {
backgroundColor: "",
borderColor: "",
fontColor: ""
}
}
}, {
menuItems: [{
item: 1,
title: "Table",
active: Menu = text("A"),
icon: arcCustomIcon({
name: "table"
}),
actions: [{
recordId: Nr,
type: "update",
field: "K",
value: "A"
}]
}, {
item: 2,
title: "Kanban",
active: Menu = text("B"),
icon: arcCustomIcon({
name: "kanban"
}),
actions: [{
recordId: Nr,
type: "update",
field: "K",
value: "B"
}]
}, {
item: 3,
title: "KPI Bar",
active: Menu = text("C"),
icon: arcCustomIcon({
name: "copy"
}),
actions: [{
recordId: Nr,
type: "update",
field: "K",
value: "C"
}]
}]
}]
})
```

## Allgemeine Parameter-Einstellungen
### uniqueId
**Typ:** `text`
**Pflichtfeld:** ja
Definiert eine eindeutige ID für die jeweilige Menü-Instanz. Diese ID wird intern zur gezielten CSS-Ansprache verwendet und ist notwendig, damit mehrere Menüs auf derselben Seite unabhängig voneinander funktionieren.
💡 **Tipp: **Setze die ID dynamisch, z. B. mit `uniqueId: "menu-" + Nr`, wenn du das Widget mehrfach aufrufst.
### embedded
**Typ:** `boolean`
**Default:** `false`
Legt fest, ob das Widget in ein bestehendes Widget (z. B. [Layout](https://www.arc-rider.de/documentation/custom-layout)) eingebettet wird oder als eigenständiges Widget auf der Ninox Oberfläche integriert wird.
💡 **Hinweis: **Wenn du `embedded: true` setzt, musst du sicherstellen, dass das umgebende Element eine passende Höhe und ggf. ein eigenes Layout mitbringt. Das Widget passt sich dann vollständig diesem Rahmen an.
### hoverActions
**Typ:** `object`
**Default:** kein Hover-Verhalten
Definiert das Verhalten beim Überfahren eines Menüpunktes mit der Maus.
Du kannst hier gezielt Farben und Animationen setzen, die beim Hover-Effekt angewendet werden sollen. Alle Felder sind optional – wenn du nichts angibst, bleibt das Standarddesign erhalten.
Feld
Typ
Beschreibung
`backgroundColor`
`text`
Hintergrundfarbe beim Hover. Z. B. HEX-Wert: `"#192e65"`
`fontColor`
`text`
Schriftfarbe beim Hover. Z. B. HEX-Wert: `"#fff"`
`borderColor`
`text`
Rahmenfarbe beim Hover. Z. B. HEX-Wert: `"#192e65"`
`iconColor`
`text`
Farbe des Icons beim Hover. Z. B. HEX-Wert:`"#fff"`
`animation`
`text`
Transition-Zeit, z. B. `"0.2s"` für weiche Effekte
`type`
Beschreibung
Aufbau Code
`update`
Setzt den Wert eines Feldes im angegebenen Datensatz
`recordId`, `field`, `value`
`popup`
Öffnet den angegebenen Datensatz als Popup
`recordId`
`delete`
Löscht den angegebenen Datensatz aus der Datenbank
`recordId`
{t('Übernommen', 'Committed', 'Confirmado')}: {value}
> ); } ``` ## Features ausprobieren ### Dezimalstellen, Suffix und Tastatureingabe Teste Ganzzahlen und Dezimalwerte. Tastaturereignisse gelten nur innerhalb der geöffneten Live-Vorschau; Enter übernimmt den Wert. ```tsx import { useState } from 'react'; import { ArcWidgetNumpad } from '@arcrider/arcwidgets-react'; export default function Example() { const [value, setValue] = useState('12.5'); const [decimals, setDecimals] = useState(2); const [keyboard, setKeyboard] = useState(false); return ( <>Übernommen: {value} Stk
> ); } ``` ## Komponenten-Props ### id Optionale, stabile ID der Widget-Instanz. Ohne Angabe erzeugt React eine ID. ### data Konfiguration vom Typ `ArcWidgetNumpadData`. Übergib Änderungen als neue Objekte; verwalte fachliche Daten und Auswahl in React-State. ### onAction Optionaler Fallback `(type: string, ...args: unknown[]) => unknown`. Der konkrete Callback hat Vorrang. Details: [State und Events](/docs/react/patterns/react-state-events). ### onSubmit Bestätigter Eingabestring. ```typescript onSubmit?: (value: string) => void; ``` ## Konfiguration Die folgenden Felder gehören in `data`. Größen werden als Strings angegeben, Zahlen und Zeitwerte entsprechend dem jeweiligen Typ. ### currentValue Aktueller Anzeigewert. Typ: `string | number`. ### actionTitle Beschriftung der Bestätigung. Typ: `string`. ### backgroundColor Hintergrundfarbe. Typ: `string`. ### fontColor Schriftfarbe. Typ: `string`. ### display Darstellung der Zahlenanzeige. Typ: `ArcWidgetNumpadDisplay`. ### suffix Inhalt nach der Beschriftung oder Einheit. Typ: `string`. ### clearOnFirstInput Ersetzt den bisherigen Wert bei der ersten Eingabe. Typ: `boolean`. ### decimalSeparator Dezimaltrennzeichen des Eingabestrings. Typ: `string`. ### maxDecimals Maximale Anzahl Nachkommastellen. Typ: `number`. ### keyboard Bedienung über die physische Tastatur. Typ: `boolean | ArcWidgetNumpadKeyboard`. ## Verhalten und Grenzen - `onSubmit` liefert einen String, etwa `"12,50"`. Normalisiere das konfigurierte Dezimaltrennzeichen vor einer numerischen Berechnung und prüfe das Ergebnis. - `currentValue` befüllt die Anzeige. `clearOnFirstInput` ist hilfreich beim Überschreiben eines vorhandenen Werts. Unterstützte `ArcWidgetSlot`-Felder erlauben einzelne JSX-Elemente, Text oder Widget-Deskriptoren. Reiche fremden Text als JSX-Text weiter; HTML-Strings werden als HTML interpretiert. [Inhalte kombinieren](/docs/react/patterns/nested-widget-descriptors). ## Datentypen Diese widgetbezogenen Typen werden aus dem Paket exportiert. Gemeinsame Typen wie `ArcWidgetSlot`, `ArcWidgetStyles` und `ArcReactWidgetProps` sind ebenfalls verfügbar. Indexsignaturen erlauben Erweiterungen; sie sind keine Zusage, dass beliebige Felder im Widget ausgewertet werden. ```typescript export interface ArcWidgetNumpadDisplay { fontColor?: string; height?: string; } export interface ArcWidgetNumpadKeyboard { enabled?: boolean; submitOnEnter?: boolean; } export interface ArcWidgetNumpadData extends Omit{t('Übernommen', 'Committed', 'Confirmado')}: {value} %
> ); } ``` ## Features ausprobieren ### Schrittweite und gemeinsamer State Der Slider steuert den Fortschrittsbalken über denselben State. Ändere die Schrittweite und verschiebe den Regler. ```tsx import { useState } from 'react'; import { ArcWidgetSlider, ArcWidgetProgressBar, } from '@arcrider/arcwidgets-react'; export default function Example() { const [value, setValue] = useState(40); const [step, setStep] = useState(5); return ( <>Übernommener Wert: {value}
> ); } ``` ## Komponenten-Props ### id Optionale, stabile ID der Widget-Instanz. Ohne Angabe erzeugt React eine ID. ### data Konfiguration vom Typ `ArcWidgetSliderData`. Übergib Änderungen als neue Objekte; verwalte fachliche Daten und Auswahl in React-State. ### onAction Optionaler Fallback `(type: string, ...args: unknown[]) => unknown`. Der konkrete Callback hat Vorrang. Details: [State und Events](/docs/react/patterns/react-state-events). ### onChange Übernimmt den neuen Eingabewert in deinen State. ```typescript onChange?: (value: number) => void; ``` ## Konfiguration Die folgenden Felder gehören in `data`. Größen werden als Strings angegeben, Zahlen und Zeitwerte entsprechend dem jeweiligen Typ. ### value Anzuzeigender bzw. aktuell bearbeiteter Wert. Typ: `number`. ### min Kleinster Wert. Typ: `number`. ### max Größter Wert. Typ: `number`. ### step Schrittweite. Typ: `number`. ### width Breite des Widgets als Größenangabe. Typ: `string`. ### height Höhe des Widgets, z. B. `"320px"`. Prozentwerte benötigen einen Elterncontainer mit festgelegter Höhe. Typ: `string`. ### track Darstellung der gesamten Schiene. Typ: `ArcWidgetSliderTrackStyle`. ### fill Darstellung des gefüllten Bereichs. Typ: `ArcWidgetSliderFillStyle`. ### thumb Darstellung des Reglers. Typ: `ArcWidgetSliderThumbStyle`. ### valueDisplay Anzeige und Formatierung des Werts. Typ: `ArcWidgetSliderValueDisplay`. ### statusDisplay Beschriftungen für Wertebereiche. Typ: `ArcWidgetSliderStatusDisplay[]`. ## Verhalten und Grenzen - `onChange` liefert eine Zahl. Übernimm sie in deinen State und gib sie als `data.value` zurück. - Setze sinnvolle `min`, `max` und `step`. Änderungen im Widget speichern noch keine Daten. Unterstützte `ArcWidgetSlot`-Felder erlauben einzelne JSX-Elemente, Text oder Widget-Deskriptoren. Reiche fremden Text als JSX-Text weiter; HTML-Strings werden als HTML interpretiert. [Inhalte kombinieren](/docs/react/patterns/nested-widget-descriptors). ## Datentypen Diese widgetbezogenen Typen werden aus dem Paket exportiert. Gemeinsame Typen wie `ArcWidgetSlot`, `ArcWidgetStyles` und `ArcReactWidgetProps` sind ebenfalls verfügbar. Indexsignaturen erlauben Erweiterungen; sie sind keine Zusage, dass beliebige Felder im Widget ausgewertet werden. ```typescript export interface ArcWidgetSliderTrackStyle { height?: string; backgroundColor?: string; borderRadius?: string; [key: string]: unknown; } export interface ArcWidgetSliderFillStyle { backgroundColor?: string; [key: string]: unknown; } export interface ArcWidgetSliderThumbStyle { width?: string; height?: string; backgroundColor?: string; borderColor?: string; borderWidth?: string; borderRadius?: string; [key: string]: unknown; } export interface ArcWidgetSliderValueDisplay { show?: boolean; color?: string; fontSize?: string; [key: string]: unknown; } export interface ArcWidgetSliderStatusDisplay { label?: string; value?: number; color?: string; track?: ArcWidgetSliderTrackStyle; fill?: ArcWidgetSliderFillStyle; thumb?: ArcWidgetSliderThumbStyle; [key: string]: unknown; } export interface ArcWidgetSliderData extends ArcWidgetBaseData { value?: number; min?: number; max?: number; step?: number; width?: string; height?: string; track?: ArcWidgetSliderTrackStyle; fill?: ArcWidgetSliderFillStyle; thumb?: ArcWidgetSliderThumbStyle; valueDisplay?: ArcWidgetSliderValueDisplay; statusDisplay?: ArcWidgetSliderStatusDisplay[]; } export interface ArcWidgetSliderProps extends ArcReactWidgetProps| Feld | Beschreibung |
|---|---|
| `height` | Höhe des Tracks, z. B. `"6px"` |
| `backgroundColor` | Farbe des Tracks, z. B. `"#E9ECF4"` |
| `borderRadius` | Border-Radius des Tracks, z. B. `"3px"` |
| Feld | Beschreibung |
|---|---|
| `width` / `height` | Größe des Knopfs, z. B. `"20px"` |
| `backgroundColor` | Füllfarbe des Knopfs |
| `borderColor` / `borderWidth` / `borderRadius` | Rahmen des Knopfs, z. B. `borderRadius: "50%"` für rund |
| Feld | Beschreibung |
|---|---|
| `label` | Icon oder Text, z. B. `"🔥"`, `"✓"`, `"⚠"`, `"✕"` |
| `value` | Schwellenwert – ab diesem Wert wird der Status aktiv |
| `color` | Farbe des Icon-Texts |
| `track` / `fill` / `thumb` | Optional: gleiche Struktur wie oben, überschreibt das Standard-Styling für diesen Status (mit `!important`) |
${t('Anforderungen und Ziele sammeln.', 'Collect requirements and goals.', 'Reúne requisitos y objetivos.')}
`, backgroundColor: '#ecfdf5', }, { value: `${t('Ansichten mit Widgets zusammensetzen.', 'Compose views with widgets.', 'Monta vistas con widgets.')}
`, backgroundColor: '#fffbeb', }, { value: `${t('State und Callbacks an eure API hängen.', 'Wire state and callbacks to your API.', 'Conecta estado y callbacks a tu API.')}
`, backgroundColor: '#f5f3ff', }, ]; const busyItems = [ ...demoItems, { value: `${t('Lizenz setzen und live gehen.', 'Set the license and go live.', 'Activa la licencia y publica.')}
`, backgroundColor: '#fff1f2', }, ]; const items = pack === 'empty' ? [] : pack === 'busy' ? busyItems : demoItems; export default function Example() { const [index, setIndex] = useState(0); return ( <>{t('Folie', 'Slide', 'Diapositiva')} {items.length ? index + 1 : 0}
> ); } ``` ## Features ausprobieren ### Richtung, Schleife und Autoplay Teste Pfeile, Punkte und die automatische Wiedergabe. Die Folien verwenden feste HTML-Strings; JSX ist in Slideshow-Inhalten nicht unterstützt. ```tsx import { useState } from 'react'; import { ArcWidgetSlideshow } from '@arcrider/arcwidgets-react'; export default function Example() { const [vertical, setVertical] = useState(false); const [autoplay, setAutoplay] = useState(false); const [loop, setLoop] = useState(true); const [index, setIndex] = useState(0); return ( <>Anforderungen und Ziele sammeln.
', backgroundColor: '#f4f4f5', }, { value: 'Ansichten mit Widgets zusammensetzen.
', backgroundColor: '#e4e4e7', }, { value: 'State und Callbacks verbinden.
', backgroundColor: '#fafafa', }, ], }} onSlideChange={(p) => setIndex(p.index)} />Aktuelle Folie: {index + 1}
> ); } ``` ## Komponenten-Props ### id Optionale, stabile ID der Widget-Instanz. Ohne Angabe erzeugt React eine ID. ### data Konfiguration vom Typ `ArcWidgetSlideshowData`. Übergib Änderungen als neue Objekte; verwalte fachliche Daten und Auswahl in React-State. ### onAction Optionaler Fallback `(type: string, ...args: unknown[]) => unknown`. Der konkrete Callback hat Vorrang. Details: [State und Events](/docs/react/patterns/react-state-events). ### onSlideChange Nullbasierter Index des aktiven Slides. ```typescript onSlideChange?: (payload: { index: number }) => void; ``` ## Konfiguration Die folgenden Felder gehören in `data`. Größen werden als Strings angegeben, Zahlen und Zeitwerte entsprechend dem jeweiligen Typ. ### items Einträge der Auswahl oder der Darstellung; siehe den zugehörigen Datentyp. Typ: `ArcWidgetSlideshowItem[]`. ### width Breite des Widgets als Größenangabe. Typ: `string`. ### height Höhe des Widgets, z. B. `"320px"`. Prozentwerte benötigen einen Elterncontainer mit festgelegter Höhe. Typ: `string`. ### backgroundColor Hintergrundfarbe. Typ: `string`. ### borderRadius Abrundung der Ecken. Typ: `string`. ### borderWidth Rahmenstärke. Typ: `string`. ### borderColor Rahmenfarbe. Typ: `string`. ### direction Anordnung horizontal oder vertikal. Typ: `'horizontal' | 'vertical' | string`. ### loop Nach dem letzten Slide wieder zum ersten wechseln. Typ: `boolean`. ### autoplay Automatischer Wechsel mit `enabled` und `speed` in Millisekunden. Typ: `{ enabled?: boolean; speed?: number; [key: string]: unknown }`. ### pause Unterbrechung der automatischen Wiedergabe bei Interaktion. Typ: `{ hover?: boolean; focus?: boolean; [key: string]: unknown }`. ### transition Animation, Dauer und Verzögerung in Millisekunden. Typ: `{ duration?: number; delay?: number; transition?: string; [key: string]: unknown }`. ### indicators Sichtbarkeit und Darstellung der Slide-Indikatoren. Typ: `{ enabled?: boolean; type?: string; position?: string; [key: string]: unknown }`. ### controls Konfiguration der Navigation. Typ: `{ enabled?: boolean; [key: string]: unknown }`. ### scroll Wechsel durch Scrollen. Typ: `{ enabled?: boolean; speed?: number; [key: string]: unknown }`. ## Verhalten und Grenzen - `items[].value` unterstützt derzeit Strings, einschließlich eigenem HTML. JSX und verschachtelte Widget-Deskriptoren werden in dieser Komponente nicht gerendert. - `autoplay.speed` und `transition.duration` werden in Millisekunden angegeben. `onSlideChange` liefert einen nullbasierten `index`. - Das Galerie-Beispiel verwendet selbst erzeugte SVG-Bilder. HTML mit dynamischen, fremden Inhalten muss vor Übergabe bereinigt werden. ## Datentypen Diese widgetbezogenen Typen werden aus dem Paket exportiert. Gemeinsame Typen wie `ArcWidgetSlot`, `ArcWidgetStyles` und `ArcReactWidgetProps` sind ebenfalls verfügbar. Indexsignaturen erlauben Erweiterungen; sie sind keine Zusage, dass beliebige Felder im Widget ausgewertet werden. ```typescript export interface ArcWidgetSlideshowItem { value: string; label?: string; width?: string; height?: string; backgroundColor?: string; [key: string]: unknown; } export interface ArcWidgetSlideshowData extends ArcWidgetBaseData { items: ArcWidgetSlideshowItem[]; width?: string; height?: string; backgroundColor?: string; borderRadius?: string; borderWidth?: string; borderColor?: string; direction?: 'horizontal' | 'vertical' | string; loop?: boolean; autoplay?: { enabled?: boolean; speed?: number; [key: string]: unknown }; pause?: { hover?: boolean; focus?: boolean; [key: string]: unknown }; transition?: { duration?: number; delay?: number; transition?: string; [key: string]: unknown }; indicators?: { enabled?: boolean; type?: string; position?: string; [key: string]: unknown }; controls?: { enabled?: boolean; [key: string]: unknown }; scroll?: { enabled?: boolean; speed?: number; [key: string]: unknown }; } export interface ArcWidgetSlideshowProps extends ArcReactWidgetProps| Feld | Typ | Beschreibung |
|---|---|---|
| `value` | `text` | Inhalt des Slides: einfacher Text, HTML-String (` … `), oder ein ` |
| `label` | `text` | Fallback-Text, falls `value` leer ist oder kein String – z. B. `"Slide 1"`. |
| `width` / `height` | `text` | Größe des einzelnen Slides (Default: `100%` / `100%`). |
| `backgroundColor` | `text` | Individuelle Hintergrundfarbe für diesen Slide. |
{t('Schritt', 'Step', 'Paso')} {steps.length ? step + 1 : 0}
> ); } ``` ## Features ausprobieren ### Schritte anklicken und Fortschritt übernehmen Die Schritte und der Weiter-Button verändern denselben aktiven Schritt. Alle bereits erreichten Schritte erhalten eine eigene Farbe. ```tsx import { useState } from 'react'; import { ArcWidgetSteps, ArcWidgetButton } from '@arcrider/arcwidgets-react'; export default function Example() { const [step, setStep] = useState(0); const labels = ['Anfrage', 'Planung', 'Umsetzung', 'Abnahme']; return ( <>Aktuell: {labels[step]}
> ); } ``` ## Komponenten-Props ### id Optionale, stabile ID der Widget-Instanz. Ohne Angabe erzeugt React eine ID. ### data Konfiguration vom Typ `ArcWidgetStepsData`. Übergib Änderungen als neue Objekte; verwalte fachliche Daten und Auswahl in React-State. ### onAction Optionaler Fallback `(type: string, ...args: unknown[]) => unknown`. Der konkrete Callback hat Vorrang. Details: [State und Events](/docs/react/patterns/react-state-events). ### onClick Meldet den Klick. Beachte die widgetspezifischen Argumente unten. ```typescript onClick?: (event: unknown, stepIndex: number) => void; ``` ## Konfiguration Die folgenden Felder gehören in `data`. Größen werden als Strings angegeben, Zahlen und Zeitwerte entsprechend dem jeweiligen Typ. ### settings Gemeinsame Darstellung der Einträge. Typ: `ArcWidgetStepsSettings`. ### steps Schritte mit Titel, Status und optionaler Klickbarkeit. Typ: `ArcWidgetStepsStep[]`. ## Verhalten und Grenzen - Leite Beschriftung und Farben jedes Schritts aus deinem State ab; klickbare Schritte brauchen `clickable: true`. - `onClick(event, stepIndex)` meldet den angeklickten Schritt. Prüfe vor dem Wechsel die Eingaben des aktuellen Schritts. Unterstützte `ArcWidgetSlot`-Felder erlauben einzelne JSX-Elemente, Text oder Widget-Deskriptoren. Reiche fremden Text als JSX-Text weiter; HTML-Strings werden als HTML interpretiert. [Inhalte kombinieren](/docs/react/patterns/nested-widget-descriptors). ## Datentypen Diese widgetbezogenen Typen werden aus dem Paket exportiert. Gemeinsame Typen wie `ArcWidgetSlot`, `ArcWidgetStyles` und `ArcReactWidgetProps` sind ebenfalls verfügbar. Indexsignaturen erlauben Erweiterungen; sie sind keine Zusage, dass beliebige Felder im Widget ausgewertet werden. ```typescript export interface ArcWidgetStepsSettings { embedded?: boolean; height?: string; colorLabel?: string; fontSizeLabel?: string; colorValue?: string; fontSizeValue?: string; backgroundColor?: string; borderColor?: string; alignX?: 'left' | 'center' | 'right' | string; stepWidth?: string; gapY?: string; stepRadius?: string; connectionLine?: { show?: boolean; height?: string; }; [key: string]: unknown; } export interface ArcWidgetStepsStep { title?: string; value?: string; subtitle?: string; colorLabel?: string; fontSizeLabel?: string; colorValue?: string; fontSizeValue?: string; backgroundColor?: string; width?: string; clickable?: boolean; } export interface ArcWidgetStepsData extends Omit| Feld | Typ | Beschreibung |
|---|---|---|
| `title` | `text` | Titel oberhalb des Kreises |
| `value` | `text`/`number` | Zentrale Anzeige im Kreis |
| `subtitle` | `text` | Text unterhalb des Kreises |
| `width` | `text` | Individuelle Breite des Steps |
| `colorLabel` | `text` | Textfarbe für `title` und `subtitle` |
| `fontSizeLabel` | `text` | Schriftgröße für `title` und `subtitle` |
| `colorValue` | `text` | Textfarbe des zentralen Werts |
| `fontSizeValue` | `text` | Schriftgröße des Werts |
| `backgroundColor` | `text` | Hintergrundfarbe des Kreises und der Verbindungslinien |
| `borderColor` | `text` | Farbe der Step-Trennlinie |
| `actions[]` | `array` | Aktionen, die beim Klick ausgelöst werden können (siehe unten) |
| Feld | Typ | Beschreibung |
|---|---|---|
| `title` | `text` | Titel oberhalb des Kreises |
| `value` | `text`/`number` | Zentrale Anzeige im Kreis |
| `subtitle` | `text` | Text unterhalb des Kreises |
| `width` | `text` | Individuelle Breite des Steps |
| `colorLabel` | `text` | Textfarbe für `title` und `subtitle` |
| `fontSizeLabel` | `text` | Schriftgröße für `title` und `subtitle` |
| `colorValue` | `text` | Textfarbe des zentralen Werts |
| `fontSizeValue` | `text` | Schriftgröße des Werts |
| `backgroundColor` | `text` | Hintergrundfarbe des Kreises und der Verbindungslinien |
| `borderColor` | `text` | Farbe der Step-Trennlinie |
| `actions[]` | `array` | Aktionen, die beim Klick ausgelöst werden können (siehe unten) |
{t( 'Leerer Zustand — über Daten auf Voll oder Demo wechseln.', 'Empty state — switch Data to Full or Demo.', 'Estado vacío — cambia Datos a Completo o Demo.', )}
), }, }} onCellClick={(_event, { row_index }) => { setSelected( row_index == null ? t('Keine Auswahl', 'Nothing selected', 'Sin selección') : `${t('Zeile', 'Row', 'Fila')} ${row_index + 1}`, ); }} />{selected}
> ); } ``` ## Features ausprobieren ### Sortierung, Unterzeilen, JSX-Zellen und leerer Zustand Sortiere über die Spaltenüberschriften, klappe Unterzeilen auf und wechsle zur leeren Liste. Die Statuszellen sind echte Badge-Komponenten. ```tsx import { useState } from 'react'; import { ArcWidgetTable, ArcWidgetBadge } from '@arcrider/arcwidgets-react'; export default function Example() { const [mode, setMode] = useState('nested'); const [sort, setSort] = useState(true); const [selected, setSelected] = useState('Keine Auswahl'); const row = (id: string, title: string, amount: number) => ({ id, clickable: true, columns: [ { value: title, width: 'fraction' }, { value: ({ 'Wechsle oben zurück zur flachen Liste oder zu den Unterzeilen.' }
), }, }} onCellClick={(_event, p) => setSelected( `Zeile ${p.row_index}, Spalte ${p.col_index}, flacher Index ${p.flat_index}`, ) } />{selected}
> ); } ``` ## Komponenten-Props ### id Optionale, stabile ID der Widget-Instanz. Ohne Angabe erzeugt React eine ID. ### data Konfiguration vom Typ `ArcWidgetTableData`. Übergib Änderungen als neue Objekte; verwalte fachliche Daten und Auswahl in React-State. ### onAction Optionaler Fallback `(type: string, ...args: unknown[]) => unknown`. Der konkrete Callback hat Vorrang. Details: [State und Events](/docs/react/patterns/react-state-events). ### onCellClick Browser-Event und Zellenposition. Klickbarkeit muss an Zeile oder Zelle aktiviert sein. ```typescript onCellClick?: (event: Event, payload: ArcWidgetTableCellPayload) => void; ``` ### onCellHoverEnter Position der betretenen Zelle; benötigt `hover_enterable`. ```typescript onCellHoverEnter?: (payload: ArcWidgetTableCellPayload) => void; ``` ### onCellHoverLeave Position der verlassenen Zelle; benötigt `hover_leaveable`. ```typescript onCellHoverLeave?: (payload: ArcWidgetTableCellPayload) => void; ``` ## Konfiguration Die folgenden Felder gehören in `data`. Größen werden als Strings angegeben, Zahlen und Zeitwerte entsprechend dem jeweiligen Typ. ### height Höhe des Widgets, z. B. `"320px"`. Prozentwerte benötigen einen Elterncontainer mit festgelegter Höhe. Typ: `string`. ### minHeight Untere Höhengrenze. Typ: `string`. ### minWidth Untere Breitengrenze. Typ: `string`. ### maxHeight Obere Höhengrenze. Typ: `string`. ### groupedBy Gruppierungsschlüssel; weglassen für flache Tabellen. Typ: `string`. ### groupsCollapsed Anfangszustand der Gruppen. Typ: `boolean`. ### collapsible Aufklappen bzw. Einklappen; das konkrete Format steht im Typ. Typ: `{ enabled?: boolean; expandColumn?: number; indentColumn?: number }`. ### theme Widgetspezifische Theme-Konfiguration. Für React kannst du Farben direkt in `styles` und den Bereichen setzen. Typ: `ArcWidgetTableTheme | ArcNestedWidgetDescriptor | string`. ### rowHoverAction Darstellung beim Überfahren einer Zeile. Typ: `ArcWidgetTableHoverAction`. ### header Spaltenüberschriften, Breiten und Sortieroptionen. Typ: `ArcWidgetTableHeader`. ### emptyTable `title` ist der einfache Fallback-Text. Wenn `value` gesetzt ist, ersetzt dieser Inhalt den Fallback vollständig. Für Überschrift und Beschreibung zusammen verwende ein JSX-Element in `value`, wie im Live-Beispiel. Darstellung bei leerer Zeilenliste. Typ: `ArcWidgetTableEmpty`. ### scrollBar Darstellung des Scrollbalkens. Typ: `ArcWidgetTableScrollBar`. ### table Datenzeilen mit `columns`; optionale Unterzeilen stehen in `items`. Typ: `ArcWidgetTableRow[]`. ### footer Inhalte und Sichtbarkeit des Tabellenfußes. Typ: `ArcWidgetTableFooter`. ### styles Gemeinsame Farben, Abstände und Rahmen; die angebotenen Felder sind widgetspezifisch. Typ: `ArcWidgetTableStyles | string`. ### borders Historische Rahmenkonfiguration. Bevorzuge `styles.borders`, sofern vom Widget angeboten. Typ: `ArcWidgetTableBorders`. ### borderRadius Abrundung der Ecken. Typ: `string`. ## Verhalten und Grenzen - Jede Spalte braucht eine Breite. Verwende mindestens einmal `width: "fraction"`; Header und Zellen müssen dieselbe Spaltenreihenfolge haben. - Setze `clickable: true` auf der Zeile oder Zelle, damit `onCellClick` ausgelöst wird. `row_index` und `col_index` sind nullbare Indizes; prüfe sie vor dem Zugriff. - Bei JSX-Zellinhalten braucht Sortierung einen skalaren `sort.value`. Verwende für Datumssortierung Millisekunden statt lokalisierter Datumsstrings. - Verschachtelte Zeilen werden über `items` beschrieben; `collapsible` steuert Einrückung und Aufklappen. Flache Beispiele können `row_index` direkt auf die Datenliste abbilden. Unterstützte `ArcWidgetSlot`-Felder erlauben einzelne JSX-Elemente, Text oder Widget-Deskriptoren. Reiche fremden Text als JSX-Text weiter; HTML-Strings werden als HTML interpretiert. [Inhalte kombinieren](/docs/react/patterns/nested-widget-descriptors). ## Datentypen Diese widgetbezogenen Typen werden aus dem Paket exportiert. Gemeinsame Typen wie `ArcWidgetSlot`, `ArcWidgetStyles` und `ArcReactWidgetProps` sind ebenfalls verfügbar. Indexsignaturen erlauben Erweiterungen; sie sind keine Zusage, dass beliebige Felder im Widget ausgewertet werden. ```typescript export interface ArcWidgetTableSort { enabled?: boolean; type?: 'text' | 'number' | 'date' | string; value?: string | number; } export interface ArcWidgetTableHeaderColumn { title?: ArcWidgetSlot; width?: string; sort?: ArcWidgetTableSort; styles?: ArcWidgetStyles | string; [key: string]: unknown; } export interface ArcWidgetTableHeader { showHeader?: boolean; height?: string; fontColor?: string; backgroundColor?: string; fontSize?: string; styles?: ArcWidgetStyles | string; columns?: ArcWidgetTableHeaderColumn[]; sortIcons?: { color?: string; activeColor?: string; hoverColor?: string; }; [key: string]: unknown; } export interface ArcWidgetTableCellPayload { row_index?: number | null; col_index?: number | null; flat_index?: number | null; group_value?: string | null; } export interface ArcWidgetTableColumn { title?: ArcWidgetSlot; value?: ArcWidgetSlot; color?: string; backgroundColor?: string; width?: string; align?: 'left' | 'center' | 'right' | string; paddingX?: string; paddingY?: string; groupExpandAction?: boolean; groupValue?: ArcWidgetSlot; groupByValue?: ArcWidgetSlot; clickable?: boolean; hover_enterable?: boolean; hover_leaveable?: boolean; popup_button?: { content?: ArcWidgetSlot }; sort?: ArcWidgetTableSort; styles?: ArcWidgetStyles | string; [key: string]: unknown; } export interface ArcWidgetTableRow { id?: string | number; rowColor?: string; rowHeight?: string; rowPaddingY?: string; groupRowColor?: string; groupRowSettings?: { height?: string; [key: string]: unknown }; columns?: ArcWidgetTableColumn[]; items?: ArcWidgetTableRow[]; clickable?: boolean; styles?: ArcWidgetStyles | string; [key: string]: unknown; } export interface ArcWidgetTableEmpty { title?: ArcWidgetSlot; value?: ArcWidgetSlot; backgroundColor?: string; [key: string]: unknown; } export interface ArcWidgetTableTheme { [key: string]: unknown; } export interface ArcWidgetTableFooter { showFooter?: boolean; showActionButton?: boolean; backgroundColor?: string; actionButtonTitle?: ArcWidgetSlot; leftSideContent?: ArcWidgetSlot; rightSideContent?: ArcWidgetSlot; [key: string]: unknown; } export interface ArcWidgetTableScrollBar { showScrollBar?: boolean; height?: string; backgroundColor?: string; handle?: { height?: string; borderRadius?: string; backgroundColor?: string; [key: string]: unknown; }; [key: string]: unknown; } export type ArcWidgetTableBorderLine = boolean | { color?: string; width?: string; borderColor?: string; borderWidth?: string; borderSize?: string; enabled?: boolean; }; export interface ArcWidgetTableBorders { color?: string; width?: string; outer?: ArcWidgetTableBorderLine; columns?: ArcWidgetTableBorderLine; rows?: ArcWidgetTableBorderLine; } export interface ArcWidgetTableStyles extends ArcWidgetStyles { borders?: ArcWidgetTableBorders; } export interface ArcWidgetTableHoverAction { hoverActive?: boolean; backgroundColor?: string; fontColor?: string; [key: string]: unknown; } export interface ArcWidgetTableData extends ArcWidgetBaseData { height?: string; minHeight?: string; minWidth?: string; maxHeight?: string; groupedBy?: string; groupsCollapsed?: boolean; collapsible?: { enabled?: boolean; expandColumn?: number; indentColumn?: number }; theme?: ArcWidgetTableTheme | ArcNestedWidgetDescriptor | string; rowHoverAction?: ArcWidgetTableHoverAction; header?: ArcWidgetTableHeader; emptyTable?: ArcWidgetTableEmpty; scrollBar?: ArcWidgetTableScrollBar; table?: ArcWidgetTableRow[]; footer?: ArcWidgetTableFooter; styles?: ArcWidgetTableStyles | string; borders?: ArcWidgetTableBorders; borderRadius?: string; } export interface ArcWidgetTableProps extends ArcReactWidgetProps{name}
{preview && ({files.length} Bilder lokal verarbeitet. Es erfolgt kein Upload auf einen Server.
| Feld | Typ | Beschreibung |
|---|---|---|
| `label` | `text` | Beschriftung (Text) im Upload-Feld. Wird in der Mitte angezeigt. Wird unter dem Icon angezeigt. |
| `value` | `widget` `text` | Mit value kannst du dein ganz eigenes Design des Uploads gestalten. Alles, was du bei value angibst, überschriebt das bestehende Standard-Design. Hier kannst du beispielsweise einen einfachen [Custom Button](/documentation/buttons) verwenden oder ein [eigenes Custom Layout](/documentation/custom-layout) gestalten, das sich deiner UI anpasst. Dieser Parameter ist optional und muss natürlich nicht befüllt werden. |
| `height` | `text` | Höhe des Upload-Feldes, z. B. `"80px"` oder `"100%"`. |
| `width` | `text` | Breite des Upload-Feldes, z. B. `"100%"`, `"300px"` oder `"auto"`. |
| `icon` | `widget` | Mit `[arcCustomIcon](https://www.arc-rider.de/documentation/custom-icon)` kann ein individuelles Icon bestimmt werden. |
| Feld | Typ | Beschreibung |
|---|---|---|
| `tableId` | `text` | Ziel-Tabelle, in der das Bild als Datensatz gespeichert wird. |
| `fieldId` | `text` | Feld-ID des Textfelds, in das der Base64-Bildinhalt geschrieben wird. |
| `widthFieldId` | `text` | *(optional)* Feld-ID für die gespeicherte Bildbreite (in Pixeln). Erstelle dafür ein Ninox-Zahlenfeld. |
| `heightFieldId` | `text` | *(optional)* Feld-ID für die gespeicherte Bildhöhe (in Pixeln). Erstelle dafür ein Ninox-Zahlenfeld. |
| `width` / `height` | `text` | *(optional)* Die width / height innerhalb des image{} Blocks gibt die Weite / Höhe deines Bildes in Pixelbreite an, in der du es speichern möchtest. |
| `format` | `text` | *(optional)* Ausgabeformat nach dem Upload (offline im Browser). `"jpeg"` / `"jpg"`, `"webp"` oder `"original"` (Default: weglassen / `"original"` = bisheriges Verhalten, MIME vom Eingang). Bei `"jpeg"`/`"webp"` wird das Bild immer neu encodiert — auch wenn es schon kleiner als `width`/`height` ist. Der Dateiname erhält die passende Extension (`.jpg` / `.webp`). |
| `quality` | `number` | *(optional)* Kompressionsqualität von `0` bis `1` (nur bei `format: "jpeg"` oder `"webp"`). Default `0.8`, wenn `format` gesetzt und `quality` fehlt. Gilt **nicht** bei `"original"`. |
| Feld | Typ | Beschreibung |
|---|---|---|
| `tableId` | `text` | Ziel-Tabelle, in der der Datensatz gespeichert wird |
| `fieldId` | `text` | Feld-ID, in das der Dateiname geschrieben wird |
| Feld | Typ | Beschreibung |
|---|---|---|
| `fieldId` | `text` | Das Feld im neuen Datensatz, das gesetzt werden soll |
| `value` | `any` | Der Wert, der in das Feld geschrieben wird |
| Feld | Typ | Beschreibung |
|---|---|---|
| `recordId` | `text` | Der ID-Wert des bestehenden Datensatzes, in dem verlinkt werden soll |
| `fieldId` | `text` | Das Referenzfeld, das mit dem neuen Upload-Datensatz gefüllt werden soll |
| Feld | Typ | Beschreibung |
|---|---|---|
| `fileType` | `text` | Zielformat der Konvertierung. Aktuell unterstützt: `"jpg"` |
| `apiKey` | `text` | Dein persönlicher API-Schlüssel für den PDF-Konvertierungsdienst (Bitte anfragen bei arcRider) |
| `width` | `number` | Zielbreite in Pixeln (wenn `dpi` nicht gesetzt ist) |
| `height` | `number` | Zielhöhe in Pixeln (wenn `dpi` nicht gesetzt ist) |
| `dpi` | `number` | Auflösung der Konvertierung in **dpi (dots per inch)** |
| `originalFile` | `object` | Optional: Feld zur Speicherung der originalen PDF-Datei als Base64 |
{status}
> ); } ``` ## Features ausprobieren ### Asynchron speichern, Ladezustand und Fehler Speichere mehrfach oder simuliere einen Fehler. Der Button wartet auf das Promise; der Zähler wird nur bei Erfolg erhöht. ```tsx import { useState } from 'react'; import { ArcWidgetButton, ArcWidgetIcon } from '@arcrider/arcwidgets-react'; export default function Example() { const [count, setCount] = useState(0); const [fail, setFail] = useState(false); const [status, setStatus] = useState('Bereit'); return ( <>{status} · Erfolgreich gespeichert: {count}
> ); } ``` ## Komponenten-Props ### id Optionale, stabile ID der Widget-Instanz. Ohne Angabe erzeugt React eine ID. ### data Konfiguration vom Typ `ArcWidgetButtonData`. Übergib Änderungen als neue Objekte; verwalte fachliche Daten und Auswahl in React-State. ### onAction Optionaler Fallback `(type: string, ...args: unknown[]) => unknown`. Der konkrete Callback hat Vorrang. Details: [State und Events](/docs/react/patterns/react-state-events). ### onClick Meldet den Klick. Beachte die widgetspezifischen Argumente unten. ```typescript onClick?: () => void | Promise| Parameter | Typ | Beschreibung |
|---|---|---|
| `showBadge` | *boolean* | Legt fest, ob ein Badge angezeigt wird (`true` / `false`). Kann auch per Skript bestimmt werden: `if cnt(Dokumente)>0 then true end ` |
| `badgeTitle` | *text* | Text oder Zahl im Badge |
| `badgeColor` | *text* | Textfarbe im Badge (z. B. `"#ffffff"`) |
| `badgeBackground` | *text* | Hintergrundfarbe des Badges (z. B. `"#e9595d"`) |
| `badgeBorderColor` | *text* | Rahmenfarbe des Badges (z. B. `"#ffffff"`) |
| `badgePosition` | *text* | Zusätzliche Positionierung des Badges *(z. B. *`*"outside"*`* für Darstellung außerhalb des Buttons)* |
| `badgeGap` | *text* | Abstand zwischen Button-Inhalt und Badge in der Standard-Position (Standard: `"6px"`). Wird bei `badgePosition: "outside"` nicht angewendet. |
| Feld | Typ | Beschreibung | Beispielwert |
|---|---|---|---|
| `fontColor` | *text* | Textfarbe beim Hover | `"#ffffff"` |
| `backgroundColor` | *text* | Hintergrundfarbe beim Hover | `"#004C8A"` |
| `borderColor` | *text* | Rahmenfarbe beim Hover | `"#003366"` |
| `iconColor` | *text* | Farbe des Icons beim Hover (nur bei SVG-Icons relevant) | `"#ffffff"` |
| `animation` | *text* | CSS-Transition-Zeit für sanfte Effekte | `"0.25s"` |
| `type` | Beschreibung | Code-Beispiel |
|---|---|---|
| `"openUrl"` | Öffnet einen externen Link in einem neuen Tab | ```javascript {type: "openUrl", value: "https://example.com" } ``` |
| `"openRecord"` | Öffnet einen bestehenden Datensatz im aktuellen Fenster | ```javascript { type: "openRecord", recordId: first(select Dashboard).Nr} ``` |
| `"openFullscreen"` | Öffnet einen Datensatz im Vollbild-Modus | ```javascript { type: "openFullscreen", recordId: Nr, tab: "Details" } ``` |
| `"popup"` | Öffnet einen Datensatz als Popup | ```javascript { type: "popup", recordId: Nr, tab: "Info" } ``` |
| `"closeRecord"` | Schließt den aktuellen Datensatz | ```javascript { type: "closeRecord", recordId: Nr } ``` |
| `"closeFullscreen"` | Schließt den Vollbildmodus | ```javascript { type: "closeFullscreen", recordId: Nr } ``` |
| `"closeAllRecords"` | Schließt alle geöffneten Datensätze | ```javascript { type: "closeAllRecords" } ``` |
| `"delete"` | Löscht den angegebenen Datensatz | ```javascript { type: "delete", recordId: Nr } ``` |
| `"update"` | Aktualisiert ein Feld in einem Datensatz | ```javascript { type: "update", recordId: Auftrag.Nr, field: "A1", value: "Erledigt" } ``` |
| `"create"` | Erstellt einen neuen Datensatz mit optionalem Popup | ```javascript { type: "create", tableId: "A", popup: true, changeFieldValues: [{ fieldId: "AZ", value: "Neu" }] } ``` |
| `"customJS"` | Führt benutzerdefinierten JavaScript-Code aus | ```javascript { type: "customJS", value: "alert('Hallo Welt!');" } ``` |
| Parameter | Typ | Beschreibung |
|---|---|---|
| `showBadge` | *boolean* | Legt fest, ob ein Badge angezeigt wird (`true` / `false`). Kann auch per Skript bestimmt werden: `if cnt(Dokumente)>0 then true end ` |
| `badgeTitle` | *text* | Text oder Zahl im Badge |
| `badgeColor` | *text* | Textfarbe im Badge (z. B. `"#ffffff"`) |
| `badgeBackground` | *text* | Hintergrundfarbe des Badges (z. B. `"#e9595d"`) |
| `badgeBorderColor` | *text* | Rahmenfarbe des Badges (z. B. `"#ffffff"`) |
| `badgePosition` | *text* | Zusätzliche Positionierung des Badges *(z. B. *`*"outside"*`* für Darstellung außerhalb des Buttons)* |
| `badgeGap` | *text* | Abstand zwischen Button-Inhalt und Badge in der Standard-Position (Standard: `"6px"`). Wird bei `badgePosition: "outside"` nicht angewendet. |
| Feld | Typ | Beschreibung | Beispielwert |
|---|---|---|---|
| `fontColor` | *text* | Textfarbe beim Hover | `"#ffffff"` |
| `backgroundColor` | *text* | Hintergrundfarbe beim Hover | `"#004C8A"` |
| `borderColor` | *text* | Rahmenfarbe beim Hover | `"#003366"` |
| `iconColor` | *text* | Farbe des Icons beim Hover (nur bei SVG-Icons relevant) | `"#ffffff"` |
| `animation` | *text* | CSS-Transition-Zeit für sanfte Effekte | `"0.25s"` |
| `type` | Beschreibung | Code-Beispiel |
|---|---|---|
| `"openUrl"` | Öffnet einen externen Link in einem neuen Tab | ```javascript {type: "openUrl", value: "https://example.com" } ``` |
| `"openRecord"` | Öffnet einen bestehenden Datensatz im aktuellen Fenster | ```javascript { type: "openRecord", recordId: first(select Dashboard).Nr} ``` |
| `"openFullscreen"` | Öffnet einen Datensatz im Vollbild-Modus | ```javascript { type: "openFullscreen", recordId: Nr, tab: "Details" } ``` |
| `"popup"` | Öffnet einen Datensatz als Popup | ```javascript { type: "popup", recordId: Nr, tab: "Info" } ``` |
| `"closeRecord"` | Schließt den aktuellen Datensatz | ```javascript { type: "closeRecord", recordId: Nr } ``` |
| `"closeFullscreen"` | Schließt den Vollbildmodus | ```javascript { type: "closeFullscreen", recordId: Nr } ``` |
| `"closeAllRecords"` | Schließt alle geöffneten Datensätze | ```javascript { type: "closeAllRecords" } ``` |
| `"delete"` | Löscht den angegebenen Datensatz | ```javascript { type: "delete", recordId: Nr } ``` |
| `"update"` | Aktualisiert ein Feld in einem Datensatz | ```javascript { type: "update", recordId: Auftrag.Nr, field: "A1", value: "Erledigt" } ``` |
| `"create"` | Erstellt einen neuen Datensatz mit optionalem Popup | ```javascript { type: "create", tableId: "A", popup: true, changeFieldValues: [{ fieldId: "AZ", value: "Neu" }] } ``` |
| `"customJS"` | Führt benutzerdefinierten JavaScript-Code aus | ```javascript { type: "customJS", value: "alert('Hallo Welt!');" } ``` |
| Paramter | **Beschreibung** |
|---|---|
| visible | Zeigt die Legende an (`true`) oder blendet sie aus (`false`). |
| position | Platzierung: `"left"`, `"right"`, `"top"` oder `"bottom"` |
| label | Was soll in der Legende stehen? Ninox Felder referenzieren oder eigene Textbezeichnungen wählen. |
| fontSize | Schriftgröße der Labels, z. B. `"12px"` |
| fontWeight | Schriftstärke: `"normal"`, `"bold"`, `"500"` usw. |
| fontColor | Farbe des Legendentextes, z. B. `"#333"` |
| gap | Abstand zwischen den Einträgen, z. B. `"6px"` |
| Paramter | **Beschreibung** |
|---|---|
| title | Haupttext in der Mitte (z. B. „Gesamt“) |
| subtitle | Untertitel direkt darunter (z. B. eine Zahl oder Status) |
| fontSize | Größe beider Texte (gilt für beide), z. B. `"18px"` |
| fontColor | Farbe der Schrift, z. B. `"#444"` |
{name} · {style} · {size}px
> ); } ``` ## Komponenten-Props ### id Optionale, stabile ID der Widget-Instanz. Ohne Angabe erzeugt React eine ID. ### data Konfiguration vom Typ `ArcWidgetIconData`. Übergib Änderungen als neue Objekte; verwalte fachliche Daten und Auswahl in React-State. ### onAction Optionaler Fallback `(type: string, ...args: unknown[]) => unknown`. Der konkrete Callback hat Vorrang. Details: [State und Events](/docs/react/patterns/react-state-events). Dieses Widget ist eine Darstellungskomponente und stellt keine eigenen Interaktions-Callbacks bereit. ## Konfiguration Die folgenden Felder gehören in `data`. Größen werden als Strings angegeben, Zahlen und Zeitwerte entsprechend dem jeweiligen Typ. ### iconSet Name des verwendeten Icon-Sets. Typ: `'phosphor' | 'openmoji' | 'material_symbols' | 'material' | string`. ### name Name des Symbols im gewählten Set. Typ: `string`. ### style Variante innerhalb des Icon-Sets. Typ: `string`. ### iconSize Größe des Symbols. Typ: `string`. ### color Symbol- oder Inhaltsfarbe. Typ: `string`. ### containerSize Größe des umgebenden Symbolbereichs. Typ: `string`. ### backgroundColor Hintergrundfarbe. Typ: `string`. ### borderRadius Abrundung der Ecken. Typ: `string`. ### borderSize Rahmenstärke. Typ: `string`. ### borderColor Rahmenfarbe. Typ: `string`. ## Verhalten und Grenzen - Icons werden bei Bedarf geladen und im Browser zwischengespeichert. Phosphor- und Material-SVGs nutzen bei leerem Cache `https://universe.arc-rider.com/cdn/`; diese Verbindung muss erreichbar und in der Content Security Policy deiner App erlaubt sein. - Setze `iconSet` und den dazu passenden `name`. Größe und Farbe gehören in `iconSize` und `color`. - Das Widget ist eine Darstellungskomponente. Interaktion und zugängliche Beschriftung liegen beim umgebenden Steuerelement. Unterstützte `ArcWidgetSlot`-Felder erlauben einzelne JSX-Elemente, Text oder Widget-Deskriptoren. Reiche fremden Text als JSX-Text weiter; HTML-Strings werden als HTML interpretiert. [Inhalte kombinieren](/docs/react/patterns/nested-widget-descriptors). ## Datentypen Diese widgetbezogenen Typen werden aus dem Paket exportiert. Gemeinsame Typen wie `ArcWidgetSlot`, `ArcWidgetStyles` und `ArcReactWidgetProps` sind ebenfalls verfügbar. Indexsignaturen erlauben Erweiterungen; sie sind keine Zusage, dass beliebige Felder im Widget ausgewertet werden. ```typescript export interface ArcWidgetIconData extends ArcWidgetBaseData { iconSet?: 'phosphor' | 'openmoji' | 'material_symbols' | 'material' | string; name: string; style?: string; iconSize?: string; color?: string; containerSize?: string; backgroundColor?: string; borderRadius?: string; borderSize?: string; borderColor?: string; } ``` ## Einsatzbeispiele - [State und Events](/docs/react/patterns/react-state-events) - [Fehlerbehebung](/docs/react/faq/react-troubleshooting) # Icon ## AI Defaults (read first) - **Function name**: `arcCustomIcon`. - **iconSet**: `"phosphor"` (default if unset), `"openmoji"`, `"material_symbols"` (alias `"material"`). - **name**: Glyph name (e.g. `"user"`, `"trash"`). Phosphor legacy: style in name (`"trash-bold"`) still works and overrides explicit **style**. - **style**: Phosphor: `regular` | `bold` | `duotone` | `light` | `thin` | `fill`. Material: `outlined` | `rounded` | `sharp` | `rounded-fill` | … (default Phosphor `regular`, Material `outlined`). - **iconSize**, **color**: Icon size and fill color (e.g. `"24px"`, `"#818599"`). - **containerSize**, **backgroundColor**, **borderRadius**, **borderSize**, **borderColor**: Built-in container – do NOT wrap in `arcCustomLayout` for styling. - **No uniqueId needed** for display-only icons. # Custom Icon Jeder Kunde erhält zum Start seiner Lizenz ein Standard Icon-Set. Solltest du mehr Icons benötigen, kontaktiere uns über [support@arc-rider.com](mailto:support@arc-rider.com) oder in unserem [Discord-Channel.](https://discord.com/invite/Fg67f8qX3K) 💡** Hinweis**: Custom Icon unterstützt [Phosphor-Icons](https://phosphoricons.com/), [Material Symbols](https://fonts.google.com/icons) (`iconSet: "material"` oder `"material_symbols"`) und [OpenMoji](https://openmoji.org/). Nach dem ersten Laden werden Phosphor- und Material-Icons im Browser zwischengespeichert – wiederholte Aufrufe sind schneller und funktionieren offline, sobald das Set einmal vollständig geladen wurde (keine Extra-Konfiguration nötig). Um Icons zu deinem Projekt hinzuzufügen, gehst du über dein User Cockpit zum Bereich Icons und wählst diese einfach an (siehe weiter unten die Erklärung).    ## Anwendungscode  ```javascript arcCustomIcon({ iconSet: "phosphor", name: "trash", containerSize: "40px", borderSize: "", borderColor: "", borderRadius: "7px", iconSize: "30px", color: "#FF2222", backgroundColor: "#fcdfde" }) ```  ```javascript arcCustomIcon({ iconSet: "openmoji", name: "envelope-with-arrow-color", containerSize: "50px", borderSize: "1px", borderColor: "", iconSize: "50px", borderRadius: "50px", color: "#C637E4", backgroundColor: "#FAE6FF" }), ``` iconSet definiert das Iconset. ```javascript iconSet: "phosphor", // Standard-Fallback iconSet: "material", // Alias für material_symbols iconSet: "material_symbols", iconSet: "openmoji", ``` Wenn du **kein** `iconSet` angibst, nutzt das Widget das zuerst vollständig vorinstallierte Set (Reihenfolge: Phosphor → Material → OpenMoji), sonst Phosphor. style (optional) wählt die Variante innerhalb des Sets. **Phosphor** — `regular` (Standard), `bold`, `duotone`, `light`, `thin`, `fill`: ```javascript arcCustomIcon({ iconSet: "phosphor", name: "trash", style: "bold", color: "#c00" }) ``` **Legacy:** Stil im Namen (`name: "trash-bold"`) funktioniert weiterhin und hat Vorrang vor einem gesetzten `style`. **Material Symbols** — `outlined` (Standard), `rounded`, `sharp`, gefüllte Varianten mit Suffix `-fill` (z. B. `rounded-fill`): ```javascript arcCustomIcon({ iconSet: "material", name: "home", style: "rounded-fill", iconSize: "24px" }) ``` name definiert den Glyph-Namen (ohne Stil-Suffix, wenn du `style` separat setzt). Namen findest du im Cockpit bzw. in den jeweiligen Icon-Katalogen. ```javascript name: "trash", // Titel immer in " schreiben ``` containerSize definiert, ob und in welcher Größe du deinen Container/Background des Icons anzeigen lassen möchtest. ```javascript containerSize: "30px", // Pixel-Wert in " angeben containerSize: "" // Kein Fallback-Wert ``` ***borderSize*** definiert, wie dick deine Umrandung sein soll. ```javascript borderSize: "1px", // Pixel-Wert in " angeben borderSize: "", // Kein Fallback-Wert ``` ***borderColor*** gibt die Farbe deiner Border/Umrandung an. ```javascript borderColor: "#000", // HEX Code in " angeben borderColor: "", // Kein Fallback-Wert ``` ***borderRadius*** gibt die Abrundung deiner Border-Ecken an. ```javascript borderRadius: "7px", // Pixel-Wert in " angeben borderRadius: "", // Kein Fallback ``` ***iconSize*** gibt die Größe deines Icons an. ```javascript iconSize: "20px", // Pixel-Wert in " angeben iconSize: "", // Fallback: "15px" ``` ***color*** gibt die Farbe deines Icons an. ```javascript color: "#FF2222", // Hex-Wert in " angeben color: "", // Kein Fallback-Wert ``` ***backgroundColor*** gibt die Hintergrund-Farbe des Containers an. ```javascript backgroundColor: "#fcdfde", // backgroundColor: "", // Kein Fallbackwert ``` ## Icons über dein Cockpit hinzufügen Du kannst die deine Aktuelle Sammlung und die verfügbaren Iconsets anzeigen lassen. Filter für Icon Styles oder Icon Gruppen findest du auf der rechten Seit. Um ein Icon an- oder abzuwählen, klicke es einfach an. Kopiere dann den Widget Code und füge ihn in deinen globalen Code. Schon kannst du sie im Anwedungscode nutzen.  --- # Images Source: https://www.arc-rider.com/docs/mini-widgets/images # Images ## AI Defaults (read first) - **Function name**: `arcCustomImage`. - **value**: Image URL or Base64 string. - **width**, **height**: Set dimensions directly. - **borderRadius**: E.g. `"8px"` for rounded corners, `"50%"` for circular. - **objectFit**: `"cover"` (crop to fill) or `"contain"` (fit inside). # Custom Image  Mit Images kannst du Bilder in Tabellen, Kanban oder anderen Widgets laden. Du hast etliche Einstellungen, von Umrandung bis Abrundung über Größeneinstellungen. ```javascript arcCustomImage({ uniqueId: "ProjektBild"+Nr, title: "Logo", width: "80px", height: "80px", backgroundSize: "cover", backgroundColor: "", style: "", // Hier kannst du eigene CSS Styles hinterlegen. radius: "5px", borderColor: "", link: text(Projekt.'Bild URL') // Hier musst du immer einen shareLink deines Bildes hinterlegen. }) ``` #### Allgemeine Settings ### uniqueId Die ***uniqueId*** vergibst du individuell und sollte einzigartig sein. Sinn dahinter ist: Wenn du mehrere Images mit unterschiedlichen Settings auf deiner Oberfläche erstellst, überschreiben sich die Styles nicht. ```javascript uniqueId: "ProjektBild" + Nr // Text in " + Ninox Nr ``` ### title ```javascript title: "Logo", ``` ### width Mit*** width*** kannst du die Breite des Bild Containers einstellen. ```javascript width: "100%", // Prozent-Werte. Hier: volle Breite. width: "100px", // Pixel-Werte ``` ### height Mit*** height*** kannst du die Höhe des Bilde Containers einstellen. ```javascript height: "auto", // Automatisch an den Inhalt angepasste Höhe. height: "300px", // Pixel-Werte ``` ### backgroundSize Mit ***backgroundSize*** definierst du die Größe des Bildes im Container. ```javascript backgroundSize: "cover", // Füllt den Container komplett aus, kann dazu führen, dass das Bild abgeschnitten wird. backgroundSize: "contain", // Das Bild wird komplett in dem Container angezeigt. backgroundSize: "50%", backgroundSize: "auto", // Bild wird in der Originalgröße angezeigt. backgroundSize: "15px 10px", // Gibt Breite und Höhe in Pixel an. ``` ### backgroundColor Mit ***backgroundColor*** kannst du die Farbe des Hintergrundes bestimmen. Vor allem nützlich, wenn kein Bild angezeigt wird oder für Bilder, die nicht auf die Gesamtgröße angepasst wurden. Es ist auch möglich, den Hintergrund transparent zu machen. ```javascript backgroundColor: "#1D8C96", // HEX-Code für Farben backgroundColor: "transparent", // für einen transparenten Hintergrund ``` ### style Bei dem*** style*** Parameter kannst du eigene CSS styles hinzufügen. ```javascript style: "opacity 30%;", // CSS styles ```{t('Suche', 'Search', 'Búsqueda')}: {query || t('Alle', 'All', 'Todos')}{' '} · {t('Letztes Ereignis', 'Last event', 'Último evento')}: {last || '–'}
> ); } ``` ## Features ausprobieren ### Live-Update, Debounce, Enter und Reset Tippe einen Suchbegriff, ändere die Verzögerung und teste Enter sowie das Zurücksetzen. Unter dem Feld siehst du den übernommenen React-State. ```tsx import { useState } from 'react'; import { ArcWidgetInput } from '@arcrider/arcwidgets-react'; export default function Example() { const [value, setValue] = useState(''); const [delay, setDelay] = useState(300); const [event, setEvent] = useState('Noch kein Ereignis'); return ( <>React-State: {value || 'leer'} · {event}
> ); } ``` ## Komponenten-Props ### id Optionale, stabile ID der Widget-Instanz. Ohne Angabe erzeugt React eine ID. ### data Konfiguration vom Typ `ArcWidgetInputData`. Übergib Änderungen als neue Objekte; verwalte fachliche Daten und Auswahl in React-State. ### onAction Optionaler Fallback `(type: string, ...args: unknown[]) => unknown`. Der konkrete Callback hat Vorrang. Details: [State und Events](/docs/react/patterns/react-state-events). ### onChange Übernimmt den neuen Eingabewert in deinen State. ```typescript onChange?: (value: string | number | null) => void | Promise| Parameter | Typ | Beschreibung |
|---|---|---|
| `uniqueId` | Text | Eindeutige ID für das Pagination-Element. Pflichtfeld. |
| `editable` | Boolean | Ermöglicht manuelle Eingabe der Seitenzahl im Input-Feld. |
| `recordId` | Text | ID des aktuellen Datensatzes, auf den das Update erfolgt. (Die Aktion ausgeführt wird) |
| `fieldId` | Text | Das Feld, das die aktuelle Seite speichert. |
| `direction` | Text | Layout der Pagination: `"horizontal"` (Standard) oder `"vertical"`. |
| `title` | Text | Der aktuell angezeigte Wert im Input-Feld. |
| `value` | Zahl | Aktuelle Seitenzahl – wichtig für Navigation und Bedingung von Buttons. |
| `total` | Zahl | Gesamtzahl an Seiten (z. B. `ceil(cnt(list) / 30)`). |
| `totalPrefix` | Text | Kleiner Zusatz vor der Gesamtseitenanzahl, z. B. `" von "`. |
| `valuePrefix` | Text | Optionaler String, der beim Update vor die Seitenzahl gesetzt wird. |
| `buttonFirst` | Widget | Optionaler Inhalt (Text oder Widget) für den „erste Seite“-Button. |
| `buttonMinus` | Widget | Optionaler Inhalt (Text oder Widget) für den „zurück“-Button. |
| `buttonPlus` | Widget | Optionaler Inhalt (Text oder Widget) für den „vor“-Button. |
| `buttonLast` | Widget | Optionaler Inhalt (Text oder Widget) für den „letzte Seite“-Button. |
{value}% des Ziels erreicht
> ); } ``` ## Komponenten-Props ### id Optionale, stabile ID der Widget-Instanz. Ohne Angabe erzeugt React eine ID. ### data Konfiguration vom Typ `ArcWidgetProgressBarData`. Übergib Änderungen als neue Objekte; verwalte fachliche Daten und Auswahl in React-State. ### onAction Optionaler Fallback `(type: string, ...args: unknown[]) => unknown`. Der konkrete Callback hat Vorrang. Details: [State und Events](/docs/react/patterns/react-state-events). Dieses Widget ist eine Darstellungskomponente und stellt keine eigenen Interaktions-Callbacks bereit. ## Konfiguration Die folgenden Felder gehören in `data`. Größen werden als Strings angegeben, Zahlen und Zeitwerte entsprechend dem jeweiligen Typ. ### valueProgress Aktueller Teilwert. Typ: `number | string`. ### valueTotal Gesamtwert; muss größer als null sein. Typ: `number | string`. ### valueText Eigene Beschriftung des Fortschritts. Typ: `string`. ### progressColor Farbe des gefüllten Fortschrittsbereichs. Typ: `string`. ### backgroundColor Hintergrundfarbe. Typ: `string`. ### width Breite des Widgets als Größenangabe. Typ: `string`. ### height Höhe des Widgets, z. B. `"320px"`. Prozentwerte benötigen einen Elterncontainer mit festgelegter Höhe. Typ: `string`. ### borderRadius Abrundung der Ecken. Typ: `string`. ### fontColor Schriftfarbe. Typ: `string`. ### fontSize Schriftgröße als CSS-Wert. Typ: `string`. ## Verhalten und Grenzen - `valueProgress` und `valueTotal` bilden das Verhältnis. Verwende einen Gesamtwert größer als null. - `valueText` ist eine eigene Beschriftung. Die Berechnung und Formatierung fachlicher Werte erfolgt in React. Unterstützte `ArcWidgetSlot`-Felder erlauben einzelne JSX-Elemente, Text oder Widget-Deskriptoren. Reiche fremden Text als JSX-Text weiter; HTML-Strings werden als HTML interpretiert. [Inhalte kombinieren](/docs/react/patterns/nested-widget-descriptors). ## Datentypen Diese widgetbezogenen Typen werden aus dem Paket exportiert. Gemeinsame Typen wie `ArcWidgetSlot`, `ArcWidgetStyles` und `ArcReactWidgetProps` sind ebenfalls verfügbar. Indexsignaturen erlauben Erweiterungen; sie sind keine Zusage, dass beliebige Felder im Widget ausgewertet werden. ```typescript export interface ArcWidgetProgressBarData extends ArcWidgetBaseData { valueProgress?: number | string; valueTotal?: number | string; valueText?: string; progressColor?: string; backgroundColor?: string; width?: string; height?: string; borderRadius?: string; fontColor?: string; fontSize?: string; } ``` ## Einsatzbeispiele - [Dashboard mit CSS Grid](/docs/react/examples/dashboardansicht-mit-grid) - [State und Events](/docs/react/patterns/react-state-events) - [Fehlerbehebung](/docs/react/faq/react-troubleshooting) # Progress Bar ## AI Defaults (read first) - **Function name**: `arcCustomProgressBar`. - **valueProgress**: Current progress (numerator), as number. - **valueTotal**: Target / total (denominator), as number. - **progressColor**: Fill color (optional; auto amber / green / black by ratio when omitted). - **backgroundColor**: Track background color. - **height**: Bar height, e.g. `"8px"`. - **valueText**: Optional label beside the bar. - Display-only — no `actions` / persist. # Progress Bar  Die Progress Bar besteht aus einem Anwendungscode, den du in einem Ninox Formel-Feld auf deiner Wunschoberfläche in der Datenbank einfügst. ## Gesamter Anwendungscode Folgend siehst du einen beispielhaften Anwendungscode für eine Progress Bar: ```javascript arcCustomProgressBar({ uniqueId: "", width: "", height: "", borderRadius: "", fontSize: "", fontColor: "", backgroundColor: "", progressColor: "", valueTotal: "", valueProgress: "", valueText: "" }) ``` ***uniqueId*** vergibst du individuell und sollte einzigartig auf deiner Oberfläche sein. Sinn dahinter ist: Wenn du mehrere Progress Bars mit unterschiedlichen Settings auf deiner Seite erstellst, überschrieben sich die Styles nicht. ```javascript uniqueId: "Progress 1", // Text in " ``` ***width*** gibt die Weite deiner Progress Bar an. ```javascript width: "300px", // Pixel-Werte in " width: "100%", // Prozent-Werte in " / Orientiert sich prozentual an der Breite des Formelfeldes. ``` ***height*** gibt die Höhe deiner Progress Bar an. ```javascript height: "300px", // Pixel-Werte in " height: "100%", // Prozent-Werte in " / Orientiert sich prozentual an der Breite des Formelfeldes. ``` ***borderRadius*** gibt die Abrundung der Progressbar-Ecken an. ```javascript borderRadius: "4px", // Pixel-Werte in " borderradius: "", // default: 50% ``` ***fontSize*** gibt die Schriftgröße der Beschriftung von ***valueText ***wieder. ```javascript fontSize: "14px", // Pixel-Werte in " ``` ***fontColor*** gibt die Schriftfarbe der Beschriftung von ***valueText ***wieder. ```javascript fontColor: "#FFFFFF", // HEX Code in " ``` ***backgroundColor*** definiert die Hintergrundfarbe deiner Progress Bar ```javascript backgroundColor: "#EEEEEE", // HEX Code in " ``` ***progressColor*** definiert die Farbe des Fortschrittbalkens. ```javascript progressColor: "#FFBF00", // HEX Coe in " progressColor: if Status= 2 then "#FFBF00" else "#e9ecf4", // Ninox-Formeln, -Schreibweisen & -Konfditionen möglich. Wichtig ist, dass text() bei ausgegeben wird. ``` ***valueTotal*** definiert den Gesamtwert deiner Progress Bar. Sozusagen den Nenner einer Bruchzahl. ```javascript valueTotal: 20, // Zahlenwert valueTotal: Projekt.umsatzZiel, // Referenzierte Ninox-Felder. Auch gehen Ninox-Funktionen & -Schreibweisen. ``` ***valueProgress*** definiert den bislang erreichten Wert. Den Zähler einer Bruchzahl. ```javascript valueProgress: 10, // Zahlenwert valueProgress: Projekt.umsatzAktuell// Referenzierte Ninox-Felder. Auch gehen Ninox-Funktionen & -Schreibweisen. ``` ***valueText*** gibt die Bezeichnung neben deiner Progress Bar aus. Sinnvoll ist es, hier die Werte als Text auszugeben, ```javascript valueText: Projekt.umsatzAktuell + " / " + Projekt.umsatzZiel ``` ## Standard-Werte als Fallback 💡 **Hinweis: **Trägst du keine Werte hinter die Parameter ein, so ist in den meisten Fällen ein Fallback-Wert im System hinterlegt. An manchen Stellen, wie Berechnungsfelder (Tatsächliche Zahlenwerte zum Beispiel) macht das keinen Sinn. In Style-Parametern, wie Farben oder Größen hingegen schon. Hier siehst du den Code ohne eingetragene Werte und in den Kommentaren dahinter die Standard-Werte: ```javascript arcCustomProgressBar({ uniqueId: "", // Kein Fallback width: "", // Fallback: 100% fontSize: "", // Fallback: 12px fontColor: "", // Fallbacke: #000000 (schwarz) backgroundColor: "", // Fallback: #EEEEEE progressColor: "", // Fallback: if data.progressColor = " then switch complete do case true: "#76CD3C" // gelb case false: if number(data.valueTotal) < number(data.valueProgress) then "#000" else "#FFBF00" // grün end default: "#e9ecf4" end else text(data.progressColor) end; valueTotal: "", // Kein Fallback valueProgress: "", // Kein Fallback valueText: "" // Kein Fallback }) ``` --- # Select (Dropdown) Source: https://www.arc-rider.com/docs/mini-widgets/select # Select in React Eine Option aus einer Liste wählen. Wert, sichtbare Beschriftung und aktive Einträge werden aus React-State abgeleitet. Referenz für `@arcrider/arcwidgets-react@0.1.0-alpha.5`. [Installation](/docs/react/intro/setup-react) · [Alle Komponenten](/docs/react/intro/react-components). ## Vollständiges Beispiel ```tsx import { ArcWidgetSelect } from '@arcrider/arcwidgets-react'; import { useState } from 'react'; const lang = document.documentElement.lang.startsWith('en') ? 'en' : document.documentElement.lang.startsWith('es') ? 'es' : 'de'; const requested = new URLSearchParams(location.search).get('data'); const pack = requested === 'empty' || requested === 'busy' ? requested : 'demo'; const t = (de: string, en: string, es: string) => lang === 'en' ? en : lang === 'es' ? es : de; const demoOptions = [ { value: 'open', title: t('Offen', 'Open', 'Abierto') }, { value: 'work', title: t('In Arbeit', 'In progress', 'En curso') }, { value: 'done', title: t('Erledigt', 'Done', 'Hecho') }, ]; const busyOptions = [ ...demoOptions, { value: 'wait', title: t('Wartet', 'Waiting', 'En espera') }, { value: 'hold', title: t('Pausiert', 'On hold', 'En pausa') }, ]; const options = pack === 'empty' ? [] : pack === 'busy' ? busyOptions : demoOptions; export default function Example() { const [status, setStatus] = useState{t('Status', 'Status', 'Estado')}: {status ?? all}
> ); } ``` ## Features ausprobieren ### Auswahl, Zurücksetzen und deaktivierter Zustand Wähle einen Status und prüfe das separate Badge mit derselben Auswahl. Teste anschließend die leere und deaktivierte Variante. ```tsx import { useState } from 'react'; import { ArcWidgetSelect, ArcWidgetBadge } from '@arcrider/arcwidgets-react'; export default function Example() { const [selected, setSelected] = useStateAuswahl: {selected ?? 'Alle'}
> ); } ``` ## Komponenten-Props ### id Optionale, stabile ID der Widget-Instanz. Ohne Angabe erzeugt React eine ID. ### data Konfiguration vom Typ `ArcWidgetSelectData`. Übergib Änderungen als neue Objekte; verwalte fachliche Daten und Auswahl in React-State. ### onAction Optionaler Fallback `(type: string, ...args: unknown[]) => unknown`. Der konkrete Callback hat Vorrang. Details: [State und Events](/docs/react/patterns/react-state-events). ### onChange Übernimmt den neuen Eingabewert in deinen State. ```typescript onChange?: (value: string | number | null) => void; ``` ## Konfiguration Die folgenden Felder gehören in `data`. Größen werden als Strings angegeben, Zahlen und Zeitwerte entsprechend dem jeweiligen Typ. ### items Einträge der Auswahl oder der Darstellung; siehe den zugehörigen Datentyp. Typ: `ArcWidgetSelectItem[]`. ### width Breite des Widgets als Größenangabe. Typ: `string`. ### height Höhe des Widgets, z. B. `"320px"`. Prozentwerte benötigen einen Elterncontainer mit festgelegter Höhe. Typ: `string`. ### itemsWidth Breite der Auswahlliste. Typ: `string`. ### itemsHeight Höhe der Auswahlliste. Typ: `string`. ### alignX Horizontale Ausrichtung. Typ: `string`. ### fontColor Schriftfarbe. Typ: `string`. ### fontSize Schriftgröße als CSS-Wert. Typ: `string`. ### fontWeight Schriftstärke. Typ: `string | number`. ### backgroundColor Hintergrundfarbe. Typ: `string`. ### borderColor Rahmenfarbe. Typ: `string`. ### borderWidth Rahmenstärke. Typ: `string`. ### borderRadius Abrundung der Ecken. Typ: `string`. ### disabled Deaktiviert die Eingabe. Typ: `boolean`. ### multiselect Hält die Liste zur Mehrfachauswahl offen; der Callback liefert einen Einzelwert. Typ: `boolean`. ### emptyValue Beschriftung bei leerer Auswahl. Typ: `string`. ### clickPosition Positionierung beim Öffnen. Typ: `string`. ### currentValue Aktueller Anzeigewert. Typ: `ArcWidgetSlot`. ### icon Optionales Symbol bzw. unterstützter Inhaltsslot. Typ: `ArcWidgetSlot`. ### placeholder Hinweis bei leerem Wert. Typ: `string`. ### focusAction Einstellungen beim Fokussieren, etwa `autoFocus`. Typ: `{ autoFocus?: boolean; [key: string]: unknown }`. ### reset Konfiguration der Rücksetzfunktion. Typ: `ArcWidgetSelectReset`. ## Verhalten und Grenzen - `items[].value` ist der Callback-Wert. `currentValue` ist die sichtbare Beschriftung; `items[].active` markiert die Auswahl. Halte alle drei aus demselben State aktuell. - `multiselect: true` hält die Liste offen. Der Callback liefert weiterhin den einzelnen angeklickten Wert; Hinzufügen oder Entfernen aus einer Auswahlmenge implementierst du selbst. - Reset liefert `null`. Behandle diesen Fall ausdrücklich. Verwende bei Select in `0.1.0-alpha.5` Text für die Optionsbeschriftungen. Direkte JSX-Elemente führen beim internen Datenvergleich zu einem Fehler; Widget-Deskriptoren werden im ausgelagerten Dropdown noch nicht gerendert. Das Live-Beispiel zeigt ein separates Badge aus demselben Auswahl-State. HTML-Strings werden als HTML interpretiert. [Inhalte kombinieren](/docs/react/patterns/nested-widget-descriptors). ## Datentypen Diese widgetbezogenen Typen werden aus dem Paket exportiert. Gemeinsame Typen wie `ArcWidgetSlot`, `ArcWidgetStyles` und `ArcReactWidgetProps` sind ebenfalls verfügbar. Indexsignaturen erlauben Erweiterungen; sie sind keine Zusage, dass beliebige Felder im Widget ausgewertet werden. ```typescript export interface ArcWidgetSelectItem { title?: ArcWidgetSlot; active?: boolean; value?: string | number; [key: string]: unknown; } export interface ArcWidgetSelectReset { title?: ArcWidgetSlot; value?: string | number; hide?: boolean; [key: string]: unknown; } export interface ArcWidgetSelectData extends Omit`Enter` bestätigt eine Auswahl, das Menü bleibt offen. - `Tab` wechselt direkt zum nächsten Feld, ohne automatisch zu bestätigen.
*!! Vorerst bleibt der bisherige Einstellungs-Parameter *`*emptyValue: "zurücksetzen"*`*noch aktiv, nutzt aber bitte in Zukunft für diese Einstellungen den Parameter *`*title: "zurücksetzen"*`* im reset Block.*```javascript emptyValue: "empty", // eigener Text emptyValue: "", // default: "(leer)") ``` ### reset Block Der `reset`-Block steuert, ob und wie im Dropdown-Menü eine **„Zurücksetzen“-Option** angezeigt wird, mit der der aktuelle Wert gelöscht oder auf einen definierten Wert zurückgesetzt werden kann. - `title` – Der angezeigte Text für den Zurücksetzen-Eintrag (z. B. `"(leer)"`, `"Zurücksetzen"`). - `value` – Der Wert, der gesetzt wird, wenn der Eintrag angeklickt wird. Wenn nicht gesetzt, wird automatisch `emptyValue` verwendet. - `hide` – Wenn `true`, wird der Reset-Eintrag **nicht angezeigt**, kann aber trotzdem intern genutzt werden. ```javascript reset: { title: "reset", value: 0, hide: true }, Einzelerklärungen: title: "reset", // eigener Text title: "", // default: Wert in Parameter emptyValue (falls emptyValue: " dann ist hier default: "(leer)") value: 0, // eigener Wert (0 = setzt den Wert auf 0 statt null zurück) value: "", // default: null (Ninox-Feld wird null gesetzt) hide: true, // select kann nicht zurückgesetzt werden hide: "", // default: false (zurücksetzen Button wird angezeigt) ``` ### focusAction Settingsblock Mit dem Settingsblock `focusAction` kannst du das Verhalten des Suchfelds im Select steuern. **Parameter:** - `autoFocus` – Bestimmt, ob der Cursor automatisch in das Suchfeld springt. Besonders für Tablets, wo die Tastatur sofort aufspringt und den User irritieren könnte, ist das Abschalten hilfreich. - `showCurrentValue` – Wenn `true`, wird der `currentValue` (z.B. deine selbst gebauten Badges via `arcCustomLayout`) links angezeigt und das Suchfeld daneben. Ideal für Multi-Select mit Tag-Darstellung. ```javascript focusAction: { autoFocus: false, // true or false // default: true showCurrentValue: true // true or false // default: false } ``` **🧩 Typische Anwendung für showCurrentValue:** Wenn du im `currentValue` eigene Badges/Tags mit `arcCustomLayout` renderst und diese beim Öffnen des Selects weiterhin sichtbar bleiben sollen (mit Suchfeld daneben), setze `showCurrentValue: true`. ### currentValue Der Parameter `currentValue` legt fest, **welcher Wert im Select-Feld bei ausgewählten items** sein soll. Er bestimmt also die **Anzeige** des Feldes im geschlossenen Zustand. **🧩 Verhalten je nach Modus** - **Single-Select (**`**multiselect: false**`**)** Der Wert wird **direkt als einzelner Text oder Zahl** übergeben (z. B. `"Offen"` oder `1`). - **Multi-Select (**`**multiselect: true**`**)** Der Wert ist ein **Array von Strings oder Zahlen**, z. B. `[Bluse, Kleidung, Oberteil]` oder `[1, 2]`. Jeder enthaltene Wert wird in der Liste als **„aktiv“** markiert. ```javascript currentValue: text(Status), // Bezeichnung deines Ninox-Feldes innerhalb von text() currentValue: arcCustomBadge({ uniqueId: "customBadge" + Nr, embedded: true, width: "100%", icon: "", iconPosition: "", fontSize: "", fontWeight: "700", borderRadius: "", singleColor: color(Status), fontColor: "", backgroundColor: "", borderColor: "", paddingY: "5px", paddingX: "10px", value: text(Status) }), // individuell gestaltetes customBadge mit der Bezeichnung deines Ninox-Feldes bei value currentValue: "", // default: es wird nicht der ausgewählte Wert angezeigt, sondern der placeholder ``` ### items Block **Pflichtfeld:** ✅ Der `items`-Block definiert die **Auswahlmöglichkeiten**, die im Dropdown angezeigt werden. Jede Option wird als Objekt mit bestimmten Eigenschaften angegeben – z. B. sichtbarer Text, interner Wert und aktiver Zustand. **🔄 **Zwei Aufbauvarianten: Du kannst das `customSelect` auf zwei verschiedene Arten mit Werten befüllen: 1. **Dynamisch aus einer Ninox-Tabelle** Ideal für Listen, die sich ändern können oder mit anderen Feldern verknüpft sind. ```javascript items: let id := Nr; (select Status_Projekte).[{ title: Titel, active: number(id.Status) = number(Nr), value: number(Nr) }] ``` 2. **Manuelle Liste im Code** Geeignet, um z. B. ein einfaches Auswahlfeld aus Ninox direkt nachzubauen, bzw. anzusteuern. (ohne externe Tabelle). ```javascript items: [{ title: "Offen", active: Status = 1, value: number(Nr) }, { title: "In Arbeit", active: Status = 2, value: number(Nr) }, { title: "Erledigt", active: Status = 3, value: number(Nr) }] ``` **🧩 Struktur pro Eintrag** - `title` – Anzeige im Dropdown (Text oder Widget, z. B. `arcCustomIcon({ name: "check", iconSize: "16px" })` neben Label-Text) - `value` – Der zu speichernde Wert - `active` – Wann wird dieser Eintrag als ausgewählt markiert 💡 **`icon`** (am Select-Root) und **`currentValue`** unterstützen ebenfalls eingebettete Mini-Widgets – dieselbe Regel wie bei `items[].title`. ## 🎹 Tastaturverhalten im Vergleich
| Taste | Single-Select Verhalten | Multi-Select Verhalten |
|---|---|---|
| **Tab** | ✅ Auswahl wird bestätigt➡️ Fokus springt weiter | ➡️ Fokus springt weiter🚫 Keine Bestätigung |
| **Enter** | ✅ Auswahl wird bestätigt🔒 Select wird geschlossen | ✅ Auswahl wird bestätigt🔓 Select bleibt geöffnet |
| **Escape** | ❌ Select wird geschlossen | ❌ Select wird geschlossen |
{status}
> ); } ``` ## Features ausprobieren ### Zeichnen, Nur-Lesen und Löschen Unterschreibe mit Maus oder Touch und schalte auf Nur-Lesen. Dieses Beispiel speichert ausschließlich im React-State. ```tsx import { useState } from 'react'; import { ArcWidgetSignature } from '@arcrider/arcwidgets-react'; export default function Example() { const [value, setValue] = useState(''); const [readonly, setReadonly] = useState(false); const [width, setWidth] = useState(2); const [status, setStatus] = useState('Noch keine Unterschrift'); return ( <>{status}
> ); } ``` ## Komponenten-Props ### id Optionale, stabile ID der Widget-Instanz. Ohne Angabe erzeugt React eine ID. ### data Konfiguration vom Typ `ArcWidgetSignatureData`. Übergib Änderungen als neue Objekte; verwalte fachliche Daten und Auswahl in React-State. ### onAction Optionaler Fallback `(type: string, ...args: unknown[]) => unknown`. Der konkrete Callback hat Vorrang. Details: [State und Events](/docs/react/patterns/react-state-events). ### onSave Die gezeichnete Unterschrift als Bild-Data-URL. ```typescript onSave?: (dataUrl: string) => void | Promise*Wichtig: Die uniqueId des Themes muss dieselbe sein, wie die uniqueListId der Table, in der das Theme eingesetzt wird !*## Anwendungscode ```javascript arcCustomThemeNaked({ uniqueId: "Aufgaben Demo 1" + Nr, rowSettings: { gap: "10px", borderRadius: "" }, columnSettings: { gap: "40px" } }), ``` ## Beispiel Im folgenden Beispiel siehst du den Anwendungscode des oben dargestellten Bildes. Dieser umfasst eine Custom Table, die Termine von Mitarbeitern darstellt. Hier sind sowohl Header als auch Footer ausgeblendet. Durch das Theme sind keinerlei Rahmenlinien in der Tabelle zu sehen und die einzelnen Zeilen sind durch ein gap von 10px getrennt. ```javascript let current := this; let list := (select Termine from 0 to 30); let data := { uniqueListId: "Aufgaben Demo 1" + Nr, tableId: tableId(first(list)), height: "620px", width: "100px", groupedBy: "", header: { showHeader: false }, theme: arcCustomThemeNaked({ uniqueId: "Aufgaben Demo 1" + Nr, rowSettings: { gap: "10px" }, columnSettings: { gap: "10px" }, borderRadius: "" }), embedded: false, table: list.[{ recordId: Nr, rowColor: "#ededed", rowHeight: "auto", rowPaddingY: "10px", groupRowColor: "", columns: [{ field: "Kategorie Icon", title: "", width: "35px", color: "", backgroundColor: "", align: "left", paddingX: "", paddingY: "", value: arcCustomIcon({ iconSet: "phosphor", name: "circle-fill", iconSize: "25px", color: record('Kategorie Aufgabe',number(Kategorie)).Farbe }), actions: [{ type: "popup", showPopupButton: false, recordId: Nr }] }, { field: "Feldname für Feld-ID", title: "", color: "", backgroundColor: "", width: "", align: "left", paddingX: "", paddingY: "", value: arcCustomLayout({ comment: "---------------------------------- LAYOUT TITEL UND KATEGORIE ----------------------------------", uniqueId: "Titel und Kategorie " + Nr, embedded: true, direction: "vertical", alignX: "left", alignY: "center", width: "100%", height: "", gap: "2px", scrollSettings: { scrollY: false, scrollX: false }, blocks: [{ width: "", height: "auto", lineHeight: "", alignX: "left", color: "", styles: "", value: arcCustomText({ uniqueId: "Bezeichnung " + Nr, fontSize: "13px", fontWeight: "600", fontColor: "", value: Auftrag.last(Aufgaben.Aufgabe) }) }, { width: "", height: "auto", lineHeight: "", alignX: "left", color: "", value: arcCustomText({ uniqueId: "Bezeichnung " + Nr, fontSize: "13px", fontWeight: "", fontColor: "#9aa3af", value: text(Kategorie) }) }] }), actions: [{ type: "popup", showPopupButton: false, recordId: Nr }] }, { field: "", title: "", width: "", color: "", backgroundColor: "", align: "left", paddingX: "", paddingY: "", value: arcCustomLayout({ comment: "---------------------------------- LAYOUT UHRZEIT & DATUM ----------------------------------", uniqueId: "Datum und Uhrzeit " + Nr, embedded: true, fullscreen: false, ninoxVersion: "", page: true, fullscreenMode: "", showAdminTools: true, hideHeaderIcons: true, direction: "horizontal", alignX: "left", alignY: "center", width: "", height: "", gap: "10px", backgroundColor: "", paddingY: "", paddingX: "", styles: "", scrollSettings: { scrollY: false, scrollX: false }, blocks: [{ width: "auto", height: "auto", lineHeight: "", alignX: "left", color: "", styles: "", value: arcCustomIcon({ iconSet: "phosphor", name: "clock", iconSize: "25px", color: "#9aa3af" }) }, { width: "fraction", height: "auto", lineHeight: "", alignX: "left", color: "", value: arcCustomText({ uniqueId: "Bezeichnung " + Nr, fontSize: "13px", fontWeight: "600", fontColor: "#9aa3af", value: 'Datum von' + ", " + Von + " - " + Bis }) }] }), actions: [{ type: "popup", showPopupButton: false, recordId: Nr }] }, { field: "Mitarbeiter", title: "", width: "", color: "", backgroundColor: "", align: "left", paddingX: "", paddingY: "", value: Mitarbeiter.'Bild-und_Name', actions: [{ type: "popup", showPopupButton: false, recordId: Nr }] }] }], footer: { showFooter: false } }; arcCustomTable(data) ``` --- # Ninox: Wie baue ich Builds ein und wie laufen Updates? Source: https://www.arc-rider.com/docs/faq/wie-man-builds-einbaut-und-updatet # Ninox: Wie baue ich Builds ein und wie laufen Updates? ## Was ist ein Build? Ein **Build** ist das fertige Code-Paket für dein Ninox-Team. Nicht jedes Widget einzeln — Universe macht aus den Widgets deines Projekts **einen** Textblock. So hängt das zusammen: 1. **Projekt** — dein Arbeitsbereich in Universe. Dort liegen Lizenz und Widgets. 2. **Build** — der daraus erzeugte Code. Den kopierst du einmal in die **globalen Funktionen** von Ninox. 3. **Anwendungscode** — die kurze Formel im Formelfeld, mit der du ein einzelnes Widget auf einer Seite anzeigst. Ohne den Build im Global Code kann Ninox die Widgets nicht laden. Den Anwendungscode brauchst du trotzdem pro Stelle, an der ein Widget erscheinen soll. Universe hält den **Standard-Build** automatisch aktuell: Neue Widgets und neue Widget-Versionen kommen von selbst hinein, auch größere Versionssprünge. Widgets, die du bewusst auf eine Version festgelegt hast, bleiben dabei unverändert. Eigene benannte Builds nutzt du im Expertenmodus, wenn du einen Stand festhalten willst — zum Beispiel vor einem Go-Live. --- ## Schritt 1: Code in Universe kopieren 1. Gehe in dein Universe-Projekt. 2. Hinterlege unter **„URL freischalten“** zuerst deine Ninox-URL oder Team-ID. Erst dann steckt die Lizenz im Code. 3. Klicke unter **„In Ninox einfügen“** bei **„Globalen Code kopieren“** auf **Kopieren**. Du musst den Code dafür nicht ansehen. Eigene Builds, Widget-Versionen und die Code-Vorschau findest du über den Schalter **Expertenmodus** oben neben „Ninox“. ## Schritt 2: In Ninox einsetzen (Erst-Setup) 1. Wechsel in deine Ninox-Datenbank im **Admin-Modus**. 2. Klicke auf die **drei Punkte** neben dem Datenbanknamen oben links. 3. Gehe auf **„Optionen“ > „Globale Funktionen“**. 4. Füge den kopierten Code am Ende des Textfeldes ein und speichere. Zusätzlich einmalig als **Trigger nach Öffnen** (Datenbankoptionen, ganz oben): ```ninox dialog("ArcRider initialization", html(arcRiderWidgetManager()), ["close"]) ``` Damit startet die Widget-Engine, sobald die Datenbank geöffnet wird. In deinem Universe-Projekt kopierst du dieses Script mit **„Start-Code kopieren“**. --- ## Wie funktionieren Updates? Nach dem ersten Einfügen hakst du in deinem Universe-Projekt **„Ja, Code ist eingefügt“** ab. Ab dann weiß Universe, welcher Stand in deinem Ninox läuft. Wenn wir Fehler beheben, neue Widget-Versionen erscheinen oder sich deine URLs ändern, erzeugt Universe einen neuen Code. In deinem Projekt erscheint dann das Banner **„Neues Update verfügbar“** mit einer Liste, welche Widgets sich geändert haben. Größere Versionssprünge sind dort als **Hauptversion** markiert; prüfe danach kurz deine Widgets. So aktualisierst du Ninox sicher: 1. Klicke im Banner auf **„Neuen Code kopieren“**. 2. Öffne die **globalen Funktionen** in Ninox. 3. Suche den alten Arc-Rider-Block. Er beginnt mit einem klaren Header (ASCII-Wortmarke und `Build ID`). 4. Ersetze diesen Block vollständig durch den neuen Code. Achte darauf, dass deine eigenen globalen Funktionen oberhalb oder unterhalb des Arc-Rider-Blocks unberührt bleiben. Zurück in Universe klickst du auf **„Ja, ich habe die neueste Version eingefügt“**. Dann verschwindet das Banner bis zum nächsten Update. Den Start-Code musst du bei Updates nicht neu einfügen. --- # React: Fehlerbehebung Source: https://www.arc-rider.com/docs/faq/react-troubleshooting # React: Fehlerbehebung ## Das Widget bleibt leer Prüfe zuerst, ob du den exportierten Komponentennamen verwendest und ob alle Optionen in `data` liegen. Die [Komponentenübersicht](/docs/react/intro/react-components) zeigt die verfügbaren Exporte. Eine Höhe von `100%` funktioniert nur in einem Elterncontainer mit berechenbarer Höhe. Beginne zum Eingrenzen mit `height: "320px"`. Bei Table braucht jede Spalte eine Breite, mindestens eine `fraction`. ## Ein Klick löst keinen Callback aus Aktiviere die Klickbarkeit an der richtigen Stelle: etwa an der Table-Zeile, einer Kanban-Karte oder einem Step. Prüfe die Callback-Signatur in der Widget-Referenz. Eine ID ist keine Callback-Position; manche Events liefern Indizes, andere Werte. ## Nach dem Speichern springt die Anzeige zurück Übernimm das bestätigte Ergebnis in React-State und leite ein neues `data`-Objekt daraus ab. Das Widget speichert nicht selbst in deiner Datenbank. Beim Kanban muss `onItemDrop` das Speicher-Promise zurückgeben. ## TypeScript erkennt einen Callback oder JSX-Slot nicht Diese Referenzen gehören zu `0.1.0-alpha.5`. Vergleiche deine installierte Version und vermeide gemischte Kopien des Pakets. Die Typen befinden sich im Paket; du benötigst kein zusätzliches Widget-Typenpaket. ## Server Rendering Die Widget-Laufzeit verwendet Browser-APIs. Importiere sie in einer Anwendung mit Server Rendering erst im Browser. Eine reine Client-Markierung reicht nicht in jedem Framework, wenn Module schon auf dem Server ausgewertet werden. Dieses vollständige Beispiel verschiebt den Laufzeit-Import in einen Effekt; der Typimport wird beim Build entfernt: ```tsx import { useEffect, useState } from 'react'; import type { ComponentType } from 'react'; import type { ArcWidgetButtonProps } from '@arcrider/arcwidgets-react'; export default function Example() { const [Button, setButton] = useState
Widget konnte nicht geladen werden.
; return Button ? ( ) : (Widget lädt …
); } ``` Nutze alternativ den Mechanismus deines Frameworks, der Server Rendering für diese Komponente ausdrücklich deaktiviert. Ein stabiler Platzhalter vermeidet Layoutsprünge. ## React Strict Mode Entwicklung kann Komponenten mehrfach auf- und abbauen. Löse Speichervorgänge in Interaktions-Callbacks aus, nicht beim Rendern. Vergib stabile eigene IDs nur dort, wo du sie benötigst; dieselbe ID darf nicht gleichzeitig mehrfach im Dokument vorkommen. ## Ein Lizenzhinweis bleibt sichtbar Ein Eintrag der Domain im Portal allein reicht nicht. Kopiere den Lizenz-Schnipsel aus dem Projekt in die App. Siehe [Paket und Lizenzierung](/docs/react/faq/react-paket-und-lizenzierung). --- # React: Wie funktionieren Paket und Lizenzierung? Source: https://www.arc-rider.com/docs/faq/react-paket-und-lizenzierung # React: Paket und Lizenzierung `@arcrider/arcwidgets-react` enthält die Komponenten, Styles, Typen und vollständige Beispiele. Die Laufzeit wird in deine Anwendung eingebunden. Diese Dokumentation beschreibt `0.1.0-alpha.5`. ## Lokale Entwicklung Installiere das Paket wie unter [React einrichten](/docs/react/intro/setup-react) beschrieben. Auf `localhost` und `127.0.0.1` erscheint kein Lizenzhinweis. Mini-Widgets benötigen auch für den produktiven Einsatz keine Premium-Freischaltung im Rahmen der Nutzungsbedingungen. ## Premium-Widgets auf einer Live-Domain Zwei Schritte: Ultimate für das Projekt, und die Produktions-Domain im Projekt hinterlegen. Darunter steht ein Lizenz-Schnipsel. Den setzt du einmal in die App, bevor die Widgets starten — als erste Zeile in `main.tsx` oder als Script in `index.html`. ```js window.ArcWidgetsLicenseKeys = ["…"]; ``` Die gespeicherte Domain allein schaltet die App nicht frei. Der Schnipsel ist die Freischaltung. `localhost` braucht ihn nicht. Änderst du die Domain, kopierst du den neuen Schnipsel einmal neu. Den Widget-Code tauschst du dafür nicht aus. Eine Domain deckt sich selbst und alle Subdomains ab (`firma.de` gilt auch für `app.firma.de`). Eine Adresse auf einem fremden Host, zum Beispiel `name.netlify.app`, ist eine eigene Domain. Die ersetzt du später durch die echte Domain und tauschst den Schnipsel. ## Lizenzhinweis Auf einer Live-Domain ohne diesen Schnipsel kann bei Premium-Widgets ein Hinweis in der Oberfläche und der Konsole erscheinen. Die Bedienelemente bleiben funktionsfähig. Jede hinterlegte Domain steht im Schnipsel. `localhost` bleibt frei. [Komponentenübersicht](/docs/react/intro/react-components) · [Preise und Projektmodell](/docs/react/faq/wie-sich-preise-zusammensetzen). --- # Was bedeutet der Lizenzhinweis (Watermark) in der Produktion? Source: https://www.arc-rider.com/docs/faq/lizenzhinweis-watermark-produktion # Lizenzhinweis in React Premium-Widgets können auf einer nicht aktivierten Live-Domain einen Hinweis in Oberfläche und Konsole anzeigen. Mini-Widgets und lokale Entwicklung auf `localhost` oder `127.0.0.1` zeigen ihn nicht. ## Bedienbarkeit Der Hinweis sperrt keine Widget-Funktionen. Deine Anwendung bleibt für Datenzugriff und Speicherung zuständig. ## Aktivierung prüfen Das npm-Paket übernimmt die Domain nicht von allein. Unter der Produktions-Domain im Projekt steht der Lizenz-Schnipsel. Den setzt du einmal in die App, bevor die Widgets starten. Ohne diesen Schnipsel bleibt der Hinweis. [React-Paket und Lizenzierung](/docs/react/faq/react-paket-und-lizenzierung) beschreibt, wo der Schnipsel hingehört. # Was bedeutet der Lizenzhinweis (Watermark) in der Produktion? Wenn du Widgets in ein Projekt legst, sind die Funktionen sofort da. Du kannst Premium-Widgets (Table, Kanban, Calendar, …) vollständig testen, bevor du Ultimate kaufst. Ohne Ultimate erscheint in einer **Produktionsumgebung** ein Hinweis — unten in der Oberfläche und einmal in der Browser-Konsole. Die Widgets bleiben bedienbar.  ## Wann erscheint der Hinweis? - Du nutzt **Premium-Widgets** und das Projekt hat kein **Ultimate-Paket**. - Die App läuft auf einer **Produktions-Umgebung**, die nicht im Universe-Projekt hinterlegt ist. Bei Ninox zählt die Private-Cloud-URL, die On-Premise-URL oder die Public-Cloud-**Team-ID**. Mini-Widgets zeigen den Hinweis nicht. Auf `localhost` und in Test-Stages erscheint er ebenfalls nicht. ## Was wird blockiert? **Nichts.** Der Hinweis ist eine Lizenzanzeige: - Widgets frieren nicht ein. - Daten werden weiter geladen und gespeichert. - Bedienung und Performance bleiben erhalten. In der Konsole steht dieselbe Information hinter dem gelben Badge **Arc Widgets**. Das hilft, wenn jemand die Seite prüft und den Toast schon weggeklickt hat. ## Wie entferne ich den Hinweis? Sobald Ultimate für das Projekt aktiv ist, verschwindet er: Im Universe-Projekt den Build neu holen und den Global Code in Ninox ersetzen. Der neue Build enthält die Freischaltung. Details: [Ninox Builds](/docs/faq/wie-man-builds-einbaut-und-updatet). Preise: [Wie berechnen sich die Preise?](/docs/faq/wie-sich-preise-zusammensetzen). # Was bedeutet der Lizenzhinweis (Watermark) in der Produktion? Wenn du Widgets in ein Projekt legst, sind die Funktionen sofort da. Du kannst Premium-Widgets (Table, Kanban, Calendar, …) vollständig testen, bevor du Ultimate kaufst. Ohne Ultimate erscheint in einer **Produktionsumgebung** ein Hinweis — unten in der Oberfläche und einmal in der Browser-Konsole. Die Widgets bleiben bedienbar. Der Hinweis ist ein Toast unten in der App und dieselbe Meldung einmal in der Browser-Konsole hinter dem gelben Badge **Arc Widgets**. ## Wann erscheint der Hinweis? - Du nutzt **Premium-Widgets** und das Projekt hat kein **Ultimate-Paket**. - Die App läuft auf einer **Produktions-Umgebung**, die nicht im Universe-Projekt hinterlegt ist. Bei Vue zählt die hinterlegte Live-Domain (inkl. Subdomains). Mini-Widgets zeigen den Hinweis nicht. Auf `localhost` und in Test-Stages erscheint er ebenfalls nicht. ## Was wird blockiert? **Nichts.** Der Hinweis ist eine Lizenzanzeige: - Widgets frieren nicht ein. - Daten werden weiter geladen und gespeichert. - Bedienung und Performance bleiben erhalten. In der Konsole steht dieselbe Information hinter dem gelben Badge **Arc Widgets**. Das hilft, wenn jemand die Seite prüft und den Toast schon weggeklickt hat. ## Wie entferne ich den Hinweis? Sobald Ultimate für das Projekt aktiv ist, verschwindet er: Produktions-Domain im Universe-Projekt hinterlegen. Preise: [Wie berechnen sich die Preise?](/docs/faq/wie-sich-preise-zusammensetzen). --- # Was ist ein Widget-Partner und welche Vorteile habe ich davon? Source: https://www.arc-rider.com/docs/faq/was-ist-ein-widget-partner-und-welche-vorteile-habe-ich-davon # Was ist ein Widget-Partner und welche Vorteile habe ich davon? Partner sind Agenturen und Integratoren, die Arc Rider Widgets **in Kundenlösungen** einsetzen. Arc Rider stuft Organisationen als Partner ein. Für den Einsatz beim Endkunden gilt: - **Ein Universe-Projekt je Endkunde.** Jeder Lizenzkontext hat ein eigenes Package. Den Preis gegenüber deinem Kunden setzt du selbst. - Du trittst **im eigenen Namen** auf. Vertragspartner deiner Mandanten bist du, nicht Arc Rider. - Builds, Umgebungen und Rechnungen bleiben pro Kundenprojekt getrennt. - Lizenz, Produktions-Domains und Rechnungen bleiben pro Kundenprojekt getrennt. - Lizenz, Produktions-Domains und Rechnungen bleiben pro Kundenprojekt getrennt. Konditionen (Rabattcode, Angebot, Freischaltung) vereinbaren wir individuell. Zusätzlich gibt es das **Ambassador-Programm**: öffentlich geteilte Arc-Rider-Erfahrung bringt Credits, die du gegen Gutscheine für neue Projekte einlösen kannst. Überblick auf der [Preisseite](/pricing). Partner-Einstufung: [support@arc-rider.com](mailto:support@arc-rider.com). --- # Wie berechnen sich die Preise für Widgets? Source: https://www.arc-rider.com/docs/faq/wie-sich-preise-zusammensetzen # Wie berechnen sich die Preise für Widgets? Zwei Stufen, beide **pro Universe-Projekt** und für genau eine Plattform (Ninox oder React). | Paket | Preis (netto) | Enthalten | |---|---|---| | Free | 0 € | Alle Mini-Widgets, produktiv nutzbar | | Ultimate | 750 €, einmalig | Alle Premium-Widgets und Mini-Widgets, inklusive Widgets, die nach dem Kauf dazukommen | Ultimate ist ein Einmalkauf. Du erhältst **Lifetime-Updates**: neue Versionen, Fixes und künftige Premium-Widgets ohne weitere Zahlung. Der erzeugte Build-Code bleibt in deinem Ninox-Projekt nutzbar. Das npm-Paket und die Lifetime-Updates bleiben in deiner React-App nutzbar. Das npm-Paket und die Lifetime-Updates bleiben in deiner Vue-App nutzbar. Im Paket steckt **eine Produktions-Umgebung**. Entwicklung (`localhost`) und nicht-produktive Stages sind frei. Weitere Produktions-URLs oder Team-IDs buchst du als Add-on oder lässt sie von uns freischalten. Weitere Produktions-Domains buchst du als Add-on oder lässt sie von uns freischalten. Weitere Produktions-Domains buchst du als Add-on oder lässt sie von uns freischalten. Du kannst den gesamten Katalog vorher nutzen. Ohne Ultimate erscheint in der Produktion bei Premium-Widgets ein Lizenzhinweis — die Oberfläche bleibt bedienbar. Ninox und React sind zwei Projekte. Wer beide Plattformen produktiv ohne Hinweis nutzen will, kauft Ultimate zweimal. Aktueller Preis und Checkout: [Preisseite](/pricing). Wie Projekt und Umgebung zusammenhängen: [Buchung und Teamstruktur](/docs/faq/wie-funktioniert-die-buchung-pro-team-und-was-muss-ich-zur-teamstruktur-wissen). --- # Wie funktioniert die Buchung pro Team und was muss ich zur Teamstruktur wissen? Source: https://www.arc-rider.com/docs/faq/wie-funktioniert-die-buchung-pro-team-und-was-muss-ich-zur-teamstruktur-wissen # Wie funktioniert die Buchung pro Team und was muss ich zur Teamstruktur wissen? Du buchst **pro Universe-Projekt**. Ein Projekt gehört einer Plattform. Darin liegen Builds, Lizenz und die Produktions-Umgebungen. Darin liegen Lizenz und die Produktions-Domains. Darin liegen Lizenz und die Produktions-Domains. Ultimate enthält **eine Produktions-Umgebung**. Entwicklung und Staging sind frei. Weitere Produktions-Umgebungen sind ein Add-on. ## Ninox: was als Produktions-Umgebung zählt - **Private Cloud:** die genaue URL, zum Beispiel `https://firma.ninoxdb.de`. - **On-Premise:** eure Installations-URL, inklusive Subdomains. - **Public Cloud:** die **Team-ID**. Sie steht in der Ninox-URL nach `/teams/` oder kommt aus der Formel `teamId()`. Ohne hinterlegte Team-ID erscheint der Lizenzhinweis. Innerhalb einer hinterlegten Umgebung darfst du die Widgets in allen Datenbanken dieses Teams nutzen. ### Eigenes Team Ein Universe-Projekt, Ultimate einmal, Team-ID oder Private-Cloud-URL hinterlegen. ### Kundenprojekte Pro Endkunde ein eigenes Universe-Projekt mit eigenem Package. So bleiben Lizenz und Build getrennt. Siehe [Widget-Partner](/docs/faq/was-ist-ein-widget-partner-und-welche-vorteile-habe-ich-davon). ### Dieselbe Lösung für mehrere Mandanten Jeder produktive Lizenzkontext braucht ein eigenes Projekt — oder zusätzliche Produktions-URLs bzw. Team-IDs im selben Projekt. ## React Eine hinterlegte Domain gilt für die Domain selbst und alle Subdomains (`kunde.de` schließt `app.kunde.de` ein). Eine Produktions-Domain steckt im Ultimate-Paket. Für Kundenprojekte gilt: pro Endkunde ein eigenes Universe-Projekt. Siehe [Widget-Partner](/docs/faq/was-ist-ein-widget-partner-und-welche-vorteile-habe-ich-davon). ## Vue Eine hinterlegte Domain gilt für die Domain selbst und alle Subdomains (`kunde.de` schließt `app.kunde.de` ein). Eine Produktions-Domain steckt im Ultimate-Paket. Für Kundenprojekte gilt: pro Endkunde ein eigenes Universe-Projekt. Siehe [Widget-Partner](/docs/faq/was-ist-ein-widget-partner-und-welche-vorteile-habe-ich-davon). Preise: [Wie berechnen sich die Preise?](/docs/faq/wie-sich-preise-zusammensetzen). Fragen zur Struktur: [support@arc-rider.com](mailto:support@arc-rider.com). --- # Wie verwalte ich mehrere Umgebungen oder URLs? Source: https://www.arc-rider.com/docs/faq/mehrere-umgebungen-und-urls # Mehrere React-Umgebungen Das Ultimate-Modell gilt pro Universe-Projekt mit einer Produktions-Umgebung; weitere produktive Umgebungen können als Add-on zugeordnet werden. Für React wird die Live-Domain im Projekt hinterlegt. ## Domain und technische Freischaltung Unter der Produktions-Domain steht der Lizenz-Schnipsel. Den setzt du einmal in die App. Eine Domain deckt sich selbst und alle Subdomains ab. Eine Adresse wie `name.netlify.app` ist eine eigene Domain und steckt nicht in der späteren Firmen-Domain. ## Lokale Entwicklung Lokale Entwicklung auf `localhost` oder `127.0.0.1` zeigt keinen Hinweis und braucht keinen Schnipsel. Jede hinterlegte Domain steht im Schnipsel. [React-Paket und Lizenzierung](/docs/react/faq/react-paket-und-lizenzierung) · [Vertragsbedingungen](/terms). # Wie verwalte ich mehrere Umgebungen oder URLs? Jedes Ultimate-Paket gilt pro Universe-Projekt und schaltet standardmäßig **eine Produktions-Umgebung** frei. ## Welche Umgebungen sind immer kostenlos? Entwicklungs- und Testumgebungen verbrauchen **kein** Kontingent: - Lokale Adressen wie `localhost`, `127.0.0.1` oder `::1`. - Nicht-produktive Staging- und Test-Umgebungen (in Universe als "dev" gekennzeichnet). ## Was zählt als eine Produktions-Umgebung? Die Lizenz-Einheit hängt vom Ninox-Hosting ab: - **Private Cloud:** die exakte Subdomain (z. B. `firma.ninoxdb.de`). - **On-Premise:** die Installations-URL des Servers (inklusive aller Subdomains). - **Public Cloud:** die **Team-ID** deines Ninox-Teams. Innerhalb dieses Teams können Widgets in beliebig vielen Datenbanken genutzt werden. ## Wie füge ich weitere Produktions-Umgebungen hinzu? Wenn die Anwendung auf mehreren getrennten Live-Systemen laufen soll: 1. **Add-ons:** zusätzliche Produktions-Umgebungen im Universe-Portal gegen eine Einmalzahlung. 2. **Freischaltung (Kulanz):** wir können zusätzliche Umgebungen im Administrationsbereich kostenlos freischalten, zum Beispiel für interne Testsysteme oder Partner-Szenarien. Mehr Details zum Lizenzmodell findest du in unseren [AGB](/terms) unter Teil III. # Wie verwalte ich mehrere Umgebungen oder URLs? Jedes Ultimate-Paket gilt pro Universe-Projekt und schaltet standardmäßig **eine Produktions-Umgebung** frei. ## Welche Umgebungen sind immer kostenlos? Entwicklungs- und Testumgebungen verbrauchen **kein** Kontingent: - Lokale Adressen wie `localhost`, `127.0.0.1` oder `::1`. - Nicht-produktive Staging- und Test-Umgebungen (in Universe als "dev" gekennzeichnet). ## Was zählt als eine Produktions-Umgebung? **Vue:** Eine hinterlegte Live-Domain. Sie deckt die Hauptdomain und **sämtliche Subdomains** ab (`firma.de` lizenziert auch `app.firma.de`). ## Wie füge ich weitere Produktions-Umgebungen hinzu? Wenn die Anwendung auf mehreren getrennten Live-Systemen laufen soll: 1. **Add-ons:** zusätzliche Produktions-Umgebungen im Universe-Portal gegen eine Einmalzahlung. 2. **Freischaltung (Kulanz):** wir können zusätzliche Umgebungen im Administrationsbereich kostenlos freischalten, zum Beispiel für interne Testsysteme oder Partner-Szenarien. Mehr Details zum Lizenzmodell findest du in unseren [AGB](/terms) unter Teil III. --- # Action Polling Source: https://www.arc-rider.com/docs/patterns/action-polling # Action Polling (`polling` auf `update`) ## AI Defaults (read first) - **`polling` nur auf `type: "update"`** — wiederholt dasselbe Feld-Update in Intervallen (typisch: Trigger-Feld). - **API-Calls gehören in den Ninox-Trigger** („Nach Änderung“), nicht ins Widget. Kein CORS-Thema. - **Stopp:** Formel-Boolean `active` (Live über Widget-Reload) **und** `maxAttempts` als Sicherheitsnetz. - **Loading** (Button/Layout) bleibt an, solange `perform` läuft — inkl. Poll-Dauer. Status-Texte: `loading.overlay.sequences` + `when:`. - **Trigger-Feld zurücksetzen** am Ende des Triggers (`false` / `null`), sonst feuert „Nach Änderung“ bei erneutem `true` oft nicht. --- ## Problem Nach einem Klick soll Ninox eine API prüfen oder ein Skript erneut ausführen, **solange** ein Job noch läuft — ohne dass der Browser die API direkt aufruft. ## Lösung `update` mit `polling`: das Widget schreibt das Trigger-Feld wiederholt; der Trigger macht `http(...)` / Logik und schreibt den Status (z. B. in ein Data-Bag-Feld). Die Formel setzt `active` aus diesem Status. ```javascript let state := parseJSON(text(helper_uiState)); let pollActive := state.jobStatus = "running"; actions: [{ type: "update", recordId: Nr, fieldId: fieldId(Nr, "trigger_checkApi"), value: true, polling: { intervalMs: 2000, maxAttempts: 30, active: pollActive } }] ``` | Feld | Default | Bedeutung | |------|---------|-----------| | `intervalMs` | `2000` | Pause zwischen Updates (ms) | | `maxAttempts` | `30` | Max. Updates inkl. dem ersten | | `active` | `true` | Poll weiter, solange `true`; Formel-Boolean (z. B. aus Data Bag) | **Ablauf:** erstes Update → warten → wenn Live-`active` noch `true` und Attempts nicht erreicht → erneut Update → … → Stopp → Loading aus. ## Live-`active` (Button / Layout) Bei Formel-Reload nach dem Trigger rufen Button und Layout `syncPollingGates` auf. So sieht die laufende Poll-Schleife den neuen Boolean — nicht nur den Snapshot vom Klick. Ohne Sync (andere Widgets): Snapshot + `maxAttempts` gelten weiterhin. ## Zusammenspiel mit Loading / Sequences ```javascript loading: { show: true, overlay: { title: "Bitte warten…", sequences: [ { after: 0, title: "Starte…", priority: 5 }, { when: pollActive, title: "API prüft…", priority: 40 } ] } } ``` `when` und `polling.active` können denselben Boolean nutzen. ## Trigger-Muster (Ninox) 1. Ja/Nein-Feld z. B. `trigger_checkApi` 2. Trigger „Nach Änderung“: wenn `true` → HTTP / Logik → Status ins Bag schreiben → `trigger_checkApi := false` 3. Widget pollt mit `value: true` und `active` aus dem Status ## Lokaler Smoke-Test (Interactive) 1. `npm start` im Widgets-Repo 2. Sidebar → **Button** → Variante **★ Polling Test (Console)** (oder P1–P3) 3. DevTools-Console filtern: `[arc-polling]` 4. Erwartung: - **Poll until active=false** — 3× `database.update`, dann Log `active=false`, Poll stoppt - **maxAttempts=3** — genau 3 Updates - **Single update** — genau 1 Update ## Grenzen - Läuft nur solange der Tab / die Ansicht offen ist (kein echter Cron). - v1: nur `type: "update"`; Live-Gate-Sync: arcCustomButton + arcCustomLayout. --- # Data Bag Actions Source: https://www.arc-rider.com/docs/patterns/data-bag-actions # Data Bag Actions (partielle Updates in JSON-Töpfen) ## AI Defaults (read first) - **`createNew.dataBag`** (Calendar Week) = nach erfolgreichem Anlegen, inkl. `##newRecordId##`. Siehe [`custom-calendar-week`](../widgets/custom-calendar-week.md). - **Gilt für alle Widgets mit `actions`**, die den Ninox Action Performer nutzen (Button, Layout, Table, KPI Bar, Calendar, Drawing, …). - **Syntax:** `dataBag: { encoding, base, patch }` — `patch` folgt JSON Merge Patch (RFC 7386): Key setzen/überschreiben; `null` / `''` löscht den Key. - **`encoding`:** `"raw"` (Default) — Plain-JSON im Textfeld via `JSON.stringify` / `parseJSON`. **`encoding` weglassen**, wenn nicht Legacy-Daten. - **`base`:** aktueller Feldwert (Rohstring aus dem Textfeld oder bereits geparstes Objekt). - **Lesen in Formeln:** `parseJSON(text(helper_uiState))` bzw. `parseJSON(helper_data)` — **ohne** `urlDecode`. - **Alternative:** plain `value` auf der Action, wenn das Zielfeld nur einen einzelnen Wert hält. - **Legacy:** `"urlEncoded"` nur bei bestehenden url-kodierten Feldern (`parseJSON(urlDecode(...))`). Siehe Abschnitt unten. - **Nicht verwechseln:** Widget-Prop `dataBag: { key, encoding }` in **arcCustomDrawing** — nur für Shape-Geometrie in verschachtelten JSON-Töpfen. Siehe [`custom-drawing`](../widgets/custom-drawing.md#-databag). --- ## Problem Viele UI-Zustände (Tab, Filter, ausgewählte ID) liegen in **einem** JSON-Textfeld (State-Bag). Beim Klick soll nur **ein Key** geändert werden — ohne den Rest zu löschen. Ohne `dataBag` musst du in Ninox den Topf manuell lesen, `setItem` aufrufen und wieder serialisieren (siehe [State-Bag in einem Textfeld](/docs/templates/tips_statefield)). Mit **`dataBag` auf der Action** übernimmt der Action Performer Read → Patch → Write. ## Action-Syntax ### Partiell patchen (empfohlen für State-Bags) ```javascript { type: "update", recordId: Nr, fieldId: fieldId(Nr, "helper_mainContentState"), dataBag: { base: helper_mainContentState, patch: { selectedShape: "123", activeTab: "details" } } } ``` - **`base`:** Rohwert aus dem Feld (String oder bereits geparstes Objekt — der Helfer normalisiert). - **`patch`:** nur die Keys, die sich ändern sollen. - Key löschen: `patch: { statusFilter: null }` oder `""`. - **`encoding`:** optional; Default `"raw"` — Plain-JSON im Textfeld. ### Einfacher Wert (dediziertes Feld) ```javascript { type: "update", recordId: Nr, fieldId: fieldId(Nr, "helper_selectedId"), value: "123" } ``` ## Wo funktioniert das? | Kontext | `dataBag` auf Action | Widget-`dataBag` (`key`) | |--------|----------------------|---------------------------| | arcCustomButton, Table, KPI Bar, Calendar, Input, … | ja (`update` + `create`) | — | | arcCustomCalendarWeek — `createNew` | ja (`create`, inkl. `##newRecordId##`) | — | | arcCustomLayout — Block-`actions` (Klick) | ja (`update` + `create`) | — | | arcCustomLayout — Widget-`actions` (Klick auf gesamtes Layout) | ja (`update` + `create`) | — | | arcCustomDrawing — Shape-`actions` (Klick) | ja (`update`) | — | | arcCustomDrawing — Geometrie speichern | — | ja (`key` + optional `encoding`) | Alle Widgets teilen sich dieselbe Action-Engine für `update` und `create` (inklusive `dataBag`). > **Hinweis für arcCustomDrawing Create:** Das Drawing-Widget legt neue Shapes über einen eigenen Create-Pfad an, nicht über die üblichen `actions`. Dort gilt weiterhin `setNewRecordId`. `dataBag` auf `type: "create"` gilt nur für explizit definierte Create-Actions in anderen Widgets (z. B. Button, Layout). **React / Vue / WeWeb:** Actions werden an den Host durchgereicht (`onAction` / `emit('action')`). `dataBag`-Patching ist dort nicht automatisch — der Host muss den Patch selbst anwenden oder den Klartext-`value` nutzen. ## `dataBag` auf `create`-Actions Neu: `dataBag` funktioniert auch auf `type: "create"`. Nach dem Anlegen des Datensatzes wird der Platzhalter `##newRecordId##` durch die numerische ID des neuen Records ersetzt — dann wird der Bag gepatch und ins Zielfeld geschrieben. ### Platzhalter `##newRecordId##` Ersetzt in beliebiger Tiefe (Strings, Objekte, Arrays) die neue numerische Record-ID nach dem Create. ```javascript { type: "create", tableId: tableId("Aufgaben"), changeFieldValues: [{ fieldId: fieldId("Aufgaben", "Projekt"), value: Nr }], dataBag: { recordId: Nr, fieldId: fieldId(Nr, "helper_uiState"), base: helper_uiState, patch: { selectedTaskId: "##newRecordId##" } } } ``` **Ablauf:** Create → neue ID → `##newRecordId##` auflösen → `readBag(base)` → `applyPatch(bag, patch)` → `writeBag` → `database.update` ### Mehrere Bags nach Create (Array) ```javascript dataBag: [ { recordId: Nr, fieldId: fieldId(Nr, "helper_uiState"), base: helper_uiState, patch: { selectedTaskId: "##newRecordId##", activeTab: "details" } }, { recordId: parentNr, fieldId: fieldId(parentNr, "helper_dashState"), base: helper_dashState, patch: { lastCreatedId: "##newRecordId##" } } ] ``` ### Kombiniert mit `setNewRecordId` Beide Mechanismen können zusammen genutzt werden: | | `setNewRecordId` | `dataBag` auf Create | |---|---|---| | **Zielfeld** | Ninox-Referenzfeld (Verknüpfung) | JSON-Textfeld (State-Bag) | | **Schreibt** | numerische ID direkt | ID als Patch-Key in JSON | | **Wann** | Parent-Datensatz verlinken | UI-State nach Create setzen | ```javascript { type: "create", tableId: tableId("Aufgaben"), changeFieldValues: [...], setNewRecordId: [{ recordId: Nr, fieldId: fieldId(Nr, "LetzteAufgabe") }], dataBag: { recordId: Nr, fieldId: fieldId(Nr, "helper_uiState"), base: helper_uiState, patch: { selectedTaskId: "##newRecordId##" } } } ``` ### Kalender `createNew` (nach erfolgreichem Anlegen) `createNew.dataBag` hängt direkt am Kalender-Create, nicht an einer Button-Action. Nach dem Anlegen (Aufziehen, Ganztags oder Ghost-Klick) läuft derselbe Create-Pfad. Typisch: Auswahl leeren, damit der Placement-Ghost verschwindet. ```javascript createNew: { tableId: tableId("Termine"), dateFrom: fieldId(first(Termine), "Datum von"), timeFrom: fieldId(first(Termine), "Von"), dateTo: fieldId(first(Termine), "Datum bis"), timeTo: fieldId(first(Termine), "Bis"), dataBag: { recordId: Nr, fieldId: fieldId(Nr, "helper_uiState"), base: helper_uiState, patch: { auswahlId: null, lastTerminId: "##newRecordId##" } } } ``` ## Beispiele ### Tab wechseln (Button) ```javascript arcCustomButton({ uniqueId: "tab-details-" + Nr, title: "Details", actions: [{ type: "update", recordId: Nr, fieldId: fieldId(Nr, "helper_uiState"), dataBag: { base: helper_uiState, patch: { activeTab: "details" } } }] }) ``` ### Mobile Dimmer schließen (Layout-Block) ```javascript blocks: [{ width: "100%", height: "100%", styles: "position: absolute; z-index: 100;", value: "", actions: [{ type: "update", recordId: Nr, fieldId: fieldId(Nr, "helper_mainContentState"), dataBag: { base: helper_mainContentState, patch: { sidebarOpen: "", selectedShape: "" } } }] }] ``` ### Shape auswählen → Sidebar (Drawing) ```javascript actions: [{ type: "update", recordId: stateRecordId, fieldId: stateField, dataBag: { base: helper_mainContentState, patch: { selectedShape: Nr } } }] ``` Ausführlich: [`custom-drawing`](../widgets/custom-drawing.md) (Shape-`actions`, Klick vs. Drag). ## Legacy: `urlEncoded` Früher wurde JSON oft mit `urlEncode(formatJSON(...))` geschrieben, weil Ninox-Actions mit `{}` in `value` problematisch waren. Mit **dataBag** (Patch im Bundle) ist das in **neuen Projekten nicht mehr nötig**. | | `raw` (Default) | `urlEncoded` (Legacy) | |---|---|---| | Im Feld | `{"activeTab":"details"}` | `%7B%22activeTab%22%3A...` | | Formel lesen | `parseJSON(text(feld))` | `parseJSON(urlDecode(text(feld)))` | | Action schreiben | `dataBag: { base, patch }` | zusätzlich `encoding: "urlEncoded"` | | Wann | neue Demos, Seeds, Trigger | bestehende Kundendaten | ## Siehe auch - [State-Bag in einem Textfeld](/docs/templates/tips_statefield) — State-Bag manuell mit Ninox `setItem` / `item` - [`custom-drawing`](../widgets/custom-drawing.md) — Widget-`dataBag` für Pin-Geometrie + Shape-Actions --- # Datum und Zeit in React Source: https://www.arc-rider.com/docs/patterns/react-dates # Datum und Zeit in React Die Kalender verwenden getrennte Werte für Kalendertage und Uhrzeiten. Das verhindert, dass eine Anzeige versehentlich einen Unix-Zeitstempel als Uhrzeit interpretiert. ## Kalendertag `dateFrom`, `dateTo` und ein datumsbasierter `startDate` erhalten Millisekunden seit dem Unix-Epoch. Die Beispiele erzeugen den Tageswert mit `new Date(2026, 8, 17).getTime()`: September hat im JavaScript-Konstruktor den Index `8`. Für Calendar Grid ist `dateSettings.month` dagegen einsbasiert: September ist `9`. ## Uhrzeit und Dauer `timeFrom` und `timeTo` sind Millisekunden seit Tagesbeginn: `9 * 60 * 60 * 1000` steht für 09:00 Uhr. Eine Stunde Dauer ist `60 * 60 * 1000`. Calendar Week verwendet für den sichtbaren Bereich `timeSettings.timeStart` und `timeSettings.duration` in derselben Einheit. `dateSettings.duration` zählt dagegen Tage. Die historischen Felder `timeSettings.from` und `timeSettings.to` werden nicht ausgewertet. ## Zeitzonen Lege in deiner Anwendung fest, ob deine Daten lokale Kalendertage oder globale Zeitpunkte darstellen. Konvertiere Backend-Zeitpunkte vor der Übergabe in die gewünschte Anzeigezone. Die Beispiele verwenden lokal erzeugte Tageswerte und `timeZoneBalance: 0`. Eine feste Stundenkorrektur bildet keine vollständige Zeitzone mit Sommerzeitwechseln ab. Teste besonders Termine um Mitternacht und an Zeitumstellungen. Lokale Datumsstrings wie `17.09.2026` eignen sich zur Anzeige, nicht als Datenaustauschformat. ## Bearbeiten und speichern Für verlässliche Speicherung benutzt das [Kalender-Beispiel](/docs/react/examples/individuelles-design-in-der-calendarweek) ein Formular und neue State-Werte. Die vorhandenen Drag-Callbacks melden den ursprünglichen Stand, aber nicht alle neuen Zielzeiten. Deshalb bleiben die dokumentierten Kalenderbeispiele ohne Drag & Drop. --- # Logbook to Billing Source: https://www.arc-rider.com/docs/patterns/logbook-to-billing # Logbook to Billing (Zeiterfassung → Aufwandsberechnung) ## AI Defaults (read first) - **Zwei getrennte Konzepte:** `TimeEntry` (einzelner Zeiteintrag) und `BillingPeriod` (Abrechnungszeitraum mit Soll-Stunden/Budget). Nicht vermischen. - **`billingMode` pro Zeiteintrag** entscheidet den Ausgabepfad: `contingent` (zählt auf ein Kontingent), `billable_extra` (wird separat abgerechnet), `non_billable` (zählt nirgends). - **Zwei Ausgabepfade, kein Automatismus dazwischen:** Stunden-Abrechnung (→ Rechnungsposition) und Budget/Kontingent-Nachweis (→ Aufwandsposition) sind unabhängige Auswertungen auf denselben `TimeEntry`-Daten — nicht ein einziger kombinierter Bericht. - **Kennzahlen sind reine Aggregationen** (`sum`, `min`, `round`) auf gefilterten `TimeEntry`-Listen — kein separates Aggregat-Objekt in der Datenbank nötig, kann pro Abfrage berechnet werden. - **Texte sind Config, kein Hardcoding:** Rechnungs- und Aufwandszeilen entstehen aus Platzhalter-Templates (`invoiceLineTemplate`, `effortLineTemplate`), nicht aus fest verdrahteten Sätzen im Formelcode. - **Kein lauffähiges Space-Beispiel in diesem Dokument** — nur Konzept, Datenmodell und illustrative Formeln. Konkrete Umsetzung ist projektspezifisch. --- ## Problem Viele Dienstleistungsverträge laufen nach demselben Muster: Mitarbeitende erfassen Zeiteinträge (Logbuch), und am Ende einer Periode muss daraus entweder 1. eine **Rechnungsposition** werden (Stunden × Satz), oder 2. ein **Aufwandsnachweis** ohne Rechnungsbetrag (wie viel von einem Kontingent/Budget wurde verbraucht). Ohne ein gemeinsames Datenmodell entsteht das für jedes Projekt neu — mit leicht unterschiedlichen Feldnamen und Formeln, die schwer wiederverwendbar sind. ## Datenmodell ### `TimeEntry` (Logbuch-Zeile) | Feld | Typ | Bedeutung | |------|-----|-----------| | `start` / `end` | Datum/Zeit | Beginn/Ende des Zeiteintrags | | `durationHours` | Zahl (berechnet) | `(end - start) / 3600000` | | `employeeRef` | Referenz | Mitarbeiter:in | | `contractRef` | Referenz | Zugehöriger Vertrag/Kunde | | `locationType` | Enum | `onsite` \| `office` \| `remote` | | `activityLabel` | Text | Freitext oder Kategorie der Tätigkeit | | `billingMode` | Enum | `contingent` \| `billable_extra` \| `non_billable` | | `billedAt` | Datum (optional) | Gesetzt, sobald der Eintrag abgerechnet wurde | ### `BillingPeriod` (Abrechnungszeitraum) | Feld | Typ | Bedeutung | |------|-----|-----------| | `contractRef` | Referenz | Zugehöriger Vertrag/Kunde | | `periodStart` / `periodEnd` | Datum | Grenzen des Zeitraums (z. B. Kalenderjahr, Quartal, Vertragslaufzeit) | | `targetHours` | Zahl | Soll-Stunden für den Zeitraum | | `hourlyRate` | Zahl | Stundensatz | | `budgetAmount` | Zahl (optional) | Alternativ zu `targetHours`, wenn in Geld statt Stunden budgetiert wird | ## Formeln (illustrativ) ```javascript let actualHours := sum( (select TimeEntry where contractRef = period.contractRef and billingMode = "contingent" and start >= period.periodStart and start <= period.periodEnd ).durationHours ); let remainingHours := period.targetHours - actualHours; let progressPercent := min(100, round(actualHours / period.targetHours * 100)); let billableQueue := (select TimeEntry where contractRef = period.contractRef and billingMode = "billable_extra" and billedAt = null ); ``` ## Zwei Ausgabepfade ### 1. Stunden-Abrechnung → Rechnungsposition Für Zeiteinträge mit `billingMode = "billable_extra"` und `billedAt = null`: 1. `billableQueue` gruppieren (z. B. nach Periode + `activityLabel`) 2. Pro Gruppe: `lineAmount := sum(durationHours) * hourlyRate` 3. Zeilentext aus `invoiceLineTemplate` (siehe Textbaustein-Mechanismus) 4. Nach Übernahme in die Rechnung: `billedAt` auf allen enthaltenen Zeiteinträgen setzen ### 2. Budget/Kontingent → Aufwandsposition Kein Rechnungsbetrag, sondern ein **Statusnachweis** pro `BillingPeriod`: - `actualHours`, `targetHours`, `remainingHours`, `progressPercent` - Zeilentext aus `effortLineTemplate` - Typische Darstellung: `arcCustomProgressBar` oder `arcCustomKpiBar` für den Fortschritt, `arcCustomTable` für die Liste der Perioden/Verträge ## Textbaustein-Mechanismus Statt fixer Sätze im Formelcode ein Config-Objekt mit Platzhaltern — der Wortlaut bleibt pro Projekt austauschbar, ohne die Formeln anzufassen: ```javascript let cfg := { labels: { onsite: "Vor Ort", office: "Büro", remote: "Remote" }, invoiceLineTemplate: "{hours} Std. {activityLabel} ({period}) à {rate} €", effortLineTemplate: "{actualHours} von {targetHours} Std. genutzt ({progressPercent} %), Rest {remainingHours} Std." }; ``` Platzhalter werden vor der Ausgabe per einfachem String-Replace aufgelöst (z. B. `replace(cfg.invoiceLineTemplate, "{hours}", text(hours))` verkettet für jeden Platzhalter). ## Beispiele ### Fortschrittsanzeige für ein Kontingent ```javascript arcCustomProgressBar({ uniqueId: "contingent-progress-" + period.Nr, value: progressPercent, label: replace( replace(cfg.effortLineTemplate, "{actualHours}", text(actualHours)), "{targetHours}", text(period.targetHours) ) }) ``` ### Offene abrechenbare Stunden als Tabelle ```javascript arcCustomTable({ uniqueId: "billable-queue-" + contractRef.Nr, table: [ { columns: [ { value: activityLabel }, { value: text(durationHours) + " Std." }, { value: text(durationHours * hourlyRate) + " €" } ] } // ... eine Zeile pro Eintrag aus billableQueue, gruppiert ] }) ``` ## Siehe auch - [`data-bag-actions`](./data-bag-actions.md) — partielle Updates, falls Filter/Auswahl-Zustand pro Periode zwischengespeichert wird - `docs/widgets/custom-kpi-bar.md`, `docs/widgets/custom-progress-bar.md` — Fortschritts-/Kennzahlen-Darstellung - `docs/widgets/custom-table.md` — Listendarstellung für Zeiteinträge/Rechnungspositionen --- # Nested Widget Descriptors Source: https://www.arc-rider.com/docs/patterns/nested-widget-descriptors # Inhalte und Widgets in React kombinieren In unterstützten `ArcWidgetSlot`-Feldern kannst du Text, ein JSX-Element oder einen Widget-Deskriptor verwenden. Für React-Anwendungen ist JSX praktisch: Props, Typprüfung und Event-Callbacks bleiben direkt an der Komponente. ## JSX und eigene Callbacks ```tsx import { ArcWidgetBadge, ArcWidgetButton, ArcWidgetLayout, ArcWidgetText, } from '@arcrider/arcwidgets-react'; import { useState } from 'react'; const lang = document.documentElement.lang.startsWith('en') ? 'en' : document.documentElement.lang.startsWith('es') ? 'es' : 'de'; const requested = new URLSearchParams(location.search).get('data'); const pack = requested === 'empty' || requested === 'busy' ? requested : 'demo'; const t = (de: string, en: string, es: string) => lang === 'en' ? en : lang === 'es' ? es : de; export default function Example() { const [count, setCount] = useState(pack === 'busy' ? 12 : 0); const badges = pack === 'empty' ? [] : [ { value: t('Offen', 'Open', 'Abierto'), color: '#d97706' }, { value: t('Live', 'Live', 'En vivo'), color: '#15803d' }, ...(pack === 'busy' ? [ { value: t('Priorität', 'Priority', 'Prioridad'), color: '#be123c', }, { value: t('Messe', 'Fair', 'Feria'), color: '#7c3aed' }, ] : []), ]; return ({message}
> ); } ``` Der Deskriptor nutzt hier den geerbten Event-Fallback des Layouts. Wenn mehrere interaktive Kinder unterschiedliche Aktionen auslösen sollen, verwende JSX mit eigenen Callback-Props. ## Inhalte aus fremden Quellen String-Slots können HTML interpretieren. Gib fremde Texte daher über JSX aus, etwa als Textinhalt eines `span`. Verwende HTML nur aus eigener oder zuvor bereinigter Quelle. Deskriptor-Konfigurationen sind kein Ersatz für die Validierung fremder Daten. [Table mit Mini-Widgets](/docs/react/examples/custom-table-mit-mini-widgets) · [Dashboard](/docs/react/examples/dashboardansicht-mit-grid). # Nested Widget Descriptors (Object-Notation) ## AI Defaults (read first) - **Top-level** Ninox formula: use **`arcCustomLayout({ uniqueId: … })`** (function call) — not a descriptor object. - **Nested** inside another widget's content slot (`blocks[].value`, table cells, drawing sidebar, kanban cards, …): use **`{ widget, uid, data }`**. - **`widget`**: internal kebab-case ID (e.g. `"arc-widget-layout"`), **not** `arcCustomLayout`. - **`uid`**: same rules as `uniqueId` — unique, no spaces/special characters. - **`data`**: same properties as the function call; use `uid` at the top level instead of `uniqueId` inside `data`. - **Next-Gen only**: parent **and** child must come from the current **`bundle.js` / profile build** (`arcWidgetManager` v3). **Legacy Ninox-formula widgets do not support this.** - When unsure whether a widget is Next-Gen: if it only exists as an old Ninox template formula (not in the bundle), use the function call or plain HTML — **never** a descriptor object. --- # Verschachtelte Widgets als JSON-Objekt Mini-Widgets und andere Widgets sitzen in Slots als **reines JSON-Objekt** — nicht als vorgerendertes HTML. Das ist nützlich in Layout-Blöcken, Tabellen-Zellen, Drawing-Sidebars und tief verschachtelten Oberflächen. ## Zwei Schreibweisen ### Top-Level (Ninox-Formelfeld) Der Einstieg bleibt ein **Funktionsaufruf**: ```javascript arcCustomLayout({ uniqueId: "dashboard-" + Nr, embedded: false, fullscreen: true, height: "100%", direction: "vertical", blocks: [ … ] }) ``` ### Verschachtelt (Inhalt in einem Slot) In `blocks[].value` (und vergleichbaren Slots) nutzt du die **Widget-Descriptor-Notation**: ```javascript { widget: "arc-widget-button", uniqueId: "open-btn-" + Nr, // oder kurz: uid: "open-btn-" + Nr data: { title: "Öffnen", height: "20px", width: "auto", actions: [{ type: "popup", recordId: "42" }] } } ``` Für die ID akzeptiert der Descriptor **beide Schreibweisen** gleichwertig: - `uniqueId` — konsistent mit der Root-Config - `uid` — Kurzform, ebenfalls gültig ### Mapping | Funktionsaufruf | Descriptor | |-----------------|------------| | `arcCustomLayout({ … })` | `widget: "arc-widget-layout"` | | `uniqueId: "…"` | `uniqueId: "…"` oder `uid: "…"` (top-level, **nicht** in `data`) | | Alle anderen Props | In `data: { … }` | ## Vollständiges Beispiel (Layout → Button) Siehe auch [Custom Layout](/docs/widgets/custom-layout) für verschachtelte Layout-Blöcke. ```javascript { widget: "arc-widget-layout", uniqueId: "sidebar-item-" + Nr, // uid: "sidebar-item-" + Nr wäre gleichwertig data: { embedded: true, direction: "horizontal", gap: "10px", blocks: [{ width: "fraction", height: "auto", value: "Mangel ID" }, { width: "auto", height: "auto", value: { widget: "arc-widget-button", uniqueId: "open-btn-" + Nr, // uid: "open-btn-" + Nr wäre gleichwertig data: { title: "Öffnen", height: "20px", actions: [{ type: "popup", recordId: "42" }] } } }] } } ``` In Ninox bleibt `uniqueId: "sidebar-item-" + Nr` üblich, und `recordId: Nr` in Actions. ## Voraussetzung: Next-Gen-Bundle Die Engine erkennt Descriptor-Objekte in Slots über **`append_content`**: Ist `value` ein Objekt mit Feld `widget`, ruft sie `arcWidgetManager.js(widget, uid, data)` auf. Das funktioniert **nur**, wenn: 1. Das **Parent-Widget** aus dem aktuellen Bundle kommt und den Slot über `append_content` rendert. 2. Das **Child-Widget** im selben Bundle **registriert** ist. ### Next-Gen (funktioniert) - Widgets, die im aktuellen Bundle / Ninox-Template registriert sind ### Legacy (funktioniert **nicht**) - Widget existiert **nur** als alte **NinoxScript-Formel** im Template / in der Datenbank (HTML-String, kein `arcWidgetManager`) - Slot erwartet fertiges HTML oder ein Formel-Ergebnis — `{ widget: … }` wird nicht interpretiert (leer, Konsole-Fehler oder kaputtes Markup) **Noch ohne Descriptor-Unterstützung:** `arcCustomGrid`, `arcCheckBox`, `arcCustomImage`, `shortNumbers` — diese **können weder Parent noch Child** in der Descriptor-Notation sein. ## Parent-Widgets mit Descriptor-Slots Diese Widgets mounten verschachtelte Descriptors (haben `append_content`): | Widget-ID | Typische Slots | |-----------|----------------| | `arc-widget-layout` | `blocks[].value` | | `arc-widget-button` | `icon`, `title`, `subtitle`, `prefix`, `suffix` | | `arc-widget-table` | `table[].columns[].value` | | `arc-widget-drawing` | `sidebarItem`, `rightSideContent`, Shape-Inhalte | | `arc-widget-kanban` | Karten-`value`, Swimlane-Inhalte | | `arc-widget-select` | `currentValue`, `items[].title` | | `arc-widget-input` | Label-/Prefix-Slots | | `arc-widget-badge` | Badge-Inhalt | | `arc-widget-upload` | `container.value` | | `arc-widget-calendar-week` | Entry-Inhalte | | `arc-widget-calendar-timeline` | Zellen-/Entry-Inhalte | | `arc-widget-calendar-grid` | Zellen-Inhalte | ## Child-Widgets (häufig verschachtelt) Alle im Bundle registrierten Widgets können als **`widget`-Wert** erscheinen, z. B.: | `widget`-Wert | Ninox-Funktion | |---------------|----------------| | `arc-widget-layout` | `arcCustomLayout` | | `arc-widget-button` | `arcCustomButton` | | `arc-widget-badge` | `arcCustomBadge` | | `arc-widget-icon` | `arcCustomIcon` | | `arc-widget-select` | `arcCustomSelect` | | `arc-widget-input` | `arcCustomInput` | | `arc-widget-text` | (Mini-Widget / Text) | Die **`widget`-ID** steht in der Tabelle oben (`arc-widget-button`, `arc-widget-layout`, …). ## Wann welches Format? | Situation | Format | |-----------|--------| | Root-Widget in Ninox-Formelfeld | `arcCustomXxx({ uniqueId, … })` | | Inhalt in Layout-Block, Table-Zelle, Drawing-Sidebar, … | `{ widget, uniqueId, data }` oder `{ widget, uid, data }` | | Legacy-Widget noch nicht im Bundle | Funktionsaufruf oder HTML — **kein** Descriptor | | Plain Text in Block | String (`value: "Text"`) | ## Fehler vermeiden - ❌ `widget: "arcCustomLayout"` oder `widget: "ArcWidgetLayout"` — falsche ID (CamelCase) - ❌ `uniqueId` oder `uid` **innerhalb** von `data` setzen — immer top-level (auf gleicher Ebene wie `widget` und `data`) - ❌ Descriptor in Top-Level-Formelfeld ohne Parent — use function call - ❌ Descriptor als Child eines Legacy-Layouts aus altem Ninox-Template ## Siehe auch - [Custom Layout](/docs/widgets/custom-layout) — Layout-Blocks und `value`-Slots - [Button](/docs/mini-widgets/buttons) — `prefix` / `suffix` als nested widgets - [Drawing Pins](/docs/examples/custom-drawing-pins) — verschachtelte Widgets in Sidebar/Detail # Nested Widget Descriptors (Object-Notation) ## AI Defaults (read first) - **Root** in Vue: the `ArcWidget…` component with `:data` and `@action`. - **Nested** in a slot: `{ widget, uid, data }` inside the parent's `data`. - **`widget`**: kebab-case ID (`"arc-widget-button"`), not the Vue component name. --- # Verschachtelte Widgets als JSON-Objekt Mini-Widgets und andere Widgets sitzen in Slots als **reines JSON-Objekt** — nicht als vorgerendertes HTML. Das ist nützlich in Layout-Blöcken, Tabellen-Zellen, Drawing-Sidebars und tief verschachtelten Oberflächen. ## Zwei Schreibweisen ### Root (Vue-Komponente) Der Einstieg ist die Komponente. Nested Descriptors leben **innen** in `data`. ### Verschachtelt (Inhalt in einem Slot) In `blocks[].value` (und vergleichbaren Slots) nutzt du die **Widget-Descriptor-Notation**: ```javascript { widget: "arc-widget-button", uniqueId: "open-btn-" + Nr, // oder kurz: uid: "open-btn-" + Nr data: { title: "Öffnen", height: "20px", width: "auto", actions: [{ type: "popup", recordId: "42" }] } } ``` Für die ID akzeptiert der Descriptor **beide Schreibweisen** gleichwertig: - `uniqueId` — konsistent mit der Root-Config - `uid` — Kurzform, ebenfalls gültig ### Mapping | Vue-Komponente | Descriptor | |----------------|------------| | `{error || 'Änderungen werden lokal gespeichert.'}
> ); } ``` ## Datenzugriff Die Beispiele sind vollständig mit lokalen Daten ausführbar. `localStorage` bleibt auf den Browser und die Domain begrenzt. Für gemeinsame Daten ersetze diesen Teil durch deine API; Zugriffskontrolle, Laden und Konflikte behandelt deine Anwendung. Für Listenfilter: [Auftragsliste](/docs/react/examples/custom-table-mit-mini-widgets). Für Drag & Drop: [Kanban speichern](/docs/react/examples/react-kanban-persistenz). --- # Custom Drawing Teil 1: Zeichnen im Bild Source: https://www.arc-rider.com/docs/examples/custom-drawing-zeichnen-im-bild-teil1 # Ein Bild annotieren und exportieren in React Ein Bild auswählen, als Form laden, weitere Formen bearbeiten und das Ergebnis als PNG bereitstellen. ## Vorbereitung Installiere das [React-Paket](/docs/react/intro/setup-react). Das Beispiel ist eine vollständige Komponente für eine Browser-App und verwendet eigene Beispieldaten. ## Vollständiges Beispiel ```tsx import { useState } from 'react'; import { ArcWidgetUpload, ArcWidgetDrawing } from '@arcrider/arcwidgets-react'; import type { ArcWidgetDrawingShape } from '@arcrider/arcwidgets-react'; export default function Example() { const [shapes, setShapes] = useStateShapes (Untertabelle)
{status}
> ); } ``` ## So funktioniert es Die Form-ID verbindet eine Markierung mit deinem Datensatz. In einer Produktivanwendung speicherst du die Formen über deine API und validierst geladene Daten. ## In deiner Anwendung Halte fachliche Daten in deinem State oder Store. Ersetze lokale Beispieldaten und gegebenenfalls `localStorage` durch deinen Datenzugriff. Behandle Laden, leere Daten und Speicherfehler sichtbar in der Oberfläche. [State und Events](/docs/react/patterns/react-state-events) · [Komponentenübersicht](/docs/react/intro/react-components). # Custom Drawing Teil 2: Mangel-Verortung mit Pins # Mangel-Verortung mit Pins  In [Custom Drawing Teil 1: Zeichnen im Bild](https://www.arc-rider.de/documentation/custom-drawing) hast du gelernt, wie du ein Tool entwickelst, mit dem du Bilder hochladen, darin zeichnen und die bearbeiteten Bilder wieder abspeichern kannst. In diesem **Teil 2** zeigen wir dir, wie du **PDF-Dateien hochladen** und darauf **Pins setzen** kannst – zum Beispiel für die **Mangelverortung**. Das User Interface unterscheidet sich hier insbesondere im rechten Aktionsbereich, wo die Mängel direkt klickbar sind. Zusätzlich können **Pläne versioniert** werden, etwa wenn Änderungen am Plan vorgenommen werden. Das Besondere dabei: **Beim Duplizieren bleiben die Mängel erhalten**, während sich der zugrunde liegende Plan aktualisieren kann. So lassen sich Änderungen effizient nachvollziehen und verwalten. ## Notwendige Widgets - Custom Drawing - Custom Upload - Custom Layout - Input - Icon - Button - Image ## Vorgehensweise Um Custom Drawing für den Use Case "Pin-Verortung auf PDF" bei dir richtig einzusetzen, musst du einige Schritte in deiner Ninox Datenbank beachten.Shapes (Untertabelle)
Pläne (Untertabelle) - Mängel (Untertabelle)
Shapes (Untertabelle)
{'Ändere den Suchbegriff.'}
Detail: {selected}
> ); } ``` ## So funktioniert es Der Filter erzeugt eine neue Zeilenliste. Die Auswahl verwendet eine stabile Auftrags-ID. Ersetze die Beispieldaten durch Daten aus deiner API. ## In deiner Anwendung Halte fachliche Daten in deinem State oder Store. Ersetze lokale Beispieldaten und gegebenenfalls `localStorage` durch deinen Datenzugriff. Behandle Laden, leere Daten und Speicherfehler sichtbar in der Oberfläche. [State und Events](/docs/react/patterns/react-state-events) · [Komponentenübersicht](/docs/react/intro/react-components). # Custom Table mit Mini Widgets # Mehr Übersicht in Tabellen: Mini Widgets direkt in der Custom Table  In diesem Beitrag zeigen wir euch ein praxisnahes und komplexes Beispiel für eine **Custom Table**, die eure **Aufgabenverwaltung in Ninox** deutlich übersichtlicher und interaktiver macht. Die Tabelle gruppiert Aufgaben nach Aufträgen und kombiniert leistungsstarke Funktionen mit einer modernen Darstellung. Dank der integrierten **Mini Widgets** – darunter [Checkbox](/documentation/check-box), [Input](/documentation/input), [Progress Bar](/documentation/progress-bar), [Button](/documentation/buttons), [Select](/documentation/select), [Icon](/documentation/custom-icon) und [Pagination](/documentation/pagination) – lässt sich die Tabelle direkt bedienen, ohne die Ansicht verlassen zu müssen. Ideal für alle, die Effizienz und Benutzerfreundlichkeit in ihren Workflows kombinieren möchten. ## Aufgabenverwaltung leicht gemacht Mit unserer Custom Table verwaltet ihr Aufgaben so einfach und effizient wie nie zuvor. Dank der Gruppierung nach Aufträgen behaltet ihr stets den Überblick – und das ist erst der Anfang. ## ✅ Aufgaben abhaken leicht gemacht Per integrierter [Checkbox](/documentation/check-box) markiert ihr erledigte Aufgaben direkt in der Tabelle. Ein visuelles Feedback zeigt euch sofort, was bereits geschafft ist. ## 📝 Beschreibungen direkt editieren In der Spalte „Beschreibung“ findet ihr mehrzeilige [Inputfelder](/documentation/input). Damit könnt ihr Aufgaben direkt bearbeiten – ohne zusätzliche Klicks oder das Öffnen einzelner Datensätze. ## 📅 Termine schnell anpassen Mit dem eingebauten Datumsfeld aktualisiert ihr Fälligkeiten im Handumdrehen. Einfach das neue Datum auswählen – fertig. ## 📊 Fortschritt immer im Blick Die integrierte [ProgressBar](/documentation/progress-bar) zeigt euch auf einen Blick, wie weit ihr mit den Aufgaben seid – farblich visualisiert und jederzeit aktuell. ## ➕ Unteraufgaben direkt hinzufügen Per [Button](/documentation/buttons) erstellt ihr neue Unteraufgaben in einem Ninox-Popup. So lassen sich Aufgaben flexibel verfeinern und strukturieren. ## 🔄 Status einfach umstellen Ein [Dropdown](/documentation/select)-Menü ermöglicht die direkte Änderung des Aufgabenstatus – schnell, intuitiv und ohne Umwege. ## 🗑️ Aufräumen leicht gemacht Ein Klick auf das [Löschen-Icon](/user/account/icons) entfernt überflüssige Aufgaben direkt aus der Tabelle. Schnell, effizient und ohne Extra-Schritte. ## 🎨 Farbige Übersicht Die dynamische groupRowColor-Funktion färbt die Zeilen je nach Fortschritt automatisch ein – für maximale Übersicht und visuelle Klarheit. ### ⚙️ Automationen weiter nutzen Die Widgets greifen nicht in eure Datenstruktur ein – ihr könnt weiterhin alle gewohnten Automationen in Ninox nutzen. Zum Beispiel: Setzt automatisch das Datum auf `today()` bei Abschluss einer Aufgabe – alles über „Trigger nach Änderung“ steuerbar. ## Fazit Mit dieser **Custom Table** bringt ihr eure Aufgabenverwaltung auf ein neues Level. Interaktive Mini Widgets wie Checkbox, Input, Progress Bar, Button, Select, Icon und Pagination machen eure Tabelle zum leistungsstarken UI-Element. 🚀 **Effizienz trifft Übersicht – direkt in Ninox.** ## Anwendungscode des Beispiels ```javascript let current := this; let list := (flteredList_Aufgaben[cnt(Auftrag.Aufgaben) > 5] order by cnt(Auftrag.Aufgaben)); //Ihr könnt euch Listen in extra Formel-Feldern vorfiltern, damit der Code nicht so lang wird let maxEntries := 'Einträge pro Seite'; // Hilfsfeld Zahl let currentPage := 'Aktuelle Seite'; // Hilfsfeld Zahl let paginatedList := unique(record(Aufgaben,0))[!= 0]; for listItem in slice(list.Nr, maxEntries * (currentPage - 1), maxEntries * currentPage) do paginatedList := unique(paginatedList, record(Aufgaben,listItem)) end; let data := { uniqueListId: "Aufgaben Demo 1", tableId: tableId(first(list)), height: "600px", width: "100px", groupedBy: "Aufgabe", groupsCollapsed: false, embedded: false, table: paginatedList.[{ recordId: Nr, rowColor: "", rowHeight: "auto", rowPaddingY: "10px", groupRowColor: let currentRecord := Nr; if cnt(list[Checkliste.Nr = currentRecord.Checkliste.Nr and Erledigt = true]) = cnt(list[Checkliste.Nr = currentRecord.Checkliste.Nr]) then "#dcf2de" else "#eee" end, columns: [{ field: "Erledigt", title: arcCheckBox({ value: if cnt(list[Erledigt = true]) != cnt(list) then false else if cnt(list[Erledigt = null]) = cnt(list) then null else if cnt(list[Erledigt = true]) = cnt(list) then true end end end }), value: arcCheckBox({ value: Erledigt }), width: "40px", align: "center", fixed: "left", actions: [{ recordId: Nr, type: "update", field: "G", value: if Erledigt = true then null else true end, headerAction: true }], groupValue: arcCustomIcon({ name: "caret-circle-double-down-fill" }) }, { field: "Aufgabe", title: "Aufgabe", width: "", value: replace(Aufgabe, ">", "❯"), actions: [{ recordId: Nr, type: "popup", showPopupButton: true }], groupValue: html("**" + Auftrag.Titel + "**"), groupByValue: Auftrag.Nr }, { field: "'Erledigt am'", title: "Beschreibung", value: arcCustomInput({ uniqueId: "Mein-Textarea-" + Nr, recordId: Nr, fieldId: "B", title: Beschreibung, value: Beschreibung, type: "textarea", embedded: true, disabled: false, width: "", height: "auto", minHeight: "40px", fontColor: "", fontSize: "", fontWeight: "", backgroundColor: "", borderWidth: "", borderColor: "", borderRadius: "", placeholderSettings: { value: "Beschreibe die Aufgabe..." }, focusAction: { width: "", showFocusOutline: "", outlineWidth: "", outlineColor: "" }, alignX: "left", paddingX: "" }), align: "left", width: "280px" }, { field: "'Erledigt am'", title: "Erledigt am", value: arcCustomInput({ uniqueId: "Mein-Input-" + Nr, recordId: Nr, fieldId: "A1", value: Startdatum, type: "date", embedded: true, disabled: false, width: "", height: "", fontColor: "", fontSize: "", fontWeight: "", backgroundColor: "#fff6d8", borderWidth: "", borderColor: "", borderRadius: "", placeholderSettings: { value: "Datum eintragen", fontColor: "#e9595c", backgroundColor: "#fff7f7" }, focusAction: { width: "", showFocusOutline: "", outlineWidth: "", outlineColor: "" }, alignX: "left", paddingX: "" }), align: "left", width: "180px" }, { field: "Aufgaben", title: "Aufgaben", value: arcCustomProgressBar({ uniqueId: "", width: "", fontSize: "", fontColor: "", backgroundColor: "", progressColor: "", valueTotal: cnt(Unteraufgaben), valueProgress: cnt(Unteraufgaben[Erledigt = true]), valueText: cnt(Unteraufgaben[Erledigt = true]) + "/" + cnt(Unteraufgaben) }), align: "left", width: "150px", groupValue: arcCustomProgressBar({ uniqueId: "Erledigte Aufgaben", title: "", width: "", fontSize: "", fontColor: "", backgroundColor: "#fff", progressColor: "", valueTotal: cnt(Auftrag.Aufgaben), valueProgress: cnt(Auftrag.Aufgaben[Erledigt = true]), valueText: cnt(Auftrag.Aufgaben[Erledigt = true]) + "/" + cnt(Auftrag.Aufgaben) }) }, { field: "", title: "", value: arcCustomButton({ uniqueId: "Unteraufgabe erstellen" + Nr, title: "Unteraufgabe", width: "100%", icon: arcCustomIcon({ name: "plus-bold", color: "#0062AA" }), fontSize: "", fontColor: "#0062AA", iconColor: "#0062AA", backgroundColor: "#0062AA33", borderColor: "#0062AA", borderRadius: "50px", showBadge: false, badgeTitle: cnt(Unteraufgaben), badgeColor: "", badgeBackground: "", badgeBorderColor: "", badgePosition: "", hoverActions: { fontColor: "#ffffff", iconColor: "#ffffff", backgroundColor: "#0062AA", borderColor: "#0062AA", animation: "0.5s" }, actions: [{ type: "create", tableId: "B", popup: true, changeFieldValues: [{ fieldId: "S", value: number(Nr) }] }] }), align: "left", width: "180px" }, { field: "Status", title: "Status", value: arcCustomSelect({ uniqueId: "Status" + Nr, currentValue: text(Status), recordId: Nr, field: "Q", width: "100%", height: "30px", fontSize: "10px", placeholder: "Status wählen", emptyValue: "Zurücksetzen", items: let id := Nr; (select Status_Aufgabe).[{ title: Bezeichnung, active: number(id.Status) = number(Nr), value: number(Nr) }] }), align: "right", width: "150px" }, { field: "delete", title: arcCustomIcon({ name: "trash" }), width: "40px", align: "center", value: arcCustomIcon({ name: "trash" }), actions: [{ recordId: Nr, type: "delete" }] }] }], footer: { showFooter: true, showActionButton: false, actionButtonTitle: "Neue Aufgabe hinzufügen", leftSideContent: arcCustomButton({ uniqueId: "Button popUp" + Nr, title: "Neue Checkliste hinzufügen", width: "", icon: arcCustomIcon({ name: "plus-bold", color: "#0062AA" }), fontSize: "", fontColor: "#0062AA", iconColor: "#0062AA", backgroundColor: "#0062AA33", borderColor: "#0062AA", borderRadius: "50px", showBadge: false, badgeTitle: "", badgeColor: "", badgeBackground: "", badgeBorderColor: "", badgePosition: "", hoverActions: { fontColor: "#ffffff", iconColor: "#ffffff", backgroundColor: "#0062AA", borderColor: "#0062AA", animation: "0.8s" }, actions: [{ type: "create", tableId: "C", popup: true, changeFieldValues: [{ fieldId: "", value: "" }, { fieldId: "", value: "" }] }] }), rightSideContent: arcCustomPagination({ uniqueId: Nr, editable: true, recordId: current.Nr, fieldId: "P", direction: "horizontal", title: current.'Aktuelle Seite', value: current.'Aktuelle Seite', total: ceil(cnt(list) / maxEntries), totalPrefix: " von " }) } }; arcCustomTable(data) ``` --- # Dashboardansicht mit Grid Source: https://www.arc-rider.com/docs/examples/dashboardansicht-mit-grid # Dashboard mit CSS Grid in React Kennzahlen, Fortschritt und eine Tabelle in einem responsiven Dashboard anordnen. ## Vorbereitung Installiere das [React-Paket](/docs/react/intro/setup-react). Das Beispiel ist eine vollständige Komponente für eine Browser-App und verwendet eigene Beispieldaten. ## Vollständiges Beispiel ```tsx import { ArcWidgetKpiBar, ArcWidgetTable, ArcWidgetProgressBar, } from '@arcrider/arcwidgets-react'; export default function Example() { return (Bitte prüfen: {name}
} {step === 2 &&Erfasst: {name}
} {step < 2 && ({error}
> ); } ``` ## So funktioniert es Vor dem nächsten Schritt wird die Pflichtangabe geprüft. Das Steps-Widget zeigt den Zustand; Fachlogik und Navigation bleiben in der Anwendung. ## In deiner Anwendung Halte fachliche Daten in deinem State oder Store. Ersetze lokale Beispieldaten und gegebenenfalls `localStorage` durch deinen Datenzugriff. Behandle Laden, leere Daten und Speicherfehler sichtbar in der Oberfläche. [State und Events](/docs/react/patterns/react-state-events) · [Komponentenübersicht](/docs/react/intro/react-components). # Datenaufnahme in Schritten # Datenaufnahme in Schritten Wenn du im Außendienst tätig bist und schnell sowie unkompliziert Daten erfassen musst, ist eine klare und gut strukturierte Eingabemaske unerlässlich. Besonders bei einer großen Anzahl an zu erfassenden Feldern ist es sinnvoll, die Dateneingabe in mehrere Schritte zu unterteilen. Auf diese Weise wird der Benutzer nicht überfordert und kann schneller sowie geordneter die notwendigen Informationen eingeben. In diesem Beitrag zeigen wir dir, wie du mit einfachen Tricks und nützlichen Widgets eine benutzerfreundliche Oberfläche gestalten kannst, die dir die Datenerfassung erheblich erleichtert. Erfahre, wie du die Effizienz deines Arbeitsalltags steigerst und dabei stets den Überblick behältst. Widget: [ Custom Layout ](/documentation/custom-layout)mit integrierten Mini Widgets: [Input](/documentation/input), [Button](/documentation/buttons), [Text](/documentation/text) und [Icon](/documentation/custom-icon). ## Anwendungscode Teil 1 Layouts & Überschriften ```javascript let current := this; let namenLayout := arcCustomLayout({ uniqueId: "namenLayout " + Nr, embedded: true, fullscreen: false, direction: "vertical", alignX: "center", alignY: "center", width: "490px", height: "100%", gap: "10px", backgroundColor: "", paddingY: "", paddingX: "", blocks: [{ width: "", height: "auto", lineHeight: "", alignX: "center", color: "", styles: "", value: arcCustomInput({ uniqueId: "vorname" + Nr, recordId: Nr, fieldId: "G", title: text(Vorname), value: text(Vorname), type: "text", embedded: true, disabled: false, width: "100%", height: "40px", alignX: "left", paddingY: "", paddingX: "", fontColor: "", fontSize: "", fontWeight: "", backgroundColor: "#F0F0F0", borderWidth: "", borderColor: "", borderRadius: "", focusAction: { width: "", showFocusOutline: "", outlineWidth: "", outlineColor: "" }, labelSettings: { title: "Vorname", fontSize: "", alignX: "", gap: "10px" } }) }, { width: "", height: "auto", lineHeight: "", alignX: "center", color: "", value: arcCustomInput({ uniqueId: "Nachname" + Nr, recordId: Nr, fieldId: "H", title: text(Nachname), value: text(Nachname), type: "text", embedded: true, disabled: false, width: "100%", height: "40px", alignX: "left", paddingY: "", paddingX: "", fontColor: "", fontSize: "", fontWeight: "", backgroundColor: "#F0F0F0", borderWidth: "", borderColor: "", borderRadius: "", focusAction: { width: "", showFocusOutline: "", outlineWidth: "", outlineColor: "" }, labelSettings: { title: "Nachname", fontSize: "", alignX: "", gap: "10px" } }) }] }); let adressLayout := arcCustomLayout({ uniqueId: "adressLayout " + Nr, embedded: true, fullscreen: false, direction: "vertical", alignX: "center", alignY: "center", width: "490px", height: "100%", gap: "10px", backgroundColor: "", paddingY: "", paddingX: "", blocks: [{ width: "", height: "auto", lineHeight: "", alignX: "center", color: "", styles: "", value: arcCustomInput({ uniqueId: "Straße u Hausnr" + Nr, recordId: Nr, fieldId: "I", title: text('Straße + Hausnr'), value: text('Straße + Hausnr'), type: "text", embedded: true, disabled: false, width: "100%", height: "40px", alignX: "left", paddingY: "", paddingX: "", fontColor: "", fontSize: "", fontWeight: "", backgroundColor: "#F0F0F0", borderWidth: "", borderColor: "", borderRadius: "", focusAction: { width: "", showFocusOutline: "", outlineWidth: "", outlineColor: "" }, labelSettings: { title: "Straße & Hausnummer", fontSize: "", alignX: "", gap: "10px" } }) }, { width: "", height: "auto", lineHeight: "", alignX: "center", color: "", value: arcCustomLayout({ uniqueId: "adressZeile PLZ und Ort " + Nr, embedded: true, fullscreen: false, direction: "horizontal", alignX: "left", alignY: "center", width: "", height: "", gap: "10px", backgroundColor: "", paddingY: "", paddingX: "", blocks: [{ width: "200px", height: "auto", lineHeight: "", alignX: "left", color: "", styles: "", value: arcCustomInput({ uniqueId: "PLZ" + Nr, recordId: Nr, fieldId: "J", title: text(PLZ), value: text(PLZ), type: "text", embedded: true, disabled: false, width: "100%", height: "40px", alignX: "left", paddingY: "", paddingX: "", fontColor: "", fontSize: "", fontWeight: "", backgroundColor: "#F0F0F0", borderWidth: "", borderColor: "", borderRadius: "", focusAction: { width: "", showFocusOutline: "", outlineWidth: "", outlineColor: "" }, labelSettings: { title: "PLZ", fontSize: "", alignX: "", gap: "10px" } }) }, { width: "", height: "auto", lineHeight: "", alignX: "left", color: "", value: arcCustomInput({ uniqueId: "Ort" + Nr, recordId: Nr, fieldId: "K", title: text(Ort), value: text(Ort), type: "text", embedded: true, disabled: false, width: "100%", height: "40px", alignX: "left", paddingY: "", paddingX: "", fontColor: "", fontSize: "", fontWeight: "", backgroundColor: "#F0F0F0", borderWidth: "", borderColor: "", borderRadius: "", focusAction: { width: "", showFocusOutline: "", outlineWidth: "", outlineColor: "" }, labelSettings: { title: "Ort", fontSize: "", alignX: "", gap: "10px" } }) }] }) }] }); let kontaktLayout := arcCustomLayout({ uniqueId: "kontaktLayout " + Nr, embedded: true, fullscreen: false, direction: "vertical", alignX: "center", alignY: "center", width: "490px", height: "100%", gap: "10px", backgroundColor: "", paddingY: "", paddingX: "", blocks: [{ width: "", height: "auto", lineHeight: "", alignX: "center", color: "", styles: "", value: arcCustomInput({ uniqueId: "kontakt" + Nr, recordId: Nr, fieldId: "N", title: text(Telefon), value: text(Telefon), type: "text", embedded: true, disabled: false, width: "100%", height: "40px", alignX: "left", paddingY: "", paddingX: "", fontColor: "", fontSize: "", fontWeight: "", backgroundColor: "#F0F0F0", borderWidth: "", borderColor: "", borderRadius: "", focusAction: { width: "", showFocusOutline: "", outlineWidth: "", outlineColor: "" }, labelSettings: { title: "Telefon", fontSize: "", alignX: "", gap: "10px" } }) }, { width: "", height: "auto", lineHeight: "", alignX: "center", color: "", value: arcCustomInput({ uniqueId: "Ansprechpartner" + Nr, recordId: Nr, fieldId: "O", title: text('E-Mail'), value: text('E-Mail'), type: "text", embedded: true, disabled: false, width: "100%", height: "40px", alignX: "left", paddingY: "", paddingX: "", fontColor: "", fontSize: "", fontWeight: "", backgroundColor: "#F0F0F0", borderWidth: "", borderColor: "", borderRadius: "", focusAction: { width: "", showFocusOutline: "", outlineWidth: "", outlineColor: "" }, labelSettings: { title: "E-Mail", fontSize: "", alignX: "", gap: "10px" } }) }] }); arcCustomLayout({ uniqueId: "Content Aufnahme " + Nr, embedded: false, fullscreen: false, direction: "vertical", alignX: "center", alignY: "top", width: "", height: "450px", gap: "", backgroundColor: "", paddingY: "", paddingX: "", blocks: [{ width: "", height: "100px", lineHeight: "", alignX: "center", color: "", value: arcCustomText({ uniqueId: "headline" + Nr, embedded: true, width: "", height: "", alignY: "", alignX: "center", fontSize: "27px", fontWeight: "700", fontColor: "", lineHeight: "", textWrap: "", textScroll: "", textAlign: "", textDirection: "horizontal", textDecoration: { type: "", style: "", color: "", thickness: "" }, value: if Menu = "1" then "Name" else if Menu = "2" then "Adresse" else if Menu = "3" then "Kontakt" end end end }) }, { width: "", height: "fraction", lineHeight: "", alignX: "center", color: "", value: if Menu = "1" then namenLayout else if Menu = "2" then adressLayout else if Menu = "3" then kontaktLayout end end end }, { width: "", height: "100px", lineHeight: "", alignX: "center", color: "", styles: "", value: menuSteps }] }) ``` ## Anwendungscode Teil 2 Menü Buttons ```javascript let current := this; function buttonSubmenu(data : any) do arcCustomButton({ uniqueId: "Button submenu " + data.uniqueId, title: data.title, width: "", height: "", alignY: "", alignX: "", icon: arcCustomIcon({ name: "check-circle-fill", color: if data.completed = true then "#b5db3b" else "#ddd" end, iconSize: "19px", containerSize: "21px", backgroundColor: "#fff", borderSize: "", borderColor: "#fff", borderRadius: "50%" }), fontSize: "", fontColor: if data.active = true then "#fff" else "#777" end, backgroundColor: if data.active = true then "#4970FF" else if data.completed = true then "#f4fcd9" else "#fff" end end, borderColor: if data.active = true then "#4970FF" else "#777" end, borderRadius: "18px" }) end; let menuList := [{ title: "Name", value: "1", showLine: true, completed: Vorname != null and Nachname != null }, { title: "Adresse", value: "2", showLine: true, completed: 'Straße + Hausnr' != null and PLZ != null and Ort != null }, { title: "Kontakt", value: "3", showLine: true, completed: Telefon != null and 'E-Mail' != null }]; arcCustomLayout({ uniqueId: "menuSteps " + Nr, embedded: true, fullscreen: false, direction: "horizontal", alignX: "center", alignY: "center", width: "", height: "", gap: "5px", backgroundColor: "", paddingY: "", paddingX: "", blocks: menuList.[{ width: "auto", height: "auto", lineHeight: "", alignX: "left", color: "", styles: "", clickAction: { type: "update", recordId: current.Nr, field: "M", value: value }, value: buttonSubmenu({ uniqueId: "buttonSubmenu " + value + " " + Nr, title: title, value: value, active: current.Menu = value, completed: completed }) }] }) ``` --- # Image-Slider Source: https://www.arc-rider.com/docs/examples/image-slider # Bildergalerie mit Slideshow in React Zwei selbst erzeugte Bilder anzeigen und den aktiven Slide ausgeben. ## Vorbereitung Installiere das [React-Paket](/docs/react/intro/setup-react). Das Beispiel ist eine vollständige Komponente für eine Browser-App und verwendet eigene Beispieldaten. ## Vollständiges Beispiel ```tsx import { useState } from 'react'; import { ArcWidgetSlideshow } from '@arcrider/arcwidgets-react'; // Statisches, selbst erzeugtes HTML; keine ungeprüften Benutzereingaben einsetzen. const images = ['Grundriss A', 'Grundriss B'].map((label) => { const svg = ``; return { value: `Bild {index + 1} von {images.length}
> ); } ``` ## So funktioniert es `items[].value` ist hier ein HTML-String mit einem Bild. Slideshow unterstützt aktuell keine JSX-Slots. Fremde Texte oder URLs dürfen nicht ungeprüft in HTML eingesetzt werden. ## In deiner Anwendung Halte fachliche Daten in deinem State oder Store. Ersetze lokale Beispieldaten und gegebenenfalls `localStorage` durch deinen Datenzugriff. Behandle Laden, leere Daten und Speicherfehler sichtbar in der Oberfläche. [State und Events](/docs/react/patterns/react-state-events) · [Komponentenübersicht](/docs/react/intro/react-components). # Image-Slider # 📸 Image-Slider in Ninox  So baust du deinen individuell gestalteten Bildwechsler – ganz ohne externe Galerie-Tools. Du möchtest mehrere Bilder aus einer Tabelle übersichtlich und interaktiv durchblättern? In diesem Beitrag zeigen wir dir Schritt für Schritt, welche Widgets und Ninox-Einstellungen du brauchst – inklusive Beispielcode zur Umsetzung. ## 🧩 Benötigte Widgets - [Custom Layout](/documentation/custom-layout) - [Custom Image](/documentation/images) - [Custom Text](/documentation/text) - [Button](/documentation/buttons) ## 📁 Aufbau deiner Tabelle Du brauchst eine Tabelle mit folgenden Feldern:| Feldname | Typ |
|---|---|
| `Bild` | Dokumenten-/Bild-Feld |
| `shareUrl` | URL-Feld |
| `sortId` | Zahlenfeld (zur Sortierung) |
| `Beschreibung` | Text-Feld |
| `currentImage` | Hilfsfeld (Text), z. B. im Layout |
{message}
> ); } ``` ## So funktioniert es Die Startstunde wird in Millisekunden umgerechnet. Drag & Drop bleibt deaktiviert, da die aktuellen Drag-Callbacks keine vollständigen neuen Zielzeiten liefern. ## In deiner Anwendung Halte fachliche Daten in deinem State oder Store. Ersetze lokale Beispieldaten und gegebenenfalls `localStorage` durch deinen Datenzugriff. Behandle Laden, leere Daten und Speicherfehler sichtbar in der Oberfläche. [State und Events](/docs/react/patterns/react-state-events) · [Komponentenübersicht](/docs/react/intro/react-components). # Individuelles Design in der CalendarWeek # Individuelles Design in der CalendarWeek  In diesem Beitrag zeigen wir euch Schritt für Schritt, wie ihr mithilfe von **Layouts** und ergänzenden **Widgets** eine individuell gestaltete **Card** für die `customCalendarWeek` aufbauen könnt. 💡 Ihr lernt dabei nicht nur, wie sich Widgets elegant **ineinander verschachteln** lassen, sondern erhaltet auch direkt die passenden **Beispielcodes** aus unserem Setup. Ob als **direkte Vorlage** (Tabellennamen, Felder und IDs müsst ihr natürlich auf euer System anpassen) – oder als **Inspiration für eure eigenen Designs** im typischen arcRiderWidget-Stil: Dieser Guide hilft euch dabei, euren Kalender optisch und funktional aufs nächste Level zu bringen. ## Benötigte Widgets - **Widget: **[*customCalendarWeek*](/documentation/custom-calendar-week) - **Integrierte Mini Widgets: **[*Layout*](/documentation/custom-layout)*, *[*Button*](https://www.arc-rider.de/documentation/buttons)*, *[*Select*](https://www.arc-rider.de/documentation/select)*, *[*Icon*](https://www.arc-rider.de/documentation/custom-icon)*, *[*Image*](/documentation/images)* und *[*Text*](/documentation/text)*.* ## Let’s build together 🚀 Bevor wir loslegen, sorgt bitte dafür, dass eure **Ninox-Datenbank** die nötigen Tabellen und Felder enthält – entweder nutzt ihr bestehende Strukturen oder erstellt sie neu. Für das Layout der Card in diesem Use Case verwenden wir folgende Tabellen:| Tabellen | |
|---|---|
|  | - Kunden
- Mitarbeiter
|
{status}
> ); } ``` ## Datenfluss Der Callback kopiert die aktuelle Liste, entfernt die Karte aus der Quellspalte und fügt sie am Ziel ein. Erst nach erfolgreichem Speichern wird React-State aktualisiert. Ein abgelehntes Promise lässt den Adapter die optimistische Verschiebung abbrechen. Das Beispiel verwendet flache Spalten. Bei gruppierten Boards beziehst du zusätzlich `source_section_index` und `target_section_index` ein. Ersetze `localStorage` durch deine API; diese sollte stabile Karten-IDs statt UI-Indizes speichern. [Kanban-Referenz](/docs/react/widgets/custom-kanban) · [State und Events](/docs/react/patterns/react-state-events). --- # Sortierung innerhalb einer Table Source: https://www.arc-rider.com/docs/examples/sortierung-innerhalb-einer-table # Text, Zahlen und Datum sortieren in React Drei Spalten mit unterschiedlichen Sortiertypen konfigurieren. ## Vorbereitung Installiere das [React-Paket](/docs/react/intro/setup-react). Das Beispiel ist eine vollständige Komponente für eine Browser-App und verwendet eigene Beispieldaten. ## Vollständiges Beispiel ```tsx import { ArcWidgetTable } from '@arcrider/arcwidgets-react'; const entries = [ { id: '1', name: 'Beta', amount: 9, day: '2026-09-20', }, { id: '2', name: 'Alpha', amount: 120, day: '2026-09-17', }, ]; export default function Example() { return ({error || 'Änderungen werden lokal gespeichert.'}
> ); } ``` ## So funktioniert es Das Beispiel speichert lokal. Ersetze `localStorage.setItem` durch deinen API-Aufruf und gib das gesamte Promise aus dem Callback zurück. Ändere den bestätigten Status erst nach erfolgreichem Speichern. ## In deiner Anwendung Halte fachliche Daten in deinem State oder Store. Ersetze lokale Beispieldaten und gegebenenfalls `localStorage` durch deinen Datenzugriff. Behandle Laden, leere Daten und Speicherfehler sichtbar in der Oberfläche. [State und Events](/docs/react/patterns/react-state-events) · [Komponentenübersicht](/docs/react/intro/react-components). # Status als Buttons ausgeben # Status als Buttons ausgeben  Möchtest du einen Status, beispielsweise den Aufgabenstatus, auf einer Oberfläche wie einer[ Custom Table](/documentation/custom-table) oder direkt auf deinem Datensatz anzeigen? Es gibt verschiedene Möglichkeiten, dies zu erreichen. Du kannst entweder ein [Select-Feld ](/documentation/select)verwenden, um den Status auswählbar zu machen, oder eine effizientere Methode nutzen: [Buttons](/documentation/buttons), die direkt angeklickt werden können. Diese Buttons lassen sich in einem [übersichtlichen Layout](/documentation/custom-layout) anordnen. Hier ist die Vorgehensweise: - **Status in einer separaten Hilfstabelle anlegen**Erstelle zunächst eine Hilfstabelle, in der die verschiedenen Statuswerte in einem Textfeld definiert werden. - **Status auf der Zieltabelle als dynamisches Feld einblenden (Ninox)**
Blende die Statuswerte in der Zieltabelle als dynamisches Feld ein, sodass sie flexibel genutzt werden können. - **Den Anwendungscode auf deiner Oberfläche einfügen**
Füge den erforderlichen Anwendungscode in deine Oberfläche ein. Das kann beispielsweise in einer Custom Table in einer Spalte sein oder aber blank auf deinem Ninox Record. - **Anwendungscode auf deinen Use Case anpassen: Deine eigene Statusliste auswählen**
Wähle die entsprechende Hilfstabelle, die du bei Schritt 1 angelegt hast, bei Blocks unter „select ….“ aus, um sicherzustellen, dass die richtigen Statuswerte verwendet werden. - **Anwendungscode auf deinen Use Case anpassen: Field bei Actions mit der fieldID deines Statusfeldes austauschen**
Tausche in den Actions das Feld mit der fieldID deines Statusfeldes, dass du bei Schritt 2 angelegt hast, aus, damit die Änderungen korrekt übernommen werden.
Mit diesen Schritten kannst du eine intuitive und benutzerfreundliche Oberfläche gestalten, auf der der Status schnell und einfach angepasst werden kann.
## Anwendungscode
```javascript
arcCustomLayout({
uniqueId: "statusTermin " + Nr,
direction: "horizontal",
alignX: "center",
alignY: "center",
width: "100%",
height: "100%",
gap: "10px",
backgroundColor: "",
paddingY: "10px",
paddingX: "10px",
blocks: let id := Nr;
(select 'Status Termin').[{
align: "center",
styles: "",
width: "fraction",
clickAction: "",
value: arcCustomButton({
uniqueId: "Termin_Status_" + id + Nr,
title: Bezeichung,
width: "100%",
icon: "",
fontSize: "",
fontColor: if id.Status = number(Nr) then
"#fff"
else
"#9ba3af"
end,
iconColor: "",
backgroundColor: if id.Status = number(Nr) then
Farbe
else
"#9ba3af33"
end,
borderColor: if id.Status = number(Nr) then
Farbe
else
"#9ba3af33"
end,
borderRadius: "5px",
actions: [{
recordId: id,
type: "update",
field: "Q1",
value: if id.Status = number(Nr) then
null
else
number(Nr)
end
}]
})
}]
})
```
---
# Vertriebs-Cockpit
Source: https://www.arc-rider.com/docs/examples/react-sales-app
# Vertriebs-Cockpit
Opportunities vom Erstgespräch bis zum Abschluss begleiten. Diese vollständige Demo verbindet mehrere echte Widgets mit einem gemeinsamen Datenbestand.
## Ansichten und Workflows
**Übersicht · Pipeline · Opportunities · Aktivitäten**
- Suche und Team-Filter gelten für alle Ansichten. Ein leerer Filter zeigt den echten Leerzustand der Tabelle.
- Klicke eine Zeile, Karte oder einen Kalendertermin an und bearbeite den Eintrag im Detailbereich.
- Navigiere in den Kalenderansichten wochenweise vor und zurück.
- Ändere den Status, den Fortschritt, das Team oder den Termin. Danach bleibt die Änderung beim Wechsel der Ansicht sichtbar.
- Ziehe eine Karte zwischen den Spalten. Status, Tabellenzeile, Detailbereich und Kennzahlen verwenden danach den gleichen aktualisierten Eintrag.
## Live-App und vollständiger Code
Über **Groß öffnen** steht die App mit voller Breite zur Verfügung. **Code** zeigt exakt die ausgeführte Komponente. Du kannst sie nach der [Installation](/docs/react/intro/setup-react) direkt in eine Browser-App übernehmen.
```tsx
import { useMemo, useState } from 'react';
import {
ArcWidgetBadge,
ArcWidgetButton,
ArcWidgetCalendarTimeline,
ArcWidgetCalendarWeek,
ArcWidgetInput,
ArcWidgetKanban,
ArcWidgetKpiBar,
ArcWidgetProgressBar,
ArcWidgetSelect,
ArcWidgetSlider,
ArcWidgetTable,
} from '@arcrider/arcwidgets-react';
// Vollständige Browser-Demo. Alle Ansichten verwenden denselben Datenbestand.
const config = {
kind: 'sales',
title: 'Vertriebs-Cockpit',
description: 'Opportunities vom Erstgespräch bis zum Abschluss begleiten.',
recordLabel: 'Opportunity',
countLabel: 'Opportunities',
searchLabel: 'Opportunities oder Kunden suchen …',
stages: ['Qualifiziert', 'Angebot', 'Gewonnen'],
views: [
{
id: 'overview',
title: 'Übersicht',
},
{
id: 'board',
title: 'Pipeline',
},
{
id: 'table',
title: 'Opportunities',
},
{
id: 'calendar',
title: 'Aktivitäten',
},
],
};
type Item = {
id: string;
title: string;
account: string;
owner: string;
stage: number;
value: number;
progress: number;
day: number;
hour: number;
};
const initial: Item[] = [
{
id: 'V-301',
title: 'Service-Portal',
account: 'Kunde Nord',
owner: 'Team Wachstum',
stage: 1,
value: 18500,
progress: 65,
day: 0,
hour: 9,
},
{
id: 'V-302',
title: 'Digitale Auftragserfassung',
account: 'Kunde West',
owner: 'Team Beratung',
stage: 0,
value: 12400,
progress: 25,
day: 1,
hour: 10,
},
{
id: 'V-303',
title: 'Ressourcen- und Einsatzplan',
account: 'Kunde Süd',
owner: 'Team Wachstum',
stage: 2,
value: 24800,
progress: 100,
day: 0,
hour: 14,
},
{
id: 'V-304',
title: 'Inventarverwaltung',
account: 'Kunde Ost',
owner: 'Team Beratung',
stage: 1,
value: 9600,
progress: 70,
day: 2,
hour: 11,
},
{
id: 'V-305',
title: 'Projekt-Dashboard',
account: 'Kunde Mitte',
owner: 'Team Partnerschaften',
stage: 0,
value: 14800,
progress: 35,
day: 3,
hour: 10,
},
{
id: 'V-306',
title: 'Kunden-Self-Service',
account: 'Kunde Hafen',
owner: 'Team Partnerschaften',
stage: 1,
value: 32000,
progress: 80,
day: 4,
hour: 13,
},
{
id: 'V-307',
title: 'Digitale Abnahme',
account: 'Kunde Park',
owner: 'Team Beratung',
stage: 0,
value: 8400,
progress: 15,
day: 2,
hour: 15,
},
{
id: 'V-308',
title: 'Vertriebsübersicht',
account: 'Kunde Campus',
owner: 'Team Wachstum',
stage: 2,
value: 11600,
progress: 100,
day: 1,
hour: 14,
},
];
const dayDate = (offset: number) => new Date(2026, 8, 14 + offset).getTime();
const money = (value: number) =>
value.toLocaleString('de-DE', {
style: 'currency',
currency: 'EUR',
maximumFractionDigits: 0,
});
const tones = ['#a16207', '#52525b', '#15803d'];
const pill = (stage: number) => (
{'Setze die Suche oder den Team-Filter zurück.'} {config.description} {'Setze die Suche oder den Team-Filter zurück.'} {config.description} {'Setze die Suche oder den Team-Filter zurück.'} {config.description} Bitte prüfen: {name} Erfasst: {name} {error}{config.title}
Interaktive Demo
{config.views.find((tab) => tab.id === view)?.title}
{filtered.length} Einträge
{config.title}
Interaktive Demo
{config.views.find((tab) => tab.id === view)?.title}
{filtered.length} Einträge
{config.title}
Interaktive Demo
{config.views.find((tab) => tab.id === view)?.title}
{filtered.length} Einträge
Parameter
Beschreibung
Default-Wert
`backgroundColor`
Hintergrund der gesamten Komponente
`#F8FAFC`
Parameter
Beschreibung
Default-Wert
`color`
Textfarbe für inaktive Buttons
`#727c90`
`backgroundColor`
Hintergrundfarbe für inaktive Buttons
`#F8FAFC`
Parameter
Beschreibung
Default-Wert
`color`
Textfarbe für aktiven Button
`#ffffff`
`backgroundColor`
Hintergrundfarbe für aktiven Button
`#3b82f6`
Parameter
Beschreibung
Beispiel
`title`
Text, der im Button angezeigt wird.
```javascript
title: "Menupunkt 1",
```
`active`
Bedingung, wann der Button als aktiv dargestellt wird.
`clickAction`
Aktion, die beim Klick auf den Button ausgeführt wird (z. B. Feld aktualisieren).
Widget
Beschreibung
`arcCustomLayout`
Grundstruktur zur Darstellung der Buttons
`arcCustomText` (optional & kostenfrei)
Nur nötig, wenn du Texte innerhalb der Switch-Items selbst gestalten willst
Parameter
Typ
Pflicht
Beschreibung
Beispiel
`id`
Zahl
✅
Eindeutige ID des KPI-Elements. Wird zur Unterscheidung der einzelnen KPIs genutzt.
`1`
`title`
Text
❌
Titel, der oberhalb des Wertes angezeigt wird.
`"Lagerbestand"`
`iconName`
Text
✅
Name des Icons aus der integrierten Icon-Bibliothek.
`"buildings"`
`value`
Text
✅
Der Hauptwert, der in der KPI-Box angezeigt wird.
`text(sum(lager.sum_lagerbestand))`
`subtitle`
Objekt
❌
Zusätzlicher Bereich unterhalb des Hauptwerts mit Detailinformationen. Siehe unten.
Siehe `subtitle`-Struktur
Parameter
Typ
Pflicht
Beschreibung
Beispiel
`color`
Text
❌
Legt die Text- und Icon-Farbe für den Subtitle fest. Unterstützt Hex- oder CSS-Farben.
`"#3b82f6"`
`iconName`
Text
❌
Icon für den Subtitle. Wird links neben dem Wert angezeigt. Wird aus dem aktiven Icon-Set aus dem User Cockpit gezogen.
`"truck"`
`value`
Text
❌
Der zusätzliche Wert, der im Subtitle angezeigt wird.
`cnt(lagerbuchungen[Buchungsart = 1])`
`description`
Text
❌
Eine kurze Beschreibung oder Erläuterung für den Wert.
`"neue Lieferungen"`
Parameter
Typ
Beschreibung
Beispiel
`uniqueId`
Text
Eindeutige ID für die Komponente (für Styling und Wiederverwendung wichtig)
```javascript
uniqueId: "card element Lagerkapazitäten" + Nr,
```
`title`
Text
Überschrift der Karte
```javascript
title: "Lagebestände",
```
`subtitle`
Text
Untertitel oder Zusatzinfo unter dem Titel
```javascript
subtitle: "Lagebestände aller Lager",
```
`switchItems`
Liste
Liste mit den Umschaltoptionen (siehe unten für Details)
```javascript
switchItems: [ {…}, {…} ]
```
Parameter
Typ
Beschreibung
Beispiel
`id`
Zahl o. Text
Eindeutige ID für das Item (kann z. B. für dynamisches Mapping genutzt werden)
```javascript
id: 1,
```
`title`
Text
Titel des Buttons im Switch (z. B. "Bestände")
```javascript
title: "Bestände",
```
`recordId`
ID/Ref
ID des aktuellen Datensatzes, auf den sich die Aktion bezieht
```javascript
recordId: Nr,
```
`fieldId`
Funktion o. Text (ID)
Referenz auf das Feld, das aktualisiert werden soll
```javascript
fieldId: fieldId(Nr, "filter_lagerkapazitaet"),
```
`value`
beliebig
Wert, der gesetzt werden soll, wenn der Button geklickt wird
```javascript
value: 2,
```
`active`
bool./Expr.
Ausdruck, wann das Item aktiv ist
```javascript
active: filter_lagerkapazitaet = "2",
```
`content`
Text
Der eigentliche Inhalt, der angezeigt wird, wenn dieses Item aktiv ist
```javascript
content: 'arcProgressBarComponent _lager'
```
Inhaltstyp
Beschreibung
Beispiel
Widget-Referenz
Aufruf eines anderen Widgets (z. B. eine Fortschrittsanzeige, ein Donut-Chart, ein eigenes Layout, etc.)
```javascript
content: helper_donuts_lager
```
Textausgabe
Einfacher Text (z. B. als Platzhalter oder zur schnellen Anzeige)
Widget
Beschreibung
`arcCustomCard`
Grundstruktur der Karte (Layout, Padding, Hintergrund)
`arcCustomLayout`
Flexible Verschachtelung von Inhalten und Layouts
`arcCustomText` (kostenfrei)
Darstellung von Titel und Untertitel
`arcCustomComponentCyberSwitch` (kostenfrei)
Umschalter zur Auswahl der Inhalte innerhalb der Karte
Kennzahlen
Fortschritt
Aktuell