Prepinac firmy do sidebaru a prepnuti prekresli obsah cely

Dodelavka. Tvrzeni "prepinac plati pro cely system" nebylo pravdive:
useApiQuery se na zmenu firmy zeptat umel, ale komponenty volajici
apiFetch primo - EntityAdmin, a tim cele Nastaveni a zalozky Resitele
a Skupiny v Lidech - o zmene nevedely a zustala na nich data predchozi
firmy. Presne ta vada, kvuli ktere se to delalo, jen o patro vedle.

- obsah dashboardu ma key podle vybrane firmy, takze prepnuti prestavi
  cely strom komponent. Zadny dotaz nemuze zustat stary, protoze zadna
  stara komponenta nezustane. Doplnovat zavislost do kazde komponenty
  zvlast je totez zapomenuti, jen o patro niz.
- prepinac je v sidebaru nad navigaci, ne v hlavicce, a vypada jako
  ovladaci prvek: ram, ikona firmy, sipka. Predchozi verze mela pruhledny
  ram a splyvala s popiskem.
- v hlavicce zustal jen nazev aktivni firmy. Driv tam stalo natvrdo
  tenants[0], takze po prepnuti ukazovala porad prvni firmu ze seznamu.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
JiriUhlir
2026-08-26 08:04:22 +02:00
co-authored by Claude Opus 5
parent 2e0b9456a9
commit 2e863c1f8b
3 changed files with 95 additions and 18 deletions
@@ -1,13 +1,15 @@
import {
AlarmClock,
Blocks,
Building2,
ChevronDown,
ExternalLink,
LayoutDashboard,
LayoutGrid,
LifeBuoy,
MessageSquarePlus,
LogOut,
Menu,
MessageSquarePlus,
MousePointerClick,
Plug,
ScrollText,
@@ -92,27 +94,36 @@ function TenantSwitch({ access }: { access: Access | null }) {
const current = tenants.find((tenant) => tenant.id === activeTenant);
// Vyber z jedne moznosti neni vyber, tak se vykresli jen nazev.
if (tenants.length <= 1) {
return (
<p className="truncate text-sm font-semibold text-white">
<p className="truncate px-2 py-1 text-sm font-semibold text-white">
{current?.name ?? tenants[0]?.name ?? 'Klientský portál'}
</p>
);
}
/*
Vypada jako ovladaci prvek, ne jako popisek. Predchozi verze mela pruhledny
ram a splyvala s textem - prepinac, ktery neni videt, je k nicemu.
*/
return (
<select
value={activeTenant ?? ''}
onChange={(event) => setActiveTenant(event.target.value)}
aria-label="Firma, za kterou pracujete"
className="-ml-2 max-w-full truncate rounded-lg border border-transparent bg-transparent px-2 py-0.5 text-sm font-semibold text-white transition-colors hover:border-ink-600/70 hover:bg-ink-850/70 focus:border-brand-400/70 focus:outline-none"
>
{tenants.map((tenant) => (
<option key={tenant.id} value={tenant.id} className="bg-ink-850">
{tenant.name}
</option>
))}
</select>
<div className="relative flex w-full items-center">
<Building2 className="pointer-events-none absolute left-3 size-4 text-white/45" />
<select
value={activeTenant ?? ''}
onChange={(event) => setActiveTenant(event.target.value)}
aria-label="Firma, za kterou pracujete"
className="w-full appearance-none truncate rounded-xl border border-ink-600/70 bg-ink-850/70 py-2 pr-8 pl-9 text-sm font-semibold text-white transition-colors hover:border-brand-400/50 focus:border-brand-400/70 focus:outline-none"
>
{tenants.map((tenant) => (
<option key={tenant.id} value={tenant.id} className="bg-ink-850">
{tenant.name}
</option>
))}
</select>
<ChevronDown className="pointer-events-none absolute right-3 size-4 text-white/45" />
</div>
);
}
@@ -145,6 +156,8 @@ function DashboardShell() {
setSidebarOpen(false);
}, [location.pathname]);
const activeTenant = useActiveTenant();
async function handleLogout() {
await logout();
navigate('/', { replace: true });
@@ -171,6 +184,15 @@ function DashboardShell() {
</button>
</div>
{/*
Prepinac firmy nad navigaci, tedy nad vsim, co se pod nim kresli.
Vybrana firma plati pro cely portal, ne pro jednu stranku, takze
patri do ramu portalu a ne dovnitr obsahu.
*/}
<div className="border-b border-ink-600/60 p-3">
<TenantSwitch access={access.data} />
</div>
<nav className="flex-1 space-y-1 overflow-y-auto p-3">
{(access.data?.nav ?? []).map((entry) => {
const route = moduleRoutes[entry.key];
@@ -253,7 +275,15 @@ function DashboardShell() {
</button>
<div className="min-w-0 flex-1">
<TenantSwitch access={access.data} />
{/*
Jen nazev, prepinac je v sidebaru. Driv tu stalo natvrdo
`tenants[0]`, takze po prepnuti ukazovalo porad prvni firmu
ze seznamu bez ohledu na vyber.
*/}
<p className="truncate text-sm font-semibold text-white">
{access.data?.tenants.find((tenant) => tenant.id === activeTenant)?.name ??
'Klientský portál'}
</p>
<p className="truncate text-xs text-white/40">{accessLabel(user, access.data)}</p>
</div>
@@ -270,7 +300,17 @@ function DashboardShell() {
</div>
</header>
<main className="px-5 py-7 sm:px-7">
{/*
Prepnuti firmy prekresli obsah **cely**, ne jen to, co si o zmene
reklo. `useApiQuery` se sice zeptat umi, ale komponenty, ktere volaji
`apiFetch` primo (EntityAdmin, a tim cele Nastaveni), o zmene nevi -
zustala by na nich data predchozi firmy. Klic to resi jednou provzdy:
jina firma znamena novy strom, takze zadny dotaz nemuze zustat stary.
Ztrata rozepsaneho stavu je pritom spravne. Filtr na cloveka z firmy A
nema ve firme B co znamenat.
*/}
<main key={activeTenant ?? 'bez-firmy'} className="px-5 py-7 sm:px-7">
<Outlet />
</main>
</div>