diff --git a/documentation/07-firmy-a-prava.md b/documentation/07-firmy-a-prava.md
index b5cbfdd..b7b5164 100644
--- a/documentation/07-firmy-a-prava.md
+++ b/documentation/07-firmy-a-prava.md
@@ -45,6 +45,30 @@ Posilaji se jako query: `?scope=tenant&tenantId=tnt_automia`.
U `tenant` a `mine` je potreba vedet **kterou** firmu, protoze uzivatel jich
muze mit vic. Bez `tenantId` se pouzije prvni.
+## Prepinac firmy je nad celym portalem
+
+Vybrana firma je **jedna hodnota pro cely dashboard** (`web/src/lib/tenant.ts`),
+ne stav jedne stranky. Prepinac je v hlavicce nad obsahem a `apiFetch` doplnuje
+`tenantId` do kazdeho dotazu na `/api/dashboard`.
+
+Doplnuje se to v API vrstve schvalne, ne na strankach. Kdyz si to mela pridavat
+kazda stranka sama, vetsina na to zapomnela a server sahnul po vychozi firme -
+uzivatel se prepnul na LogiTrans a v Lidech koukal na lidi Automie. To neni
+nepohodli, to jsou cizi data pod hlavickou jine firmy.
+
+Vyjimky, kdy se nedoplnuje:
+
+| Kdy | Proc |
+| --------------------------- | -------------------------------------------------------- |
+| Cesta uz `tenantId` nese | proklik z widgetu na konkretni firmu ma prednost |
+| `scope=all` | pohled pres vsechny firmy, jedna firma tam nic neznamena |
+| Neni vybrano | server pouzije vychozi firmu uctu |
+| Cesta mimo `/api/dashboard` | prihlaseni a verejny web s firmou nepracuji |
+
+Volba prezije obnoveni stranky (localStorage). Kdyz ulozena firma uz mezi
+`access.tenants` neni - clovek z ni odesel, prihlasil se nekdo jiny na tomtez
+pocitaci - spadne se na vychozi firmu uctu, aby kazdy dotaz nekoncil na 403.
+
## Server je autorita
Vsechno rozhoduje `src/data/access.ts`, jedno misto pro cely portal. Kdyby se to
@@ -125,6 +149,13 @@ podle toho, ktera firma je prave zvolena.
## Co chybi
+**Prava se scitaji pres vsechny firmy, neprepocitavaji se podle vybrane.**
+`permissionsOf(user)` vezme role ze vsech clenstvi dohromady. Kdo je spravce
+v jedne firme a resitel v druhe, ma prava spravce i po prepnuti do te druhe.
+Data se tim neprolomi - server je porad filtruje podle firmy - ale tlacitka
+ukazuji vic, nez by mela. Napravit to znamena predat firmu do `permissionsOf`
+a prepocitat cache za dvojici uzivatel a firma.
+
| Chybi | Poznamka |
| ------------------------- | ------------------------------------------------ |
| Sprava clenstvi z portalu | memberships jdou zmenit jen v kodu |
diff --git a/documentation/99-zmeny.md b/documentation/99-zmeny.md
index 1c8a90d..e21f339 100644
--- a/documentation/99-zmeny.md
+++ b/documentation/99-zmeny.md
@@ -2,6 +2,37 @@
Nejnovejsi nahore.
+## 2026-08-26 - prepinac firmy plati pro cely portal
+
+Prepinac byl stav uvnitr stranky Prehled. Prepnuti na LogiTrans zmenilo prehled
+a nic jineho: ostatni stranky volaly API bez `tenantId` a server sahnul po
+vychozi firme, takze v Lidech zustali lide Automie. To neni nepohodli, to jsou
+cizi data pod hlavickou jine firmy.
+
+### Zmeneno
+
+- **Vybrana firma je jedna hodnota pro cely dashboard**
+ (`web/src/lib/tenant.ts`), ne stav stranky. Prezije obnoveni stranky.
+- **`tenantId` doplnuje `apiFetch`**, ne jednotlive stranky. Kdyz si to mela
+ pridavat kazda stranka sama, vetsina na to zapomnela - a zapomnetlivost byla
+ prave ta chyba. Nedoplnuje se, kdyz cesta uz firmu nese (proklik z widgetu),
+ u `scope=all` a mimo `/api/dashboard`.
+- **`useApiQuery` se pri prepnuti zepta znovu.** Bez toho by na strance zustala
+ cisla predchozi firmy, dokud by ji nekdo neobnovil.
+- **Prepinac je v hlavicce nad obsahem.** Driv byl v Prehledu, ted plati
+ viditelne pro vsechno. Nahradil i text, ktery ukazoval natvrdo prvni firmu
+ ze seznamu bez ohledu na to, co bylo vybrane.
+- **Ulozena firma, do ktere uzivatel uz nepatri, spadne na vychozi.** Jinak by
+ po odchodu z firmy koncil kazdy dotaz na 403.
+
+### Zjisteno u toho
+
+- **Prava se scitaji pres vsechny firmy**, neprepocitavaji se podle vybrane.
+ Kdo je spravce v jedne firme a resitel v druhe, ma po prepnuti porad prava
+ spravce. Data se tim neprolomi, server je filtruje dal podle firmy, ale
+ tlacitka ukazuji vic, nez by mela. Zapsane v
+ [07-firmy-a-prava.md](07-firmy-a-prava.md), sekce Co chybi.
+
## 2026-08-26 - helpdesk: pozadavek, ktery vidi zadavatel i resitel
Zakaznik nemel jak poslat pozadavek. Ticket pritom patri jedne firme, kdezto
diff --git a/web/src/components/dashboard/DashboardLayout.tsx b/web/src/components/dashboard/DashboardLayout.tsx
index 41b47ef..72f7c28 100644
--- a/web/src/components/dashboard/DashboardLayout.tsx
+++ b/web/src/components/dashboard/DashboardLayout.tsx
@@ -25,6 +25,7 @@ import { EventToasts } from '@/components/dashboard/EventToasts';
import { LiveIndicator } from '@/components/dashboard/LiveIndicator';
import { Logo } from '@/components/layout/Logo';
import { cn } from '@/lib/cn';
+import { setActiveTenant, useActiveTenant } from '@/lib/tenant';
import { useApiQuery } from '@/lib/useApiQuery';
import type { Access } from '@/types/dashboard';
@@ -64,6 +65,57 @@ export function DashboardLayout() {
}
/** Kratky popis pristupu do topbaru. Uzivatel musi poznat, co vlastne vidi. */
+/**
+ * Prepinac firmy.
+ *
+ * Stoji **nad celym dashboardem**, ne uvnitr jedne stranky. Driv byl v Prehledu
+ * a tykal se jen jeho: uzivatel se prepnul na jinou firmu a v Lidech dal koukal
+ * na lidi te puvodni. Vybrana firma je proto jedna hodnota pro cely portal
+ * a `apiFetch` ji dava do kazdeho dotazu.
+ *
+ * Kdo ma jednu firmu, vidi jen jeji nazev - vyber z jedne moznosti neni vyber.
+ */
+function TenantSwitch({ access }: { access: Access | null }) {
+ const activeTenant = useActiveTenant();
+ const tenants = access?.tenants ?? [];
+
+ /*
+ * Dorovnani po nacteni. Ulozena volba muze byt firma, do ktere uz uzivatel
+ * nepatri (odebrali ho, prihlasil se nekdo jiny na tomtez pocitaci) - pak by
+ * kazdy dotaz koncil na 403. Radsi se spadne na vychozi firmu uctu.
+ */
+ useEffect(() => {
+ if (tenants.length === 0) return;
+ if (activeTenant && tenants.some((tenant) => tenant.id === activeTenant)) return;
+ setActiveTenant(access?.defaultTenantId ?? tenants[0]?.id ?? null);
+ }, [tenants, activeTenant, access?.defaultTenantId]);
+
+ const current = tenants.find((tenant) => tenant.id === activeTenant);
+
+ if (tenants.length <= 1) {
+ return (
+