diff --git a/backend/api/v1/documents/serializers.py b/backend/api/v1/documents/serializers.py index 9f65a9c4..6ad2e103 100644 --- a/backend/api/v1/documents/serializers.py +++ b/backend/api/v1/documents/serializers.py @@ -115,6 +115,7 @@ class FolderSerializer(serializers.Serializer): name = serializers.CharField() parent_id = serializers.IntegerField(source='parent.id', read_only=True, allow_null=True) path = serializers.SerializerMethodField() + document_count = serializers.IntegerField(read_only=True, default=0) def get_path(self, obj) -> str: """Возвращает полный путь папки.""" diff --git a/backend/api/v1/documents/views.py b/backend/api/v1/documents/views.py index 5d716dc5..1225a742 100644 --- a/backend/api/v1/documents/views.py +++ b/backend/api/v1/documents/views.py @@ -349,9 +349,14 @@ class FolderViewSet(ModelViewSet): ?parent_id= - показать только дочерние папки указанного parent ?root=true - показать только корневые папки (parent=null) """ + from django.db.models import Count + qs = super().get_queryset() request = getattr(self, "request", None) + # Аннотируем количество документов в каждой папке + qs = qs.annotate(document_count=Count('documents')) + if not request: return qs diff --git a/frontend/src/app/documents/page.tsx b/frontend/src/app/documents/page.tsx index a02ed3ff..7e7194be 100644 --- a/frontend/src/app/documents/page.tsx +++ b/frontend/src/app/documents/page.tsx @@ -151,6 +151,29 @@ export default function DocumentsPage() { }); }, [documents, search]); + // Find selected folder and build breadcrumb path + const selectedFolder = useMemo(() => { + if (!selectedFolderId) return null; + + const findFolder = (foldersArray: FolderNode[]): FolderNode | null => { + for (const folder of foldersArray) { + if (folder.id === selectedFolderId) return folder; + if (folder.children) { + const found = findFolder(folder.children); + if (found) return found; + } + } + return null; + }; + + return findFolder(folders); + }, [selectedFolderId, folders]); + + const breadcrumbs = useMemo(() => { + if (!selectedFolder) return []; + return selectedFolder.path.split(' / '); + }, [selectedFolder]); + // Mock dashboard stats const dashboardStats = useMemo(() => { const byStatus = documents.reduce((acc: any[], doc) => { @@ -211,10 +234,28 @@ export default function DocumentsPage() {
+ )} {showFolderDropdown && (
@@ -384,6 +425,29 @@ export default function DocumentsPage() {
)} + {/* Breadcrumbs */} + {breadcrumbs.length > 0 && ( +
+ + +
+ )} + {/* Documents List */}
@@ -473,6 +537,12 @@ export default function DocumentsPage() {
+ {doc.folder_path && ( +

+ + {doc.folder_path} +

+ )}

Автор: {authorName}

Создано: {formatDate(doc.created_at)}

Обновлено: {formatDate(doc.updated_at)}

diff --git a/frontend/src/components/documents/folders/FolderTree.tsx b/frontend/src/components/documents/folders/FolderTree.tsx index df6c72af..6618f835 100644 --- a/frontend/src/components/documents/folders/FolderTree.tsx +++ b/frontend/src/components/documents/folders/FolderTree.tsx @@ -8,6 +8,7 @@ export interface FolderNode { name: string; parent_id: number | null; path: string; + document_count?: number; children?: FolderNode[]; } @@ -116,6 +117,11 @@ export function FolderTree({ )} {node.name} + {node.document_count !== undefined && node.document_count > 0 && ( + + {node.document_count} + + )} {/* Actions */} diff --git a/frontend/src/types/api.ts b/frontend/src/types/api.ts index d8902bc8..bfed656f 100644 --- a/frontend/src/types/api.ts +++ b/frontend/src/types/api.ts @@ -121,6 +121,11 @@ export interface Document { file_url?: string; file_name?: string; file_size?: number; + folder?: { + id: number; + name: string; + }; + folder_path?: string; status: string; // human-readable: "Черновик", "На рассмотрении", etc. status_code: DocumentStatus; // machine-readable: "draft", "in_review", etc. created_by: User;