Import source: app/src/admin/MediaLibrary
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
@@ -0,0 +1,89 @@
|
|||||||
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
|
import { useToast, ConfirmModal, inputStyle } from '../../teknav-ui.jsx';
|
||||||
|
import { adminApi } from '../lib/admin-api.js';
|
||||||
|
import { PanelState, actionBtn } from './Shared.jsx';
|
||||||
|
|
||||||
|
function MediaLibrary({ role }) {
|
||||||
|
const toast = useToast();
|
||||||
|
const [items, setItems] = useState([]);
|
||||||
|
const [query, setQuery] = useState('');
|
||||||
|
const [mimeType, setMimeType] = useState('');
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState(false);
|
||||||
|
const [confirm, setConfirm] = useState(null);
|
||||||
|
|
||||||
|
const refresh = useCallback(() => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(false);
|
||||||
|
return adminApi.listMedia({ q: query, mimeType, limit: 60 })
|
||||||
|
.then(res => setItems(res.items ?? []))
|
||||||
|
.catch(() => { setItems([]); setError(true); })
|
||||||
|
.finally(() => setLoading(false));
|
||||||
|
}, [query, mimeType]);
|
||||||
|
|
||||||
|
useEffect(() => { refresh(); }, [refresh]);
|
||||||
|
|
||||||
|
const copyUrl = async (url) => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(url);
|
||||||
|
toast('آدرس رسانه کپی شد');
|
||||||
|
} catch {
|
||||||
|
toast('کپی آدرس رسانه ناموفق بود', 'error');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteMedia = async (id) => {
|
||||||
|
try {
|
||||||
|
await adminApi.deleteMedia(id);
|
||||||
|
toast('رسانه حذف شد');
|
||||||
|
refresh();
|
||||||
|
} catch (e) {
|
||||||
|
toast(e.body?.error === 'media_in_use' ? 'این رسانه در محتوا یا پروفایل استفاده شده است' : 'حذف رسانه ناموفق بود', 'error');
|
||||||
|
} finally {
|
||||||
|
setConfirm(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{confirm && <ConfirmModal msg="آیا از حذف این رسانه مطمئن هستید؟" onConfirm={() => deleteMedia(confirm)} onCancel={() => setConfirm(null)} />}
|
||||||
|
<div style={{ display: 'flex', gap: 12, marginBottom: 20, flexWrap: 'wrap' }}>
|
||||||
|
<input value={query} onChange={e => setQuery(e.target.value)} placeholder="جستجوی نام فایل..." style={{ ...inputStyle, maxWidth: 260 }} dir="rtl" />
|
||||||
|
<select value={mimeType} onChange={e => setMimeType(e.target.value)} style={{ ...inputStyle, maxWidth: 180 }} dir="rtl">
|
||||||
|
<option value="">همه فایلها</option>
|
||||||
|
<option value="image/">تصویرها</option>
|
||||||
|
<option value="image/webp">WebP</option>
|
||||||
|
<option value="image/png">PNG</option>
|
||||||
|
<option value="image/jpeg">JPEG</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<PanelState loading={loading} error={error} empty={!loading && !error && items.length === 0} emptyTitle="هنوز رسانهای در بکاند ثبت نشده است" />
|
||||||
|
{!loading && !error && items.length > 0 && (
|
||||||
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill,minmax(220px,1fr))', gap: 16 }}>
|
||||||
|
{items.map(item => (
|
||||||
|
<div key={item.id} style={{ background: '#fff', border: '1px solid #E4DDD2', borderRadius: 12, overflow: 'hidden', direction: 'rtl' }}>
|
||||||
|
<div style={{ height: 138, background: '#F4EFE6', display: 'flex', alignItems: 'center', justifyContent: 'center', overflow: 'hidden' }}>
|
||||||
|
{item.mimeType?.startsWith('image/')
|
||||||
|
? <img src={item.url} alt={item.filename} style={{ width: '100%', height: '100%', objectFit: 'cover' }} loading="lazy" />
|
||||||
|
: <span style={{ color: '#5F6B6D', fontSize: 13 }}>{item.mimeType}</span>}
|
||||||
|
</div>
|
||||||
|
<div style={{ padding: 14 }}>
|
||||||
|
<div title={item.filename} style={{ fontSize: 13, fontWeight: 700, color: '#263238', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{item.filename}</div>
|
||||||
|
<div style={{ fontSize: 11, color: '#5F6B6D', marginTop: 6 }}>
|
||||||
|
{item.sizeBytes ? `${Math.round(item.sizeBytes / 1024).toLocaleString('fa-IR')} KB` : 'اندازه نامشخص'}
|
||||||
|
{item.width && item.height ? ` · ${item.width}×${item.height}` : ''}
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', gap: 8, marginTop: 12, flexWrap: 'wrap' }}>
|
||||||
|
<button onClick={() => copyUrl(item.url)} style={{ ...actionBtn, background: '#0F6B7318', color: '#0F6B73' }}>کپی URL</button>
|
||||||
|
{role === 'admin' && <button onClick={() => setConfirm(item.id)} style={{ ...actionBtn, background: '#C94C4C18', color: '#C94C4C' }}>حذف</button>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { MediaLibrary };
|
||||||
Reference in New Issue
Block a user