import { useState, useEffect, useRef, useCallback } from 'react'; import { Upload, Image as ImageIcon, Trash2, Copy, CheckCircle2, Search, X, FolderOpen } from 'lucide-react'; import { toast } from 'react-hot-toast'; import api, { BASE_DOMAIN } from '../services/api'; import Spinner from '../components/ui/Spinner'; import Pagination from '../components/ui/Pagination'; import ConfirmModal from '../components/ui/ConfirmModal'; interface Media { id: string; url: string; filename: string; mimetype: string; size: number; altText?: string; title?: string; description?: string; caption?: string; createdAt: string; } export default function MediaManager() { const [mediaList, setMediaList] = useState([]); const [isLoading, setIsLoading] = useState(true); const [isUploading, setIsUploading] = useState(false); const [search, setSearch] = useState(''); const [page, setPage] = useState(1); const limit = 10; const [copiedId, setCopiedId] = useState(null); const [isDragOver, setIsDragOver] = useState(false); const [previewUrl, setPreviewUrl] = useState(null); const fileInputRef = useRef(null); const fetchMedia = useCallback(async () => { try { const res = await api.get('/admin/media'); setMediaList(Array.isArray(res.data) ? res.data : res.data?.data || []); } catch (error) { console.error('Failed to fetch media', error); toast.error('خطا در دریافت رسانه‌ها'); } }, []); const uploadFile = useCallback(async (file: File) => { const formData = new FormData(); formData.append('file', file); try { setIsUploading(true); await api.post('/admin/media/upload', formData, { headers: { 'Content-Type': 'multipart/form-data' } }); toast.success('تصویر با موفقیت آپلود شد'); fetchMedia(); } catch (error) { console.error('Upload failed', error); toast.error('خطا در آپلود فایل'); } finally { setIsUploading(false); } }, [fetchMedia]); useEffect(() => { let isSubscribed = true; api.get('/admin/media').then(res => { if (isSubscribed) setMediaList(Array.isArray(res.data) ? res.data : res.data?.data || []); }).catch(error => { console.error('Failed to fetch media', error); toast.error('خطا در دریافت رسانه‌ها'); }).finally(() => { if (isSubscribed) setIsLoading(false); }); return () => { isSubscribed = false; }; }, []); useEffect(() => { const handlePaste = (e: ClipboardEvent) => { const items = e.clipboardData?.items; if (!items) return; for (const item of Array.from(items)) { if (item.type.startsWith('image/')) { e.preventDefault(); const file = item.getAsFile(); if (!file) continue; uploadFile(file); break; } } }; document.addEventListener('paste', handlePaste); return () => document.removeEventListener('paste', handlePaste); }, [uploadFile]); const [deleteTargetId, setDeleteTargetId] = useState(null); const [editingSeoMedia, setEditingSeoMedia] = useState(null); const [seoFormData, setSeoFormData] = useState({ altText: '', title: '', description: '', caption: '' }); const handleSaveSeo = async () => { if (!editingSeoMedia) return; try { await api.put(`/admin/media/${editingSeoMedia.id}`, seoFormData); toast.success('تنظیمات سئوی تصویر با موفقیت ذخیره شد'); fetchMedia(); setEditingSeoMedia(null); } catch { toast.error('خطا در ذخیره تنظیمات سئوی تصویر'); } }; const handleUpload = async (e: React.ChangeEvent) => { if (!e.target.files || e.target.files.length === 0) return; await uploadFile(e.target.files[0]); if (fileInputRef.current) fileInputRef.current.value = ''; }; const handleDrop = async (e: React.DragEvent) => { e.preventDefault(); setIsDragOver(false); const file = e.dataTransfer.files[0]; if (file && file.type.startsWith('image/')) { await uploadFile(file); } }; const confirmDelete = async () => { if (!deleteTargetId) return; try { await api.delete(`/admin/media/${deleteTargetId}`); toast.success('تصویر با موفقیت حذف شد'); setMediaList(prev => prev.filter(m => m.id !== deleteTargetId)); } catch (error) { console.error('Delete failed', error); toast.error('خطا در حذف تصویر'); } finally { setDeleteTargetId(null); } }; const handleCopyUrl = async (media: Media) => { const fullUrl = media.url.startsWith('http') ? media.url : `${BASE_DOMAIN}${media.url}`; try { await navigator.clipboard.writeText(fullUrl); setCopiedId(media.id); setTimeout(() => setCopiedId(null), 2000); } catch { const textarea = document.createElement('textarea'); textarea.value = fullUrl; document.body.appendChild(textarea); textarea.select(); document.execCommand('copy'); document.body.removeChild(textarea); setCopiedId(media.id); setTimeout(() => setCopiedId(null), 2000); } }; const formatSize = (bytes: number) => { if (bytes < 1024) return bytes + ' B'; if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'; return (bytes / (1024 * 1024)).toFixed(1) + ' MB'; }; const filtered = mediaList.filter(m => !search || m.filename.toLowerCase().includes(search.toLowerCase()) ); const totalSize = mediaList.reduce((sum, m) => sum + m.size, 0); return (

مدیریت رسانه

{mediaList.length} فایل · {formatSize(totalSize)} فضا اشغال شده

Ctrl+V برای چسباندن تصویر
setSearch(e.target.value)} className="w-full bg-white border border-gray-200 rounded-xl py-3 pr-11 pl-4 text-sm focus:outline-none focus:ring-2 focus:ring-purple-500/20" />
{ e.preventDefault(); setIsDragOver(true); }} onDragLeave={() => setIsDragOver(false)} onDrop={handleDrop} className={`rounded-2xl border-2 border-dashed transition-colors ${ isDragOver ? 'border-purple-400 bg-purple-50' : 'border-gray-200 bg-white' }`} > {isLoading ? (
) : filtered.length === 0 ? (

{search ? 'فایلی با این نام یافت نشد' : 'گالری خالی است. اولین عکس را آپلود کنید!'}

) : (() => { const totalPages = Math.ceil(filtered.length / limit) || 1; const paginatedList = filtered.slice((page - 1) * limit, page * limit); return (
{paginatedList.map(media => { const imgUrl = media.url.startsWith('http') ? media.url : `${BASE_DOMAIN}${media.url}`; return (
setPreviewUrl(imgUrl)} >
{media.filename}

{media.filename}

{formatSize(media.size)}

{new Date(media.createdAt).toLocaleDateString('fa-IR')}

); })}
); })()}
{previewUrl && (
setPreviewUrl(null)} >
Preview
)} {editingSeoMedia && (

تنظیمات سئوی تصویر

setSeoFormData({ ...seoFormData, altText: e.target.value })} placeholder="مثال: عکس مکمل کانیدروکس گپ کانینا" className="w-full px-3 py-2 border rounded-xl text-sm" />
setSeoFormData({ ...seoFormData, title: e.target.value })} placeholder="عنوان تصویر برای تولتیپ هور" className="w-full px-3 py-2 border rounded-xl text-sm" />