# 📄 DOCX Document Management - Frontend Vite Implementation Plan

> **Focus:** Frontend UI/UX cho Document page trong Cuccu Note (Vite + React + TypeScript + TailwindCSS)
> **Created:** 2026-04-19
> **Status:** 🎨 DESIGN PHASE → Ready to implement

---

## 🎯 Mục tiêu nghiệp vụ (Business Goal)

Cho phép user:
1. **Upload** file DOCX (drag-drop, multi-file)
2. **Preview** nội dung DOCX trước khi import
3. **Import** DOCX thành Memo với formatting được giữ nguyên
4. **Quản lý** thư viện DOCX (list, view, download, delete)
5. **AI Enhance** (optional): Auto-tag, summarize, structure detection

---

## 🗂️ File Structure (Vite + React)

```
frontend/src/
├── pages/
│   ├── DocumentsPage.tsx          # Main page /documents
│   └── DocumentDetailPage.tsx     # /documents/:id (preview)
├── components/
│   └── DOCX/
│       ├── UploadArea/
│       │   ├── index.tsx          # Main upload dropzone
│       │   ├── FileDropzone.tsx
│       │   ├── UploadProgress.tsx
│       │   └── ValidationErrors.tsx
│       ├── PreviewPanel/
│       │   ├── index.tsx          # Preview extracted content
│       │   ├── DocumentPreview.tsx
│       │   ├── MarkdownRenderer.tsx
│       │   └── MetadataDisplay.tsx
│       ├── ImportForm/
│       │   ├── index.tsx
│       │   ├── TitleInput.tsx
│       │   ├── TagSelector.tsx
│       │   ├── WorkspaceSelect.tsx
│       │   └── VisibilitySelect.tsx
│       ├── AIEnhancePanel/
│       │   ├── index.tsx
│       │   ├── SuggestionCard.tsx
│       │   ├── TagSuggestions.tsx
│       │   ├── SummarySuggestion.tsx
│       │   └── StructureDetection.tsx
│       ├── DocumentCard/
│       │   ├── index.tsx
│       │   ├── DocumentThumbnail.tsx
│       │   └── DocumentActions.tsx
│       ├── DocumentList/
│       │   ├── index.tsx
│       │   ├── DocumentGridView.tsx
│       │   ├── DocumentTableView.tsx
│       │   └── FilterBar.tsx
│       └── common/
│           ├── LoadingSpinner.tsx
│           └── ErrorBoundary.tsx
├── hooks/
│   ├── useDocumentUpload.ts
│   ├── useDocuments.ts
│   ├── useDocumentDetail.ts
│   ├── useImportDocument.ts
│   └── useAIEnhance.ts
├── service/
│   ├── docxService.ts
│   └── types.ts
├── utils/
│   ├── docxParser.ts (client-side preview, optional)
│   └── fileHelpers.ts
└── locales/
    ├── en.json (add document keys)
    └── vi.json (add document keys)
```

---

## 🎨 UI/UX Design System

### Color Palette (dùng existing theme)
- Primary: `oklch(0.6 0.2 250)` (màu primary của Cuccu)
- Background: `oklch(0.97 0.01 250)` (light) / `oklch(0.15 0.01 250)` (dark)
- Border: `oklch(0.8 0.02 250)`
- Muted: `oklch(0.7 0.02 250)`

### Typography
- Font: Inter (sans) + JetBrains Mono (code)
- Headings: `text-xl font-semibold`
- Body: `text-sm text-foreground`

### Components Style
- Card: `rounded-xl border border-border bg-background shadow-sm`
- Button: `px-4 py-2 rounded-md font-medium transition-colors`
- Input: `flex h-10 w-full rounded-md border border-input bg-background px-3 py-2`

---

## 📱 Page 1: Documents Library (`/documents`)

### Layout Structure:
```
┌─────────────────────────────────────────────────────────────┐
│  [Sidebar]  [Main Content Area]                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─ Page Header ──────────────────────────────────────────┐│
│  │ 📄 Documents                    [Upload New DOCX]     ││
│  └────────────────────────────────────────────────────────┘│
│                                                             │
│  ┌─ Filter Bar ───────────────────────────────────────────┐│
│  │ 🔍 Search documents...       [All▼] [Pending] [Imported]││
│  └────────────────────────────────────────────────────────┘│
│                                                             │
│  ┌─ Documents Grid/List ──────────────────────────────────┐│
│  │  ┌─────────────┐  ┌─────────────┐                     ││
│  │  │ 📄 Report   │  │ 📄 Notes    │                     ││
│  │  │ meeting.doc │  │ project.doc │                     ││
│  │  │ 2.4 MB     │  │ 1.1 MB      │                     ││
│  │  │ ✅ Imported │  │ ⏳ Pending   │                     ││
│  │  │ [View] [DL] │  │ [Preview]    │                     ││
│  │  └─────────────┘  └─────────────┘                     ││
│  │  (hoặc Table view với columns: Name, Size, Date, Status││
│  └────────────────────────────────────────────────────────┘│
│                                                             │
│  ┌─ Empty State ──────────────────────────────────────────┐│
│  │  [Empty illustration]                                   ││
│  │  No documents yet. Upload your first DOCX!            ││
│  └────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘
```

### State Management:
```typescript
interface DocumentsState {
  documents: Document[];
  viewMode: 'grid' | 'table';
  filter: 'all' | 'pending' | 'imported';
  searchQuery: string;
  isLoading: boolean;
  pagination: { page: number; total: number; limit: number };
}
```

---

## 📄 Page 2: Document Detail & Preview (`/documents/:id`)

### Layout:
```
┌─────────────────────────────────────────────────────────────┐
│  [Sidebar]  [Main]                                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─ Document Header ─────────────────────────────────────┐│
│  │ 📄 meeting_notes.docx                    [Edit] [DL]  ││
│  │ 2.4 MB • Uploaded 3 days ago • Status: Processed     ││
│  └────────────────────────────────────────────────────────┘│
│                                                             │
│  ┌─ Two Column Layout ───────────────────────────────────┐│
│  │  Left (40%)               Right (60%)                 ││
│  │  ┌─ Preview ──────────┐  ┌─ Extracted Content ────┐││
│  │  │ [Thumbnail]        │  │ # Meeting Notes         ││
│  │  │ First page image   │  │                         ││
│  │  │                    │  │ **Date:** Jan 15, 2024 ││
│  │  │ 📊 5 pages         │  │                         ││
│  │  │ 🏷️ 3 tags          │  │ ## Attendees            ││
│  │  │ ✍️ John Doe        │  │ - Alice                ││
│  │  └────────────────────┘  │ - Bob                  ││
│  │                           │                         ││
│  │                           │ ## Action Items         ││
│  │                           │ - [ ] Task 1            ││
│  │                           │ - [x] Task 2            ││
│  │                           │                         ││
│  │                           │ [Editable textarea]     ││
│  │                           └─────────────────────────┘│
│  └────────────────────────────────────────────────────────┘│
│                                                             │
│  ┌─ Import Actions ───────────────────────────────────────┐│
│  │ Title: [Meeting Notes - Jan 15, 2024]                 ││
│  │ Tags:  [project] [meeting] [+]                       ││
│  │ Workspace: [PERSONAL▼]  Visibility: [PRIVATE▼]       ││
│  │                                                       ││
│  │ [✨ AI Enhance]  [Import as Memo]  [Back to Library] ││
│  └────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘
```

---

## ⚡ Component Details

### 1. **UploadArea** (`components/DOCX/UploadArea/index.tsx`)

**Props:**
```typescript
interface UploadAreaProps {
  onUpload: (files: File[]) => Promise<UploadResult[]>;
  maxSize?: number; // bytes, default 50MB
  maxFiles?: number;
  accept?: string[]; // ['.docx']
  disabled?: boolean;
}
```

**Implementation:**
```tsx
export const UploadArea: React.FC<UploadAreaProps> = ({
  onUpload,
  maxSize = 50 * 1024 * 1024,
  maxFiles = 5,
  accept = ['.docx'],
}) => {
  const [isDragging, setIsDragging] = useState(false);
  const [uploading, setUploading] = useState(false);
  const [progress, setProgress] = useState<Record<string, number>>({});

  const onDrop = useCallback(async (acceptedFiles: File[]) => {
    setIsDragging(false);
    setUploading(true);

    // Validate each file
    const validFiles: File[] = [];
    const errors: string[] = [];

    acceptedFiles.forEach(file => {
      if (!accept.includes('.' + file.name.split('.').pop())) {
        errors.push(`${file.name}: Invalid file type`);
      } else if (file.size > maxSize) {
        errors.push(`${file.name}: Too large (max ${maxSize/1024/1024}MB)`);
      } else {
        validFiles.push(file);
      }
    });

    if (validFiles.length > 0) {
      await onUpload(validFiles);
    }

    setUploading(false);
  }, [onUpload, maxSize, accept]);

  return (
    <Dropzone
      onDrop={onDrop}
      onDragEnter={() => setIsDragging(true)}
      onDragLeave={() => setIsDragging(false)}
      accept={{ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': ['.docx'] }}
      multiple={maxFiles > 1}
      disabled={uploading || disabled}
    >
      {({ getRootProps, getInputProps }) => (
        <div
          {...getRootProps()}
          className={cn(
            "border-2 border-dashed rounded-xl p-12 text-center cursor-pointer transition-colors",
            isDragging ? "border-primary bg-primary/5" : "border-border",
            uploading && "opacity-50 pointer-events-none"
          )}
        >
          <input {...getInputProps()} />
          <UploadCloud className="w-12 h-12 mx-auto mb-4 text-muted-foreground" />
          <p className="text-lg font-medium mb-2">
            {uploading ? 'Uploading...' : 'Drop DOCX files here'}
          </p>
          <p className="text-sm text-muted-foreground mb-4">
            or click to browse • Max {maxSize/1024/1024}MB per file
          </p>
          {uploading && (
            <Progress value={Object.values(progress).reduce((a, b) => a + b, 0) / Object.keys(progress).length} />
          )}
          {errors.length > 0 && (
            <Alert variant="destructive" className="mt-4">
              <AlertCircle className="h-4 w-4" />
              <AlertTitle>Upload Errors</AlertTitle>
              <AlertDescription>
                <ul className="list-disc pl-4">
                  {errors.map((err, i) => <li key={i}>{err}</li>)}
                </ul>
              </AlertDescription>
            </Alert>
          )}
        </div>
      )}
    </Dropzone>
  );
};
```

---

### 2. **PreviewPanel** (`components/DOCX/PreviewPanel/index.tsx`)

Hiển thị nội dung đã parse:

```tsx
interface PreviewPanelProps {
  document: Document;
  parsedContent?: ParsedDocxContent;
  editable?: boolean;
  onChange?: (content: string) => void;
}

export const PreviewPanel: React.FC<PreviewPanelProps> = ({
  document,
  parsedContent,
  editable = false,
  onChange,
}) => {
  const [content, setContent] = useState(parsedContent?.content || '');

  useEffect(() => {
    if (parsedContent) {
      setContent(parsedContent.content);
    }
  }, [parsedContent]);

  return (
    <div className="h-full flex flex-col">
      {/* Thumbnail */}
      {document.thumbnail_url && (
        <div className="mb-4 p-4 border rounded-lg bg-muted/20">
          <img
            src={document.thumbnail_url}
            alt={`${document.filename} preview`}
            className="max-w-full h-auto mx-auto"
          />
          <p className="text-xs text-center mt-2 text-muted-foreground">
            First page preview
          </p>
        </div>
      )}

      {/* Metadata */}
      <div className="flex gap-4 mb-4 text-sm text-muted-foreground">
        <span>📄 {document.file_size ? formatBytes(document.file_size) : 'N/A'}</span>
        <span>📑 {parsedContent?.metadata?.pages || '?'} pages</span>
        <span>✍️ {parsedContent?.metadata?.author || 'Unknown'}</span>
      </div>

      {/* Content Editor/Viewer */}
      <div className="flex-1 border rounded-lg overflow-hidden">
        {editable ? (
          <textarea
            value={content}
            onChange={(e) => {
              setContent(e.target.value);
              onChange?.(e.target.value);
            }}
            className="w-full h-full p-4 resize-none focus:outline-none font-mono text-sm"
            placeholder="Document content will appear here..."
          />
        ) : (
          <div className="p-4 h-full overflow-y-auto">
            <MarkdownRenderer content={content} />
          </div>
        )}
      </div>
    </div>
  );
};
```

---

### 3. **ImportForm** (`components/DOCX/ImportForm/index.tsx`)

Form nhập metadata trước khi import:

```tsx
interface ImportFormProps {
  document: Document;
  parsedContent: ParsedDocxContent;
  onSubmit: (data: ImportFormData) => Promise<void>;
  onCancel: () => void;
  aiSuggestions?: AISuggestions;
}

export const ImportForm: React.FC<ImportFormProps> = ({
  document,
  parsedContent,
  onSubmit,
  onCancel,
  aiSuggestions,
}) => {
  const [title, setTitle] = useState(
    aiSuggestions?.title || parsedContent.metadata?.title || document.original_name.replace('.docx', '')
  );
  const [tags, setTags] = useState<string[]>(aiSuggestions?.tags || []);
  const [workspaceId, setWorkspaceId] = useState<string>('PERSONAL');
  const [visibility, setVisibility] = useState<'PRIVATE' | 'PROTECTED' | 'PUBLIC'>('PRIVATE');

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    await onSubmit({
      title,
      tags,
      workspace_id: workspaceId,
      visibility,
      content: parsedContent.content, // Already edited if PreviewPanel editable
    });
  };

  return (
    <form onSubmit={handleSubmit} className="space-y-4">
      {/* Title */}
      <div>
        <label className="block text-sm font-medium mb-1">Title</label>
        <input
          type="text"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
          className="w-full h-10 px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-primary"
          placeholder="Memo title"
          required
        />
      </div>

      {/* Tags */}
      <div>
        <label className="block text-sm font-medium mb-1">Tags</label>
        <TagSelector
          selected={tags}
          onChange={setTags}
          suggestions={aiSuggestions?.tags}
        />
      </div>

      {/* Workspace & Visibility */}
      <div className="grid grid-cols-2 gap-4">
        <div>
          <label className="block text-sm font-medium mb-1">Workspace</label>
          <select
            value={workspaceId}
            onChange={(e) => setWorkspaceId(e.target.value)}
            className="w-full h-10 px-3 py-2 border rounded-md bg-background"
          >
            <option value="PERSONAL">Personal</option>
            <option value="AI_SALES_CRM">AI Sales CRM</option>
          </select>
        </div>
        <div>
          <label className="block text-sm font-medium mb-1">Visibility</label>
          <select
            value={visibility}
            onChange={(e) => setVisibility(e.target.value as any)}
            className="w-full h-10 px-3 py-2 border rounded-md bg-background"
          >
            <option value="PRIVATE">Private</option>
            <option value="PROTECTED">Protected</option>
            <option value="PUBLIC">Public</option>
          </select>
        </div>
      </div>

      {/* Actions */}
      <div className="flex gap-2 pt-4">
        <Button type="submit" className="flex-1">
          📥 Import as Memo
        </Button>
        <Button type="button" variant="outline" onClick={onCancel}>
          Cancel
        </Button>
      </div>
    </form>
  );
};
```

---

### 4. **AIEnhancePanel** (`components/DOCX/AIEnhancePanel/index.tsx`)

Slide-over panel với suggestions:

```tsx
export const AIEnhancePanel: React.FC<{
  open: boolean;
  onClose: () => void;
  suggestions: AISuggestions;
  onApply: (applied: Partial<AISuggestions>) => void;
}> = ({ open, onClose, suggestions, onApply }) => {
  const [selected, setSelected] = useState({
    tags: suggestions.tags.map(() => true),
    summary: true,
    structure: true,
  });

  return (
    <Drawer open={open} onClose={onClose} side="right">
      <DrawerContent className="w-full max-w-md">
        <DrawerHeader>
          <DrawerTitle>✨ AI Suggestions</DrawerTitle>
          <DrawerDescription>
            AI has analyzed your document and made these suggestions
          </DrawerDescription>
        </DrawerHeader>

        <div className="p-4 space-y-6 overflow-y-auto">
          {/* Auto-Tags */}
          <div>
            <h4 className="font-medium mb-2 flex items-center gap-2">
              <Hash className="w-4 h-4" />
              Suggested Tags
            </h4>
            <div className="flex flex-wrap gap-2">
              {suggestions.tags.map((tag, idx) => (
                <Badge
                  key={tag}
                  variant={selected.tags[idx] ? "default" : "outline"}
                  className="cursor-pointer"
                  onClick={() => {
                    const newSelected = [...selected.tags];
                    newSelected[idx] = !newSelected[idx];
                    setSelected({ ...selected, tags: newSelected });
                  }}
                >
                  {tag}
                </Badge>
              ))}
            </div>
          </div>

          {/* Summary */}
          {suggestions.summary && (
            <div>
              <h4 className="font-medium mb-2 flex items-center gap-2">
                <FileText className="w-4 h-4" />
                Summary
              </h4>
              <Card>
                <CardContent className="p-3 text-sm text-muted-foreground">
                  {suggestions.summary}
                </CardContent>
              </Card>
            </div>
          )}

          {/* Structure */}
          {suggestions.structure && suggestions.structure.length > 0 && (
            <div>
              <h4 className="font-medium mb-2 flex items-center gap-2">
                <List className="w-4 h-4" />
                Detected Structure
              </h4>
              <ul className="space-y-1">
                {suggestions.structure.map((section, idx) => (
                  <li key={idx} className="text-sm flex items-center gap-2 p-2 rounded hover:bg-muted">
                    <ChevronRight className="w-3 h-3" />
                    <span className="font-mono">{section.heading}</span>
                    <span className="text-xs text-muted-foreground">(lines {section.start}-{section.end})</span>
                  </li>
                ))}
              </ul>
            </div>
          )}
        </div>

        <DrawerFooter>
          <Button onClick={() => onApply({
            tags: suggestions.tags.filter((_, idx) => selected.tags[idx]),
            summary: selected.summary ? suggestions.summary : undefined,
            structure: selected.structure ? suggestions.structure : undefined,
          })}>
            Apply Selected
          </Button>
          <Button variant="outline" onClick={onClose}>Skip</Button>
        </DrawerFooter>
      </DrawerContent>
    </Drawer>
  );
};
```

---

### 5. **DocumentCard** (`components/DOCX/DocumentCard/index.tsx`)

```tsx
export const DocumentCard: React.FC<{
  document: Document;
  onView: (id: string) => void;
  onDownload: (id: string) => void;
  onDelete: (id: string) => void;
  onImport?: (id: string) => void;
  viewMode: 'grid' | 'table';
}> = ({ document, onView, onDownload, onDelete, onImport, viewMode }) => {
  if (viewMode === 'grid') {
    return (
      <Card className="group hover:shadow-md transition-shadow">
        <CardHeader className="pb-3">
          <div className="flex items-start justify-between">
            <div className="flex items-center gap-2 min-w-0">
              <File className="w-8 h-8 flex-shrink-0 text-blue-500" />
              <div className="min-w-0">
                <CardTitle className="text-sm truncate">
                  {document.original_name}
                </CardTitle>
                <CardDescription className="text-xs">
                  {formatBytes(document.file_size)} • {formatDistanceToNow(new Date(document.uploaded_at))}
                </CardDescription>
              </div>
            </div>
            <DropdownMenu>
              <DropdownMenuTrigger asChild>
                <Button variant="ghost" size="sm" className="h-8 w-8 p-0">
                  <MoreVertical className="w-4 h-4" />
                </Button>
              </DropdownMenuTrigger>
              <DropdownMenuContent align="end">
                <DropdownMenuItem onClick={() => onView(document.id)}>
                  <Eye className="w-4 h-4 mr-2" /> View
                </DropdownMenuItem>
                <DropdownMenuItem onClick={() => onDownload(document.id)}>
                  <Download className="w-4 h-4 mr-2" /> Download
                </DropdownMenuItem>
                {document.status === 'pending' && onImport && (
                  <DropdownMenuItem onClick={() => onImport(document.id)}>
                    <Import className="w-4 h-4 mr-2" /> Import
                  </DropdownMenuItem>
                )}
                <DropdownMenuSeparator />
                <DropdownMenuItem onClick={() => onDelete(document.id)} className="text-destructive">
                  <Trash2 className="w-4 h-4 mr-2" /> Delete
                </DropdownMenuItem>
              </DropdownMenuContent>
            </DropdownMenu>
          </div>
        </CardHeader>
        <CardContent className="pt-0">
          <div className="flex items-center gap-2 text-xs">
            <Badge variant={document.status === 'imported' ? 'default' : 'secondary'}>
              {document.status === 'imported' ? '✅ Imported' : '⏳ Pending'}
            </Badge>
            {document.memo_id && (
              <span className="text-muted-foreground">→ Memo created</span>
            )}
          </div>
        </CardContent>
      </Card>
    );
  }

  // Table view
  return (
    <div className="flex items-center py-3 border-b last:border-0 hover:bg-muted/50">
      <div className="flex-1 min-w-0 flex items-center gap-3">
        <File className="w-5 h-5 flex-shrink-0 text-blue-500" />
        <div>
          <p className="font-medium truncate">{document.original_name}</p>
          <p className="text-xs text-muted-foreground">
            {formatBytes(document.file_size)} • {formatDistanceToNow(new Date(document.uploaded_at))}
          </p>
        </div>
      </div>
      <Badge variant={document.status === 'imported' ? 'default' : 'secondary'} className="mr-4">
        {document.status}
      </Badge>
      <div className="flex gap-1">
        <Button size="sm" variant="ghost" onClick={() => onView(document.id)}>
          <Eye className="w-4 h-4" />
        </Button>
        <Button size="sm" variant="ghost" onClick={() => onDownload(document.id)}>
          <Download className="w-4 h-4" />
        </Button>
        {document.status === 'pending' && onImport && (
          <Button size="sm" onClick={() => onImport(document.id)}>
            <Import className="w-4 h-4 mr-2" /> Import
          </Button>
        )}
        <Button size="sm" variant="ghost" onClick={() => onDelete(document.id)} className="text-destructive">
          <Trash2 className="w-4 h-4" />
        </Button>
      </div>
    </div>
  );
};
```

---

## 🔌 API Service Layer

**File:** `src/service/docxService.ts`

```typescript
import { ApiDocument, ApiDocxParseResult, ImportFormData } from './types';

export const docxService = {
  // Upload DOCX
  uploadDocument: async (file: File): Promise<ApiDocument> => {
    const formData = new FormData();
    formData.append('file', file);
    const res = await fetch('/api/v1/documents/docx/upload', {
      method: 'POST',
      headers: getAuthHeaders(),
      body: formData,
    });
    if (!res.ok) throw new Error(`Upload failed: ${res.statusText}`);
    return res.json();
  },

  // Get document with parsed content
  getDocument: async (id: string): Promise<{
    document: ApiDocument;
    parsed_content?: ApiDocxParseResult;
  }> => {
    const res = await fetch(`/api/v1/documents/docx/${id}`, {
      headers: getAuthHeaders(),
    });
    if (!res.ok) throw new Error(`Fetch failed: ${res.statusText}`);
    return res.json();
  },

  // List documents
  listDocuments: async (params: {
    page?: number;
    limit?: number;
    status?: 'pending' | 'imported' | 'all';
  }): Promise<{
    documents: ApiDocument[];
    total: number;
    page: number;
    limit: number;
  }> => {
    const searchParams = new URLSearchParams();
    if (params.page) searchParams.append('page', params.page.toString());
    if (params.limit) searchParams.append('limit', params.limit.toString());
    if (params.status && params.status !== 'all') searchParams.append('status', params.status);

    const res = await fetch(`/api/v1/documents/docx?${searchParams}`, {
      headers: getAuthHeaders(),
    });
    if (!res.ok) throw new Error(`List failed: ${res.statusText}`);
    return res.json();
  },

  // Import as memo
  importDocument: async (id: string, data: ImportFormData): Promise<ApiMemo> => {
    const res = await fetch(`/api/v1/documents/docx/${id}/import`, {
      method: 'POST',
      headers: {
        ...getAuthHeaders(),
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(data),
    });
    if (!res.ok) throw new Error(`Import failed: ${res.statusText}`);
    return res.json();
  },

  // Delete document
  deleteDocument: async (id: string): Promise<void> => {
    const res = await fetch(`/api/v1/documents/docx/${id}`, {
      method: 'DELETE',
      headers: getAuthHeaders(),
    });
    if (!res.ok) throw new Error(`Delete failed: ${res.statusText}`);
  },

  // AI Enhance (optional)
  enhanceDocument: async (id: string): Promise<AISuggestions> => {
    const res = await fetch(`/api/v1/documents/docx/${id}/enhance`, {
      method: 'POST',
      headers: getAuthHeaders(),
    });
    if (!res.ok) throw new Error(`Enhance failed: ${res.statusText}`);
    return res.json();
  },
};
```

---

## 🧩 Hooks

**`src/hooks/useDocuments.ts`:**
```typescript
export const useDocuments = (params: {
  status?: 'pending' | 'imported' | 'all';
  page?: number;
}) => {
  const queryClient = useQueryClient();

  return useQuery({
    queryKey: ['documents', params],
    queryFn: () => docxService.listDocuments(params),
    staleTime: 30000,
  });
};
```

**`src/hooks/useDocumentUpload.ts`:**
```typescript
export const useDocumentUpload = () => {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (file: File) => docxService.uploadDocument(file),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['documents'] });
    },
  });
};
```

**`src/hooks/useImportDocument.ts`:**
```typescript
export const useImportDocument = () => {
  const queryClient = useQueryClient();
  const navigate = useNavigate();

  return useMutation({
    mutationFn: ({ id, data }: { id: string; data: ImportFormData }) =>
      docxService.importDocument(id, data),
    onSuccess: (memo) => {
      queryClient.invalidateQueries({ queryKey: ['documents'] });
      navigate(`/memos/${memo.id}`); // Redirect to new memo
    },
  });
};
```

---

## 🌐 i18n Keys (thêm vào en.json & vi.json)

```json
{
  "document": {
    "title": "Documents",
    "upload": "Upload DOCX",
    "upload_drop": "Drop DOCX files here",
    "upload_btn": "Select Files",
    "preview": "Preview",
    "import": "Import as Memo",
    "delete": "Delete",
    "download": "Download",
    "view": "View",
    "back_to_library": "Back to Library",
    "empty_title": "No Documents",
    "empty_desc": "Upload DOCX files to convert them into memos",
    "status": {
      "pending": "Pending",
      "processed": "Processed",
      "imported": "Imported",
      "error": "Error"
    },
    "metadata": {
      "pages": "Pages",
      "author": "Author",
      "size": "Size"
    },
    "import_form": {
      "title": "Memo Title",
      "tags": "Tags",
      "workspace": "Workspace",
      "visibility": "Visibility",
      "import_btn": "Import as Memo"
    },
    "ai_enhance": {
      "title": "AI Suggestions",
      "tags": "Suggested Tags",
      "summary": "Summary",
      "structure": "Detected Structure",
      "apply": "Apply Selected",
      "skip": "Skip"
    }
  }
}
```

---

## 🛠️ Implementation Steps (Frontend)

### **Day 1: Setup & Basic Components**
1. Tạo `pages/DocumentsPage.tsx` skeleton với layout
2. Tạo `components/DOCX/UploadArea/` với react-dropzone
3. Tạo `service/docxService.ts` với API methods
4. Tạo `hooks/useDocumentUpload.ts`, `useDocuments.ts`
5. Tạo types trong `service/types.ts`
6. Add i18n keys

### **Day 2: Document List & Card**
1. Implement `DocumentCard` component (grid & table view)
2. Implement `DocumentList` với filter/search
3. Connect to API, show loading/error states
4. Add view mode toggle (grid/table)

### **Day 3: Preview & Parsing**
1. Tạo `PreviewPanel` component với markdown renderer
2. Add client-side DOCX parser (dùng `mammoth` hoặc `docx-preview` npm package)
3. Show thumbnail (convert first page to image using `docx-to-pdf` + `pdf-to-image` on backend, hoặc simple placeholder)

### **Day 4: Import Flow**
1. Tạo `ImportForm` component
2. Implement `useImportDocument` hook
3. Connect import → create memo → redirect
4. Test full flow: upload → preview → edit → import → memo detail

### **Day 5: AI Enhancement (Optional)**
1. Tạo `AIEnhancePanel` slide-over
2. Implement `useAIEnhance` hook
3. Backend endpoint `/api/v1/documents/docx/{id}/enhance` (mock if no AI)
4. Connect suggestions UI

### **Day 6: Polish & Testing**
1. Add loading states, error handling
2. Responsive design (mobile)
3. Accessibility (keyboard nav, ARIA)
4. Unit tests cho components
5. E2E test với Playwright

---

## 📦 NPM Dependencies Cần Thêm

```json
{
  "dependencies": {
    "react-dropzone": "^14.2.3",
    "react-markdown": "^9.0.1",
    "remark-gfm": "^4.0.0",
    "react-syntax-highlighter": "^15.5.0",
    "date-fns": "^3.6.0",
    "lucide-react": "^0.300.0",
    "zustand": "^4.5.0" // hoặc dùng react-query + context
  },
  "devDependencies": {
    "@types/react-dropzone": "^14.1.0",
    "@types/react-syntax-highlighter": "^15.5.0"
  }
}
```

---

## 🎯 Success Criteria

- ✅ User can upload .docx file (drag-drop)
- ✅ Upload shows progress bar
- ✅ Document list displays all uploads
- ✅ Preview shows extracted markdown content
- ✅ Import creates new memo with correct content
- ✅ Document status updates to "imported"
- ✅ Delete removes document & cleanup
- ✅ Search & filter works
- ✅ Responsive on mobile

---

## 📝 Notes

1. **DOCX Parsing:** Frontend có thể parse DOCX bằng `mammoth` browser version, nhưng để chính xác nên để backend parse (dùng `python-docx`). Frontend chỉ cần hiển thị preview từ API.
2. **Thumbnail:** Backend cần tạo thumbnail (first page as image) khi upload. Có dùng `docx2pdf` + `pdf-to-image` hoặc đơn giản là icon.
3. **AI Enhancement:** Nếu không có OpenAI key, dùng rule-based (detect headings by font size, extract keywords).
4. **Storage:** Backend lưu file vào `./uploads/documents/{user_id}/{uuid}.docx`. Trong production nên dùng S3.
5. **Security:** Validate file type, scan virus, rate limit upload.

---

**Đây là full frontend plan bro!** Tôi đã chia từng component, props, hooks, UI wireframe đầy đủ. 

**Bạn muốn tôi bắt đầu code implement ngay không?** Tôi sẽ tạo:

1. `frontend/src/pages/DocumentsPage.tsx`
2. `frontend/src/components/DOCX/...`
3. `frontend/src/hooks/useDocument*.ts`
4. `frontend/src/service/docxService.ts`

Chỉ cần bạn nói **"GO"** tôi bắt đầu! 🚀
