diff --git a/app/src/admin/CommentModeration.jsx b/app/src/admin/CommentModeration.jsx new file mode 100644 index 0000000..bb3a33c --- /dev/null +++ b/app/src/admin/CommentModeration.jsx @@ -0,0 +1,141 @@ +import { useState, useEffect, useCallback } from 'react'; +import { useNav, useToast, EmptyState } from '../../teknav-ui.jsx'; +import { adminApi } from '../lib/admin-api.js'; +import { BRAND, PanelState, actionBtn } from './Shared.jsx'; + +function CommentsModeration() { + const toast = useToast(); + const { navigate } = useNav(); + const [flaggedOnly, setFlaggedOnly] = useState(false); + const [items, setItems] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(false); + const [selected, setSelected] = useState(new Set()); + + const refresh = useCallback(() => { + setLoading(true); + setError(false); + setSelected(new Set()); + return adminApi.listComments({ flagged: flaggedOnly, limit: 50 }) + .then(res => setItems(res.items ?? [])) + .catch(() => { setItems([]); setError(true); }) + .finally(() => setLoading(false)); + }, [flaggedOnly]); + + useEffect(() => { refresh(); }, [refresh]); + + const toggleSelect = (id) => setSelected(prev => { + const next = new Set(prev); + next.has(id) ? next.delete(id) : next.add(id); + return next; + }); + + const toggleAll = () => setSelected(prev => prev.size === items.length ? new Set() : new Set(items.map(i => i.id))); + + const remove = async (id) => { + try { + await adminApi.deleteComment(id); + toast('نظر حذف شد'); + refresh(); + } catch { + toast('حذف نظر ناموفق بود', 'error'); + } + }; + + const unflag = async (id) => { + try { + await adminApi.unflagComment(id); + toast('گزارش‌های نظر پاک شد'); + refresh(); + } catch { + toast('پاک کردن گزارش‌ها ناموفق بود', 'error'); + } + }; + + const bulkAction = async (action) => { + const ids = [...selected]; + if (!ids.length) return; + try { + await adminApi.bulkModerateComments(ids, action); + toast(`${ids.length.toLocaleString('fa-IR')} نظر ${action === 'delete' ? 'حذف شد' : action === 'approve' ? 'تأیید شد' : 'علامت‌گذاری شد'}`); + refresh(); + } catch { + toast('عملیات دسته‌ای ناموفق بود', 'error'); + } + }; + + const bulkDeleteFlagged = async () => { + if (!window.confirm('همه نظرهای گزارش‌شده حذف شوند؟')) return; + try { + const res = await adminApi.deleteFlaggedComments(); + toast(`${(res?.deleted ?? 0).toLocaleString('fa-IR')} نظر حذف شد`); + refresh(); + } catch { + toast('حذف دسته‌ای ناموفق بود', 'error'); + } + }; + + return ( +
+
+ + {flaggedOnly && items.length > 0 && ( + + )} +
+ + {!loading && !error && items.length > 0 && ( + <> +
+ +
+
+ {items.map(item => ( +
+
+ toggleSelect(item.id)} style={{ marginTop: 4, flexShrink: 0, accentColor: BRAND.primary }} /> +
+
+
+
{item.authorName} @{item.authorUsername}
+ +
+ {item.time} +
+

{item.body}

+
+ {item.flagCount.toLocaleString('fa-IR')} گزارش + + +
+
+
+
+ ))} +
+ + )} + + {selected.size > 0 && ( +
+ {selected.size.toLocaleString('fa-IR')} مورد انتخاب شده +
+ + + + +
+ )} +
+ ); +} + +export { CommentsModeration };