feat(templates): UI fuer Berechtigungs-Vorlagen — Modul 6/16 des UI-Backlogs
Backend existierte vollstaendig (list mit entity_type-Filter, create, update, delete, apply — templates:read/write, seit Audit-Fix im Katalog), Frontend hatte 0% Abdeckung. - api/permissionTemplates.ts: TanStack-Hooks (usePermissionTemplates, create/update/delete/apply mit Cache-Invalidierung, TemplateLevel-Typ) - pages/PermissionTemplates.tsx: Template-Karten (Name, Level-Badge, Entity-Type, Auto-Share-Zusammenfassung), Create/Edit-Dialog mit Level-Select und JSON-Textarea inkl. Array-Validierung mit Fehlertext, Apply-Dialog (Entity-Type vorbelegt, Entity-ID), Ergebnis-Banner mit Anzahl erstellter Berechtigungen, Delete mit Confirm — alle Aktionen hinter templates:write gegated - Platzierung: Settings-Subpage /settings/permission-templates (statisch, Core-Route) + Settings-Nav-Item - i18n permissionTemplates.* de/en Verifikation: Vitest 10/10 (Rendering, Level-Badges, Create mit validem + invalidem JSON, Edit prefilled, Apply mit Ergebnis, Delete-Confirm, Permission-Gating, Empty/Error) · tsc exit 0 · production build exit 0.
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Permission Templates API client — reusable permission presets.
|
||||
*
|
||||
* Backend: /api/v1/permission-templates (list, create, update, delete,
|
||||
* apply). Templates define default sharing rules; applying them creates
|
||||
* entity_permissions entries automatically.
|
||||
* Permissions: templates:read (list) / templates:write (everything else).
|
||||
*/
|
||||
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { apiGet, apiPost, apiPut, apiDelete } from '@/api/client';
|
||||
|
||||
export type TemplateLevel = 'read' | 'write' | 'admin' | 'delete';
|
||||
|
||||
export interface ShareEntry {
|
||||
principal_type?: string;
|
||||
principal_id?: string;
|
||||
level?: string;
|
||||
}
|
||||
|
||||
export interface PermissionTemplate {
|
||||
id: string;
|
||||
name: string;
|
||||
entity_type: string;
|
||||
trigger_condition: Record<string, unknown> | null;
|
||||
auto_share_with: ShareEntry[] | null;
|
||||
level: TemplateLevel;
|
||||
tenant_id: string;
|
||||
created_at: string | null;
|
||||
updated_at: string | null;
|
||||
}
|
||||
|
||||
export interface TemplateListResponse {
|
||||
items: PermissionTemplate[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface TemplateCreatePayload {
|
||||
name: string;
|
||||
entity_type: string;
|
||||
level?: TemplateLevel;
|
||||
trigger_condition?: Record<string, unknown> | null;
|
||||
auto_share_with?: ShareEntry[] | null;
|
||||
}
|
||||
|
||||
export type TemplateUpdatePayload = Partial<TemplateCreatePayload>;
|
||||
|
||||
export interface TemplateApplyPayload {
|
||||
entity_type: string;
|
||||
entity_id: string;
|
||||
template_id?: string | null;
|
||||
}
|
||||
|
||||
export interface TemplateApplyResponse {
|
||||
applied: Array<Record<string, unknown>>;
|
||||
count: number;
|
||||
}
|
||||
|
||||
// ─── Query hooks ─────────────────────────────────────────────
|
||||
|
||||
export function usePermissionTemplates(entityType?: string) {
|
||||
const qs = entityType ? `?entity_type=${encodeURIComponent(entityType)}` : '';
|
||||
return useQuery<TemplateListResponse>({
|
||||
queryKey: ['permission-templates', entityType ?? null],
|
||||
queryFn: () => apiGet<TemplateListResponse>(`/permission-templates${qs}`),
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Mutation hooks ──────────────────────────────────────────
|
||||
|
||||
function useInvalidateTemplates() {
|
||||
const qc = useQueryClient();
|
||||
return () => {
|
||||
qc.invalidateQueries({ queryKey: ['permission-templates'] });
|
||||
};
|
||||
}
|
||||
|
||||
export function useCreatePermissionTemplate() {
|
||||
const invalidate = useInvalidateTemplates();
|
||||
return useMutation<PermissionTemplate, Error, TemplateCreatePayload>({
|
||||
mutationFn: (data) => apiPost<PermissionTemplate>('/permission-templates', data),
|
||||
onSuccess: invalidate,
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdatePermissionTemplate() {
|
||||
const invalidate = useInvalidateTemplates();
|
||||
return useMutation<
|
||||
PermissionTemplate,
|
||||
Error,
|
||||
{ templateId: string; payload: TemplateUpdatePayload }
|
||||
>({
|
||||
mutationFn: ({ templateId, payload }) =>
|
||||
apiPut<PermissionTemplate>(`/permission-templates/${templateId}`, payload),
|
||||
onSuccess: invalidate,
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeletePermissionTemplate() {
|
||||
const invalidate = useInvalidateTemplates();
|
||||
return useMutation<void, Error, string>({
|
||||
mutationFn: (templateId) => apiDelete(`/permission-templates/${templateId}`),
|
||||
onSuccess: invalidate,
|
||||
});
|
||||
}
|
||||
|
||||
export function useApplyPermissionTemplate() {
|
||||
return useMutation<TemplateApplyResponse, Error, TemplateApplyPayload>({
|
||||
mutationFn: (data) => apiPost<TemplateApplyResponse>('/permission-templates/apply', data),
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user