feat(tenants): UI fuer Mandanten-Verwaltung — Modul 4/16 des UI-Backlogs
Backend existierte vollstaendig (list, create, list users, assign user; tenants:read/write), Frontend hatte 0% Abdeckung. - api/tenants.ts: TanStack-Hooks (useTenants, useTenantUsers mit enabled-Gating, create, assignUser mit Cache-Invalidierung) - pages/Tenants.tsx: Tenant-Karten (Name, Slug, Standard-Badge), expandierbare User-Liste pro Tenant (Rolle, E-Mail), Create-Dialog (Name + Slug mit Auto-Normalisierung), Assign-User-Dialog mit User-Picker (bereits zugewiesene gefiltert) — Create/Users/Assign hinter tenants:write gegated - Platzierung: Settings-Subpage /settings/tenants (statisch, Core-Route) + Settings-Nav-Item - i18n tenants.* de/en Verifikation: Vitest 8/8 (Rendering, Standard-Badge, Expand-User-Liste, Create-Flow, Assign-Flow, Permission-Gating, Empty/Error) · tsc exit 0 · production build exit 0.
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Tenants API client — multi-tenant management.
|
||||
*
|
||||
* Backend: /api/v1/tenants (list, create, list users, assign user).
|
||||
* Permissions: tenants:read (list) / tenants:write (create, users, assign).
|
||||
*/
|
||||
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { apiGet, apiPost } from '@/api/client';
|
||||
|
||||
export interface Tenant {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
is_default: boolean;
|
||||
}
|
||||
|
||||
export interface TenantListResponse {
|
||||
items: Tenant[];
|
||||
}
|
||||
|
||||
export interface TenantUser {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
role: string;
|
||||
is_active: boolean;
|
||||
}
|
||||
|
||||
export interface TenantUsersResponse {
|
||||
items: TenantUser[];
|
||||
}
|
||||
|
||||
export interface TenantCreatePayload {
|
||||
name: string;
|
||||
slug: string;
|
||||
}
|
||||
|
||||
// ─── Query hooks ─────────────────────────────────────────────
|
||||
|
||||
export function useTenants() {
|
||||
return useQuery<TenantListResponse>({
|
||||
queryKey: ['tenants'],
|
||||
queryFn: () => apiGet<TenantListResponse>('/tenants'),
|
||||
});
|
||||
}
|
||||
|
||||
export function useTenantUsers(tenantId: string | null) {
|
||||
return useQuery<TenantUsersResponse>({
|
||||
queryKey: ['tenants', tenantId, 'users'],
|
||||
queryFn: () => apiGet<TenantUsersResponse>(`/tenants/${tenantId}/users`),
|
||||
enabled: !!tenantId,
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Mutation hooks ──────────────────────────────────────────
|
||||
|
||||
export function useCreateTenant() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation<
|
||||
{ id: string; name: string; slug: string },
|
||||
Error,
|
||||
TenantCreatePayload
|
||||
>({
|
||||
mutationFn: (data) => apiPost('/tenants', data),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ['tenants'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useAssignUserToTenant() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation<
|
||||
{ message: string },
|
||||
Error,
|
||||
{ tenantId: string; userId: string }
|
||||
>({
|
||||
mutationFn: ({ tenantId, userId }) =>
|
||||
apiPost(`/tenants/${tenantId}/users`, { user_id: userId }),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ['tenants'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user