feat: Kontakt-Ordner mit Drag&Drop Baum (wie KI-Chat Sidebar)
- Backend: ContactFolder model (hierarchisch, parent_id, sort_order)
- Migration 0022: contact_folders Tabelle + folder_id FK auf contacts
- CRUD Routes: /api/v1/contact-folders (list, create, update, delete, reorder)
- Move Contact API: /api/v1/contact-folders/contacts/{id}/move
- folder_id Filter in contacts list API
- Frontend: ContactFolderTree mit Baum-Struktur, Drag&Drop, Kontext-Menü
- Kontakt-Personen-Icon statt Ordner-Symbol
- ContactList Items draggable (List/Table/Cards View)
- React Query Hooks für Folder CRUD + Move Contact
- Alle 266 Frontend-Tests passing
This commit is contained in:
@@ -669,6 +669,7 @@ export interface UnifiedContact {
|
||||
tags?: string | null;
|
||||
image?: string | null;
|
||||
custom?: Record<string, any> | null;
|
||||
folder_id?: string | null;
|
||||
default_person_id?: string | null;
|
||||
admin_contactperson_id?: string | null;
|
||||
contact_persons?: ContactPerson[];
|
||||
@@ -683,14 +684,16 @@ export function useUnifiedContacts(
|
||||
contactType?: string,
|
||||
sortBy?: string,
|
||||
sortOrder?: string,
|
||||
folderId?: string,
|
||||
) {
|
||||
const params = new URLSearchParams({ page: String(page), page_size: String(pageSize) });
|
||||
if (search) params.set('search', search);
|
||||
if (contactType) params.set('type', contactType);
|
||||
if (sortBy) params.set('sort_by', sortBy);
|
||||
if (sortOrder) params.set('sort_order', sortOrder);
|
||||
if (folderId) params.set('folder_id', folderId);
|
||||
return useQuery({
|
||||
queryKey: ['unifiedContacts', page, pageSize, search, contactType, sortBy, sortOrder],
|
||||
queryKey: ['unifiedContacts', page, pageSize, search, contactType, sortBy, sortOrder, folderId],
|
||||
queryFn: () =>
|
||||
apiGet<PaginatedResponse<UnifiedContact>>(`/contacts?${params.toString()}`),
|
||||
});
|
||||
@@ -1240,3 +1243,55 @@ export function useUserGroups(userId: string | null) {
|
||||
enabled: !!userId,
|
||||
});
|
||||
}
|
||||
|
||||
// ── Contact Folders ──
|
||||
|
||||
export function useContactFolders() {
|
||||
return useQuery({
|
||||
queryKey: ['contactFolders'],
|
||||
queryFn: () => apiGet<import('./contactFolders').ContactFolder[]>('/contact-folders'),
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateContactFolder() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (data: { name: string; parent_id?: string }) => apiPost('/contact-folders', data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['contactFolders'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateContactFolder() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, data }: { id: string; data: { name?: string; parent_id?: string | null; sort_order?: number } }) =>
|
||||
apiPut(`/contact-folders/${id}`, data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['contactFolders'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteContactFolder() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => apiDelete(`/contact-folders/${id}`),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['contactFolders'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useMoveContactToFolder() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ contactId, folderId }: { contactId: string; folderId: string | null }) =>
|
||||
apiPut(`/contact-folders/contacts/${contactId}/move`, { folder_id: folderId }),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['unifiedContacts'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['contactFolders'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user