This commit is contained in:
jze9
2026-05-15 20:12:44 +05:00
parent 2335497226
commit dd7d8a7ccf
21 changed files with 672 additions and 121 deletions

View File

@@ -1,24 +1,35 @@
import { useState, useEffect, useRef } from 'react'
import { Upload, X, ImageIcon, Video, Loader2, Check } from 'lucide-react'
import { Upload, X, ImageIcon, Video, Loader2, Check, Search } from 'lucide-react'
import { api } from '../api'
export function MediaModal({ type, token, onSelect, onClose }) {
const [items, setItems] = useState([])
const [allItems, setAllItems] = useState([])
const [loading, setLoading] = useState(true)
const [uploading, setUploading] = useState(false)
const [search, setSearch] = useState('')
const fileRef = useRef(null)
useEffect(() => {
api.listMedia(token, type)
.then(data => { setItems(data); setLoading(false) })
.then(data => { setAllItems(data); setItems(data); setLoading(false) })
.catch(() => setLoading(false))
}, [])
useEffect(() => {
if (!search.trim()) {
setItems(allItems)
} else {
const q = search.toLowerCase()
setItems(allItems.filter(it => it.filename?.toLowerCase().includes(q)))
}
}, [search, allItems])
const handleUpload = async file => {
setUploading(true)
try {
const res = await api.uploadMedia(file, token)
setItems(prev => [res, ...prev])
setAllItems(prev => [res, ...prev])
} catch {}
setUploading(false)
}
@@ -64,6 +75,29 @@ export function MediaModal({ type, token, onSelect, onClose }) {
</div>
</div>
{/* Search bar */}
<div className="px-5 py-3 border-b border-slate-100">
<div className="relative">
<Search size={14} className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-400" />
<input
type="text"
value={search}
onChange={e => setSearch(e.target.value)}
placeholder="Поиск по имени файла..."
className="w-full pl-8 pr-4 py-1.5 text-sm border border-slate-200 rounded-lg outline-none focus:border-indigo-400 focus:ring-1 focus:ring-indigo-200"
/>
{search && (
<button
type="button"
onClick={() => setSearch('')}
className="absolute right-2 top-1/2 -translate-y-1/2 p-0.5 hover:bg-slate-100 rounded"
>
<X size={12} className="text-slate-400" />
</button>
)}
</div>
</div>
{/* Content */}
<div className="flex-1 overflow-y-auto p-4">
{loading ? (
@@ -73,14 +107,16 @@ export function MediaModal({ type, token, onSelect, onClose }) {
) : isEmpty ? (
<div className="flex flex-col items-center justify-center h-48 text-slate-300 gap-3">
{isImage ? <ImageIcon size={36} /> : <Video size={36} />}
<p className="text-sm">Файлы не найдены</p>
<button
type="button"
onClick={() => fileRef.current?.click()}
className="text-xs text-indigo-500 hover:text-indigo-600 underline"
>
Загрузить первый файл
</button>
<p className="text-sm">{search ? 'Ничего не найдено' : 'Файлы не найдены'}</p>
{!search && (
<button
type="button"
onClick={() => fileRef.current?.click()}
className="text-xs text-indigo-500 hover:text-indigo-600 underline"
>
Загрузить первый файл
</button>
)}
</div>
) : (
<div className={`grid gap-2.5 ${isImage ? 'grid-cols-4 sm:grid-cols-5' : 'grid-cols-2 sm:grid-cols-3'}`}>
@@ -101,14 +137,12 @@ export function MediaModal({ type, token, onSelect, onClose }) {
) : (
<video src={item.url} className="w-full h-full object-cover" />
)}
{/* Hover overlay */}
<div className="absolute inset-0 bg-indigo-600/0 group-hover:bg-indigo-600/25 transition-colors flex items-center justify-center">
<Check
size={22}
className="text-white opacity-0 group-hover:opacity-100 drop-shadow-lg transition-opacity"
/>
</div>
{/* Filename tooltip */}
<div className="absolute bottom-0 inset-x-0 px-2 py-1.5 bg-gradient-to-t from-black/60 to-transparent opacity-0 group-hover:opacity-100 transition-opacity">
<p className="text-white text-[10px] truncate leading-none">{item.filename}</p>
</div>