Mandanten (Tenant)
Jeder ActaNova-Aufruf ist mandantenbezogen: die Services setzen den Tenant in
den Pfad (/Tenants/Items/<tenant>/…). Damit du ihn nicht durch die halbe App
durchreichen musst, hält die Library ihn in einem Context — pro App, nicht
cockpit-weit.
Deklaration an der App
Welche Mandanten eine App bedient, steht in ihrer Definition:
const apps: CockpitApp[] = [
{ id: 'akten', title: 'Akten', description: '…', tenant: ['Stadt'], component: AktenApp },
{ id: 'anträge', title: 'Anträge', description: '…', tenant: ['Stadt', 'Bezirk'], component: AntraegeApp },
];
Daraus ergibt sich das Verhalten beim Öffnen der App:
tenant | Verhalten |
|---|---|
| genau ein Eintrag | Dieser Mandant ist aktiv. Der User sieht keine Auswahl. |
| mehrere Einträge | Es erscheint ein Auswahl-Gate vor der App („Mandant wählen"). Die Wahl wird gemerkt und beim nächsten Mal übersprungen. |
| leeres Array | Fehlkonfiguration: die Library wirft App "<id>" hat keinen Tenant definiert (CockpitApp.tenant ist leer). |
Gemerkt wird die Wahl im localStorage unter cockpit:tenant:<appId> — also
getrennt pro App. Ein gespeicherter Wert wird nur übernommen, wenn er noch in
tenant steht; entfernst du einen Mandanten aus der App-Definition, fragt das
Gate erneut.
Den aktiven Mandanten lesen
import { useTenant } from '@gentics/cockpit/core';
function Kopfzeile() {
const tenant = useTenant(); // z. B. "Stadt"
return <span>Mandant: {tenant}</span>;
}
useTenant() muss innerhalb einer App laufen.
Außerhalb von React — in Services, Hilfsfunktionen, Event-Handlern ohne
Component-Kontext — gibt es getActiveTenant(). Es liefert denselben Wert,
wirft aber nicht, sondern gibt null zurück, wenn gerade keine App läuft:
import { getActiveTenant } from '@gentics/cockpit/core';
const tenant = getActiveTenant(); // "Stadt" oder null
Darauf baut auch makeCall
auf: Service-Aufrufe brauchen den Tenant deshalb nicht als Parameter.
Einen Umschalter bauen
Für Apps mit mehreren Mandanten liefert useTenantSelection() alles, was ein
eigener Umschalter braucht:
import { useTenantSelection } from '@gentics/cockpit/core';
function MandantWechseln() {
const { tenant, available, setTenant } = useTenantSelection();
if (available.length < 2) return null;
return (
<select value={tenant} onChange={(e) => setTenant(e.target.value)}>
{available.map((t) => (
<option key={t} value={t}>{t}</option>
))}
</select>
);
}
setTenant ignoriert Werte, die nicht in available stehen, und schreibt die
neue Wahl in den localStorage.
Der Wechsel wirkt sofort: der Tenant steckt in den Query-Keys aller Store-Hooks
(["files", tenant], ["file", tenant, id], …). React Query lädt damit die
Daten des neuen Mandanten und hält die des alten weiter im Cache.
Einzelne Aufrufe auf einen anderen Mandanten richten
Jeder Store-Hook nimmt optional einen Tenant. Explizit übergeben schlägt den Context:
useFiles(); // aktiver Mandant aus dem Context
useFiles('Bezirk'); // gezielt dieser Mandant
useFile(id);
useFile(id, 'Bezirk');
usePersons({ enabled: true, tenant: 'Bezirk' });
usePerson(id, { tenant: 'Bezirk' });
useDocumentsByFile(fileId, { enabled: true }, 'Bezirk');
Fehlt beides — kein Parameter und kein Context —, wirft der Hook:
Kein Tenant verfügbar: weder als Parameter übergeben noch aus dem Context.
Hook innerhalb einer App (TenantProvider) aufrufen oder tenant explizit übergeben.
Das ist der typische Fehler, wenn ein Store-Hook außerhalb einer App verwendet wird. Der Weg daraus ist immer einer der beiden im Text genannten.
Nicht mandantenbezogen sind die /Me-Aufrufe: useCurrentUser() nimmt deshalb
keinen Tenant-Parameter und funktioniert auch außerhalb einer App.
Was der Mandant nicht ist
- Kein URL-Bestandteil. Der aktive Mandant steht nicht im Pfad — ein Link auf
/akten/<id>zeigt beim Empfänger den Datensatz seines gemerkten Mandanten. - Keine Berechtigung. Dass eine App einen Mandanten anbietet, heißt nicht, dass der angemeldete User dort etwas sehen darf; das entscheidet ActaNova. Siehe Auth & Rechte.
- Nicht cockpit-global. Zwei Apps können gleichzeitig unterschiedliche Mandanten aktiv haben. Beim Wechsel der App wird der Context neu aufgebaut.
