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:
Agent Zero
2026-07-20 01:33:44 +02:00
parent 4bc11efc25
commit 29202325a6
17 changed files with 942 additions and 8 deletions
+4
View File
@@ -141,6 +141,8 @@ class ContactCreate(BaseModel):
image: str | None = None
# Custom
custom: dict | None = None
# Folder assignment
folder_id: str | None = None
# Contact persons (optional inline create)
contact_persons: list[ContactPersonCreate] | None = None
@@ -208,6 +210,7 @@ class ContactUpdate(BaseModel):
tags: str | None = Field(None, max_length=500)
image: str | None = None
custom: dict | None = None
folder_id: str | None = None
default_person_id: str | None = None
admin_contactperson_id: str | None = None
@@ -278,6 +281,7 @@ class ContactResponse(BaseModel):
tags: str | None = None
image: str | None = None
custom: dict | None = None
folder_id: str | None = None
default_person_id: str | None = None
admin_contactperson_id: str | None = None
created_at: str | None = None
+33
View File
@@ -0,0 +1,33 @@
"""Contact folder schemas — CRUD for hierarchical contact folders."""
from __future__ import annotations
from pydantic import BaseModel, Field
class ContactFolderCreate(BaseModel):
name: str = Field(..., min_length=1, max_length=255)
parent_id: str | None = None
class ContactFolderUpdate(BaseModel):
name: str | None = Field(None, min_length=1, max_length=255)
parent_id: str | None = None
sort_order: int | None = None
class ContactFolderResponse(BaseModel):
id: str
name: str
parent_id: str | None = None
user_id: str
sort_order: int = 0
class ContactFolderTreeResponse(ContactFolderResponse):
children: list["ContactFolderTreeResponse"] = Field(default_factory=list)
contact_count: int = 0
class MoveContactRequest(BaseModel):
folder_id: str | None = None