"use client"; import { useState, useRef } from "react"; import { Upload, Download, Loader2, CheckCircle2, XCircle } from "lucide-react"; import { apiPostFormData } from "@/lib/api/client"; interface ImportResult { filename: string; success: boolean; entryId?: string; slug?: string; title?: string; error?: string; } interface ImportResponse { success: boolean; totalFiles: number; imported: number; failed: number; results: ImportResult[]; } interface ImportExportActionsProps { selectedEntryIds?: string[]; onImportComplete?: () => void; } export function ImportExportActions({ selectedEntryIds = [], onImportComplete, }: ImportExportActionsProps): React.JSX.Element { const [isImporting, setIsImporting] = useState(false); const [isExporting, setIsExporting] = useState(false); const [importResult, setImportResult] = useState(null); const [showImportDialog, setShowImportDialog] = useState(false); const fileInputRef = useRef(null); /** * Handle import file selection */ const handleImportClick = (): void => { fileInputRef.current?.click(); }; /** * Handle file upload and import */ const handleFileChange = async (e: React.ChangeEvent): Promise => { const file = e.target.files?.[0]; if (!file) return; // Validate file type if (!file.name.endsWith(".md") && !file.name.endsWith(".zip")) { alert("Please upload a .md or .zip file"); return; } setIsImporting(true); setShowImportDialog(true); setImportResult(null); try { const formData = new FormData(); formData.append("file", file); // Use API client to ensure CSRF token is included const result = await apiPostFormData("/api/knowledge/import", formData); setImportResult(result); // Notify parent component if (result.imported > 0 && onImportComplete) { onImportComplete(); } } catch (error) { console.error("Import error:", error); alert(error instanceof Error ? error.message : "Failed to import file"); setShowImportDialog(false); } finally { setIsImporting(false); // Reset file input if (fileInputRef.current) { fileInputRef.current.value = ""; } } }; /** * Handle export */ const handleExport = async (format: "markdown" | "json" = "markdown"): Promise => { setIsExporting(true); try { // Build query params const params = new URLSearchParams({ format, }); // Add selected entry IDs if any if (selectedEntryIds.length > 0) { selectedEntryIds.forEach((id) => { params.append("entryIds", id); }); } const response = await fetch(`/api/knowledge/export?${params.toString()}`, { method: "GET", }); if (!response.ok) { throw new Error("Export failed"); } // Get filename from Content-Disposition header const contentDisposition = response.headers.get("Content-Disposition"); const filenameMatch = contentDisposition?.match(/filename="(.+)"/); const filename = filenameMatch?.[1] ?? `knowledge-export-${format}.zip`; // Download file const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); } catch (error) { console.error("Export error:", error); alert("Failed to export entries"); } finally { setIsExporting(false); } }; /** * Close import dialog */ const handleCloseImportDialog = (): void => { setShowImportDialog(false); setImportResult(null); }; return ( <> {/* Action Buttons */}
{/* Import Button */} {/* Export Dropdown */}
{/* Dropdown Menu */}
{selectedEntryIds.length > 0 && (
{selectedEntryIds.length} selected
)}
{/* Hidden File Input */} {/* Import Result Dialog */} {showImportDialog && (
{/* Header */}

{isImporting ? "Importing..." : "Import Results"}

{/* Content */}
{isImporting && (
Processing file...
)} {importResult && (
{/* Summary */}
Total Files
{importResult.totalFiles}
Imported
{importResult.imported}
Failed
{importResult.failed}
{/* Results List */} {importResult.results.length > 0 && (

Details

{importResult.results.map((result, index) => (
{result.success ? ( ) : ( )}
{result.title ?? result.filename}
{result.success ? (
{result.slug && `Slug: ${result.slug}`}
) : (
{result.error}
)}
))}
)}
)}
{/* Footer */} {!isImporting && (
)}
)} ); }