+
- {fileName || "Документ"}
+ {fileName || "Документ"}
{/* Поиск */}
diff --git a/frontend/src/components/ui/Modal.tsx b/frontend/src/components/ui/Modal.tsx
index 1f5702dd..0cd9405c 100644
--- a/frontend/src/components/ui/Modal.tsx
+++ b/frontend/src/components/ui/Modal.tsx
@@ -1,19 +1,18 @@
"use client";
import { useEffect, useRef, type ReactNode } from "react";
-import { X, Maximize2, Minimize2 } from "lucide-react";
+import { X } from "lucide-react";
import { useState } from "react";
export interface ModalProps {
isOpen: boolean;
onClose: () => void;
title?: string;
- children: ReactNode | ((isFullscreen: boolean) => ReactNode);
+ children: ReactNode;
size?: "sm" | "md" | "lg" | "xl" | "full";
showCloseButton?: boolean;
closeOnClickOutside?: boolean;
closeOnEsc?: boolean;
- showFullscreenToggle?: boolean;
footer?: ReactNode;
className?: string;
}
@@ -27,11 +26,9 @@ export function Modal({
showCloseButton = true,
closeOnClickOutside = true,
closeOnEsc = true,
- showFullscreenToggle = false,
footer,
className = "",
}: ModalProps) {
- const [isFullscreen, setIsFullscreen] = useState(false);
const [isAnimating, setIsAnimating] = useState(false);
const modalRef = useRef
(null);
const contentRef = useRef(null);
@@ -113,22 +110,20 @@ export function Modal({
};
const sizeClasses = {
- sm: "max-w-md",
- md: "max-w-2xl",
- lg: "max-w-4xl",
- xl: "max-w-6xl",
- full: "max-w-[95vw]",
+ sm: "max-w-[95vw] sm:max-w-md",
+ md: "max-w-[95vw] sm:max-w-2xl",
+ lg: "max-w-[95vw] sm:max-w-4xl",
+ xl: "max-w-[95vw] sm:max-w-6xl",
+ full: "max-w-[98vw] sm:max-w-[95vw]",
};
- const modalSizeClass = isFullscreen
- ? "w-screen h-screen max-w-none rounded-none"
- : `w-full ${sizeClasses[size]} max-h-[90vh]`;
+ const modalSizeClass = `w-full ${sizeClasses[size]} max-h-[95vh] sm:max-h-[90vh]`;
return (
{/* Header */}
- {(title || showCloseButton || showFullscreenToggle) && (
-
+ {(title || showCloseButton) && (
+
{title && (
-
+
{title}
)}
-
- {showFullscreenToggle && (
-
- )}
+
{showCloseButton && (
);
diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts
index d71851fb..b3f38e80 100644
--- a/frontend/src/lib/api.ts
+++ b/frontend/src/lib/api.ts
@@ -476,6 +476,197 @@ class ApiClient {
return this.request(`/api/v1/folders/${id}/documents/`);
}
+ // Document Comments
+ async getDocumentComments(documentId: number): Promise {
+ return this.request(`/api/v1/document-comments/?document=${documentId}`);
+ }
+
+ async createDocumentComment(data: { document: number; text: string; parent?: number }): Promise {
+ return this.request('/api/v1/document-comments/', {
+ method: 'POST',
+ body: JSON.stringify(data),
+ });
+ }
+
+ async updateDocumentComment(id: number, text: string): Promise {
+ return this.request(`/api/v1/document-comments/${id}/`, {
+ method: 'PATCH',
+ body: JSON.stringify({ text }),
+ });
+ }
+
+ async deleteDocumentComment(id: number): Promise {
+ return this.request(`/api/v1/document-comments/${id}/`, {
+ method: 'DELETE',
+ });
+ }
+
+ async getCommentReplies(commentId: number): Promise {
+ return this.request(`/api/v1/document-comments/${commentId}/replies/`);
+ }
+
+ // Document Tags
+ async getDocumentTags(): Promise {
+ return this.request('/api/v1/document-tags/');
+ }
+
+ async getDocumentTag(id: number): Promise {
+ return this.request(`/api/v1/document-tags/${id}/`);
+ }
+
+ async createDocumentTag(data: { name: string; color?: string }): Promise {
+ return this.request('/api/v1/document-tags/', {
+ method: 'POST',
+ body: JSON.stringify(data),
+ });
+ }
+
+ async updateDocumentTag(id: number, data: { name?: string; color?: string }): Promise {
+ return this.request(`/api/v1/document-tags/${id}/`, {
+ method: 'PATCH',
+ body: JSON.stringify(data),
+ });
+ }
+
+ async deleteDocumentTag(id: number): Promise {
+ return this.request(`/api/v1/document-tags/${id}/`, {
+ method: 'DELETE',
+ });
+ }
+
+ async getDocumentsByTag(tagId: number): Promise {
+ return this.request(`/api/v1/document-tags/${tagId}/documents/`);
+ }
+
+ // Document Types
+ async getDocumentTypes(): Promise {
+ return this.request('/api/v1/document-types/');
+ }
+
+ async getDocumentType(id: number): Promise {
+ return this.request(`/api/v1/document-types/${id}/`);
+ }
+
+ async createDocumentType(data: { name: string; description?: string; icon?: string }): Promise {
+ return this.request('/api/v1/document-types/', {
+ method: 'POST',
+ body: JSON.stringify(data),
+ });
+ }
+
+ async updateDocumentType(id: number, data: { name?: string; description?: string; icon?: string }): Promise {
+ return this.request(`/api/v1/document-types/${id}/`, {
+ method: 'PATCH',
+ body: JSON.stringify(data),
+ });
+ }
+
+ async deleteDocumentType(id: number): Promise {
+ return this.request(`/api/v1/document-types/${id}/`, {
+ method: 'DELETE',
+ });
+ }
+
+ async getDocumentsByType(typeId: number): Promise {
+ return this.request(`/api/v1/document-types/${typeId}/documents/`);
+ }
+
+ // Cabinets (Virtual collections)
+ async getCabinets(): Promise {
+ return this.request('/api/v1/cabinets/');
+ }
+
+ async getCabinet(id: number): Promise {
+ return this.request(`/api/v1/cabinets/${id}/`);
+ }
+
+ async createCabinet(data: { name: string; description?: string; parent?: number }): Promise {
+ return this.request('/api/v1/cabinets/', {
+ method: 'POST',
+ body: JSON.stringify(data),
+ });
+ }
+
+ async updateCabinet(id: number, data: { name?: string; description?: string; parent?: number }): Promise {
+ return this.request(`/api/v1/cabinets/${id}/`, {
+ method: 'PATCH',
+ body: JSON.stringify(data),
+ });
+ }
+
+ async deleteCabinet(id: number): Promise {
+ return this.request(`/api/v1/cabinets/${id}/`, {
+ method: 'DELETE',
+ });
+ }
+
+ async getCabinetDocuments(id: number): Promise {
+ return this.request(`/api/v1/cabinets/${id}/documents/`);
+ }
+
+ async addDocumentToCabinet(cabinetId: number, documentId: number): Promise {
+ return this.request(`/api/v1/cabinets/${cabinetId}/add_document/`, {
+ method: 'POST',
+ body: JSON.stringify({ document_id: documentId }),
+ });
+ }
+
+ async removeDocumentFromCabinet(cabinetId: number, documentId: number): Promise {
+ return this.request(`/api/v1/cabinets/${cabinetId}/remove_document/`, {
+ method: 'POST',
+ body: JSON.stringify({ document_id: documentId }),
+ });
+ }
+
+ async getCabinetChildren(id: number): Promise {
+ return this.request(`/api/v1/cabinets/${id}/children/`);
+ }
+
+ async getCabinetHierarchy(id: number): Promise {
+ return this.request(`/api/v1/cabinets/${id}/hierarchy/`);
+ }
+
+ // Document Versions (django-reversion)
+ async getDocumentVersions(id: number): Promise {
+ return this.request(`/api/v1/documents/${id}/versions/`);
+ }
+
+ async getDocumentActivity(id: number): Promise {
+ return this.request(`/api/v1/documents/${id}/activity/`);
+ }
+
+ async revertDocumentToVersion(id: number, versionId: number): Promise {
+ return this.request(`/api/v1/documents/${id}/revert/`, {
+ method: 'POST',
+ body: JSON.stringify({ version_id: versionId }),
+ });
+ }
+
+ // Related Documents
+ async getRelatedDocuments(id: number): Promise {
+ return this.request(`/api/v1/documents/${id}/related/`);
+ }
+
+ async addRelatedDocument(id: number, relatedId: number): Promise {
+ return this.request(`/api/v1/documents/${id}/add_related/`, {
+ method: 'POST',
+ body: JSON.stringify({ related_document_id: relatedId }),
+ });
+ }
+
+ async removeRelatedDocument(id: number, relatedId: number): Promise {
+ return this.request(`/api/v1/documents/${id}/remove_related/`, {
+ method: 'DELETE',
+ body: JSON.stringify({ related_document_id: relatedId }),
+ });
+ }
+
+ // Document Thumbnails
+ getDocumentThumbnail(id: number, size?: 'small' | 'medium' | 'large' | 'original'): string {
+ const sizeParam = size ? `?size=${size}` : '';
+ return `/api/v1/documents/${id}/thumbnail/${sizeParam}`;
+ }
+
// Заявки
async getRequests(params?: { status?: string; type?: string; search?: string; page?: number; limit?: number }): Promise {
const queryParams = new URLSearchParams();
@@ -742,6 +933,30 @@ class ApiClient {
return this.request('/api/v1/schedule/calendars/');
}
+ async getCalendarSubscriptions(): Promise {
+ return this.request('/api/v1/schedule/subscriptions/');
+ }
+
+ async subscribeToCalendar(calendarId: number): Promise {
+ return this.request('/api/v1/schedule/subscriptions/', {
+ method: 'POST',
+ body: JSON.stringify({ calendar: calendarId }),
+ });
+ }
+
+ async unsubscribeFromCalendar(subscriptionId: number): Promise {
+ return this.request(`/api/v1/schedule/subscriptions/${subscriptionId}/`, {
+ method: 'DELETE',
+ });
+ }
+
+ async updateSubscription(subscriptionId: number, data: any): Promise {
+ return this.request(`/api/v1/schedule/subscriptions/${subscriptionId}/`, {
+ method: 'PATCH',
+ body: JSON.stringify(data),
+ });
+ }
+
async getCalendar(id: number): Promise {
return this.request(`/api/v1/schedule/calendars/${id}/`);
}
@@ -938,11 +1153,11 @@ class ApiClient {
}
// Посты - CRUD операции
- async createPost(data: { content: string; attachments?: File[] }): Promise {
+ async createPost(data: any): Promise {
const formData = new FormData();
formData.append('content', data.content);
if (data.attachments) {
- data.attachments.forEach((file) => {
+ data.attachments.forEach((file: File) => {
formData.append('attachments', file);
});
}
@@ -966,11 +1181,11 @@ class ApiClient {
return response.json();
}
- async updatePost(postId: number, data: { content: string; attachments?: File[] }): Promise {
+ async updatePost(postId: number, data: any): Promise {
const formData = new FormData();
formData.append('content', data.content);
if (data.attachments) {
- data.attachments.forEach((file) => {
+ data.attachments.forEach((file: File) => {
formData.append('attachments', file);
});
}
diff --git a/frontend/src/lib/document-utils/ocr.ts b/frontend/src/lib/document-utils/ocr.ts
index acbd2501..e7f67b1d 100644
--- a/frontend/src/lib/document-utils/ocr.ts
+++ b/frontend/src/lib/document-utils/ocr.ts
@@ -32,8 +32,8 @@ export async function performOCR(
} = options;
try {
- const worker = await Tesseract.createWorker({
- logger: (m) => {
+ const worker = await Tesseract.createWorker(languages as any, undefined, {
+ logger: (m: any) => {
if (onProgress) {
onProgress({
status: m.status,
@@ -43,10 +43,6 @@ export async function performOCR(
}
});
- // Загружаем языки
- await worker.loadLanguage(languages.join('+'));
- await worker.initialize(languages.join('+'));
-
// Распознаем текст
const { data } = await worker.recognize(file);
diff --git a/frontend/src/lib/document-utils/pdf.ts b/frontend/src/lib/document-utils/pdf.ts
index 4d16b5e9..26515414 100644
--- a/frontend/src/lib/document-utils/pdf.ts
+++ b/frontend/src/lib/document-utils/pdf.ts
@@ -85,7 +85,8 @@ export async function generatePDFThumbnail(
await page.render({
canvasContext: context,
- viewport
+ viewport,
+ canvas
}).promise;
return canvas.toDataURL('image/jpeg', 0.7);
@@ -187,13 +188,15 @@ export async function getPDFInfo(file: File): Promise<{
const arrayBuffer = await file.arrayBuffer();
const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
const metadata = await pdf.getMetadata();
+
+ const info = metadata.info as any;
return {
numPages: pdf.numPages,
- title: metadata.info?.Title,
- author: metadata.info?.Author,
- creationDate: metadata.info?.CreationDate
- ? new Date(metadata.info.CreationDate)
+ title: info?.Title,
+ author: info?.Author,
+ creationDate: info?.CreationDate
+ ? new Date(info.CreationDate)
: undefined
};
} catch (error) {
diff --git a/frontend/src/types/api.ts b/frontend/src/types/api.ts
index 80dbf75f..dd42e029 100644
--- a/frontend/src/types/api.ts
+++ b/frontend/src/types/api.ts
@@ -153,6 +153,121 @@ export interface Document {
is_acknowledged?: boolean;
}
+// Document Comments
+export interface DocumentComment {
+ id: number;
+ document: number;
+ author: {
+ id: number;
+ full_name: string;
+ avatar?: string;
+ };
+ text: string;
+ parent?: number;
+ created_at: string;
+ updated_at: string;
+ replies_count: number;
+ can_edit: boolean;
+ can_delete: boolean;
+}
+
+export interface CreateDocumentCommentData {
+ document: number;
+ text: string;
+ parent?: number;
+}
+
+// Document Tags
+export interface DocumentTag {
+ id: number;
+ name: string;
+ slug: string;
+ color?: string;
+ created_at: string;
+ documents_count: number;
+}
+
+export interface CreateDocumentTagData {
+ name: string;
+ color?: string;
+}
+
+// Document Types
+export interface DocumentType {
+ id: number;
+ name: string;
+ slug: string;
+ description?: string;
+ icon?: string;
+ created_at: string;
+ documents_count: number;
+}
+
+export interface CreateDocumentTypeData {
+ name: string;
+ description?: string;
+ icon?: string;
+}
+
+// Cabinets (Virtual collections)
+export interface Cabinet {
+ id: number;
+ name: string;
+ slug: string;
+ description?: string;
+ parent?: number;
+ created_by: number;
+ created_at: string;
+ documents_count: number;
+ children_count: number;
+}
+
+export interface CreateCabinetData {
+ name: string;
+ description?: string;
+ parent?: number;
+}
+
+export interface CabinetHierarchy {
+ id: number;
+ name: string;
+ parent: number | null;
+ children: CabinetHierarchy[];
+}
+
+// Document Versions (django-reversion)
+export interface DocumentVersion {
+ id: number;
+ revision_id: number;
+ version: number;
+ created_at: string;
+ user: string;
+ comment: string;
+ changes: Record;
+}
+
+export interface DocumentActivity {
+ id: number;
+ timestamp: string;
+ user: string;
+ action: string;
+ description: string;
+ related_object?: any;
+}
+
+export interface RevertDocumentData {
+ version_id: number;
+}
+
+// Related Documents
+export interface RelatedDocument {
+ id: number;
+ title: string;
+ file_type: string;
+ created_at: string;
+ uploaded_by: string;
+}
+
// Requests types
export interface Request {
id: number;