102 lines
3.6 KiB
TypeScript
102 lines
3.6 KiB
TypeScript
'use client';
|
|
|
|
import * as Select from '@radix-ui/react-select';
|
|
import { useCallback, useEffect, useState } from 'react';
|
|
import { iamTenant } from '@/lib/api/iam';
|
|
import type { IamTenant } from '@/lib/api/types/tenant';
|
|
import { useTenantStore } from '@/stores/tenant-store';
|
|
import { useTabStore } from '@/stores/tab-store';
|
|
|
|
/** Radix Select 不允许空字符串作 value,用哨兵表示「默认租户」 */
|
|
const DEFAULT_TENANT_VALUE = '__tenant_default__';
|
|
|
|
export function TenantSwitcher() {
|
|
const tenantId = useTenantStore((s) => s.tenantId);
|
|
const setTenantId = useTenantStore((s) => s.setTenantId);
|
|
const resetTabsForTenantSwitch = useTabStore((s) => s.resetForTenantSwitch);
|
|
const [rows, setRows] = useState<IamTenant[] | null>(null);
|
|
const [loadErr, setLoadErr] = useState(false);
|
|
|
|
const load = useCallback(async () => {
|
|
try {
|
|
const data = await iamTenant.list();
|
|
setRows(data.items ?? []);
|
|
setLoadErr(false);
|
|
} catch {
|
|
setLoadErr(true);
|
|
setRows([]);
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
void load();
|
|
}, [load]);
|
|
|
|
const short = tenantId ? (tenantId.length > 10 ? `${tenantId.slice(0, 8)}…` : tenantId) : '默认';
|
|
|
|
if (loadErr || !rows || rows.length === 0) {
|
|
return (
|
|
<div
|
|
className="hidden max-w-[160px] truncate text-xs text-neutral-500 md:block"
|
|
title={tenantId ?? ''}
|
|
>
|
|
租户 {short}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="hidden items-center gap-1 md:flex">
|
|
<span className="text-xs text-neutral-500" id="tenant-switcher-label">
|
|
租户
|
|
</span>
|
|
<Select.Root
|
|
value={tenantId ?? DEFAULT_TENANT_VALUE}
|
|
onValueChange={(v) => {
|
|
setTenantId(v === DEFAULT_TENANT_VALUE ? null : v);
|
|
resetTabsForTenantSwitch();
|
|
}}
|
|
>
|
|
<Select.Trigger
|
|
className="inline-flex max-w-[140px] items-center justify-between gap-1 truncate rounded border border-neutral-200 bg-white px-1.5 py-0.5 text-xs text-neutral-800 outline-none hover:bg-neutral-50 focus-visible:ring-2 focus-visible:ring-neutral-400"
|
|
aria-labelledby="tenant-switcher-label"
|
|
title={tenantId ?? '默认'}
|
|
>
|
|
<Select.Value placeholder="默认" />
|
|
<Select.Icon aria-hidden>
|
|
<span className="text-[10px] text-neutral-500">▾</span>
|
|
</Select.Icon>
|
|
</Select.Trigger>
|
|
<Select.Portal>
|
|
<Select.Content
|
|
className="z-[200] max-h-[min(320px,70vh)] overflow-hidden rounded-md border border-neutral-200 bg-white shadow-lg"
|
|
position="popper"
|
|
sideOffset={4}
|
|
align="start"
|
|
>
|
|
<Select.Viewport className="p-1">
|
|
<Select.Item
|
|
value={DEFAULT_TENANT_VALUE}
|
|
className="relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-xs text-neutral-800 outline-none data-highlighted:bg-neutral-100"
|
|
>
|
|
<Select.ItemText>默认</Select.ItemText>
|
|
</Select.Item>
|
|
{rows.map((r) => (
|
|
<Select.Item
|
|
key={r.id}
|
|
value={r.id}
|
|
className="relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-xs text-neutral-800 outline-none data-highlighted:bg-neutral-100"
|
|
>
|
|
<Select.ItemText className="truncate">
|
|
{r.tenant_name || r.tenant_code || r.id}
|
|
</Select.ItemText>
|
|
</Select.Item>
|
|
))}
|
|
</Select.Viewport>
|
|
</Select.Content>
|
|
</Select.Portal>
|
|
</Select.Root>
|
|
</div>
|
|
);
|
|
}
|