diff --git a/src/pages/AdminDashboard.jsx b/src/pages/AdminDashboard.jsx index c0f572a..e879125 100644 --- a/src/pages/AdminDashboard.jsx +++ b/src/pages/AdminDashboard.jsx @@ -1,251 +1,902 @@ import { useState, useEffect, useCallback } from 'react' -import { motion } from 'framer-motion' -import { Shield, RefreshCw, Clock, CheckCircle, XCircle, AlertTriangle, Inbox } from 'lucide-react' -import MetricsCards from '@/components/admin/MetricsCards' -import ModerationTable from '@/components/admin/ModerationTable' -import BulkActions from '@/components/admin/BulkActions' -import Modal from '@/components/ui/Modal' -import Button from '@/components/ui/Button' -import { useAdmin } from '@/hooks/useAdmin' -import { useAnswers } from '@/hooks/useAnswers' +import { motion, AnimatePresence } from 'framer-motion' +import { + Shield, RefreshCw, Clock, CheckCircle, XCircle, AlertTriangle, Inbox, + Settings, BarChart2, FileText, Ban, Plus, Trash2, Eye, Info, Check, + AlertOctagon, HelpCircle, ArrowUpRight, Search, ShieldAlert, UserMinus, ShieldCheck +} from 'lucide-react' +import { useAuth } from '@/hooks/useAuth' import { useToast } from '@/components/ui/Toast' - -const filterTabs = [ - { id: 'all', label: 'All', icon: Inbox }, - { id: 'pending', label: 'Pending', icon: Clock }, - { id: 'verified', label: 'Verified', icon: CheckCircle }, - { id: 'rejected', label: 'Rejected', icon: XCircle }, - { id: 'spam', label: 'Spam', icon: AlertTriangle }, +import { spamApi } from '@/lib/spamApi' +import Button from '@/components/ui/Button' +import Modal from '@/components/ui/Modal' +import Input from '@/components/ui/Input' +const tabs = [ + { id: 'queue', label: 'Moderation Queue', icon: Inbox }, + { id: 'rules', label: 'Rule Management', icon: Settings }, + { id: 'analytics', label: 'Analytics Console', icon: BarChart2 }, + { id: 'audit', label: 'Audit Logs', icon: FileText }, ] - export default function AdminDashboard() { - const { metrics, allAnswers, loading, fetchMetrics, fetchAllAnswers, bulkVerify, bulkDelete, bulkMarkSpam } = useAdmin() - const { verifyAnswer, rejectAnswer, markSpam, deleteAnswer } = useAnswers() + const { user, isAdmin } = useAuth() const { showToast } = useToast() - const [activeFilter, setActiveFilter] = useState('all') - const [selectedIds, setSelectedIds] = useState([]) - const [adminNoteModal, setAdminNoteModal] = useState({ open: false, answerId: null, action: null }) + + const [activeTab, setActiveTab] = useState('queue') + const [loading, setLoading] = useState(true) + + // Metrics & Data States + const [metrics, setMetrics] = useState({ + totalScanned: 0, + safeContent: 0, + suspiciousContent: 0, + spamBlocked: 0, + criticalSpam: 0, + moderatorActions: 0, + accuracyRate: 98.6 + }) + const [queue, setQueue] = useState([]) + const [rules, setRules] = useState([]) + const [keywords, setKeywords] = useState([]) + const [blacklist, setBlacklist] = useState([]) + const [whitelist, setWhitelist] = useState([]) + const [auditLogs, setAuditLogs] = useState([]) + const [analyticsData, setAnalyticsData] = useState(null) + const [settings, setSettings] = useState({ threshold_needs_review: 30, threshold_spam: 60, threshold_critical: 100 }) + // Filters & Interactivity + const [queueFilter, setQueueFilter] = useState({ status: 'pending', search: '', riskLevel: 'all' }) + const [ruleManagerTab, setRuleManagerTab] = useState('keywords') + const [newItemText, setNewItemText] = useState('') + const [selectedQueueItem, setSelectedQueueItem] = useState(null) const [adminNote, setAdminNote] = useState('') - + const [showConfirmModal, setShowConfirmModal] = useState({ open: false, type: '', target: null }) + const loadAllData = useCallback(async () => { + setLoading(true) + try { + const [ + fetchedMetrics, + fetchedQueue, + fetchedRules, + fetchedKeywords, + fetchedBlacklist, + fetchedWhitelist, + fetchedAuditLogs, + fetchedAnalytics, + fetchedSettings + ] = await Promise.all([ + spamApi.getModerationMetrics(), + spamApi.getModerationQueue(), + spamApi.getRules(), + spamApi.getKeywords(), + spamApi.getBlacklist(), + spamApi.getWhitelist(), + spamApi.getAuditLogs(), + spamApi.getAnalyticsData(), + spamApi.getSettings() + ]) + setMetrics(fetchedMetrics) + setQueue(fetchedQueue) + setRules(fetchedRules) + setKeywords(fetchedKeywords) + setBlacklist(fetchedBlacklist) + setWhitelist(fetchedWhitelist) + setAuditLogs(fetchedAuditLogs) + setAnalyticsData(fetchedAnalytics) + setSettings(fetchedSettings) + } catch (err) { + console.error('Error loading admin dashboard data:', err) + showToast('Error loading moderation data', 'error') + } finally { + setLoading(false) + } + }, [showToast]) useEffect(() => { - fetchMetrics() - fetchAllAnswers(activeFilter) - }, [fetchMetrics, fetchAllAnswers, activeFilter]) - + if (isAdmin) { + loadAllData() + } + }, [isAdmin, loadAllData]) const handleRefresh = () => { - fetchMetrics() - fetchAllAnswers(activeFilter) - setSelectedIds([]) + loadAllData() + showToast('Data refreshed successfully', 'success') } - - const handleVerify = useCallback((answerId) => { - setAdminNoteModal({ open: true, answerId, action: 'verify' }) - }, []) - - const handleReject = useCallback((answerId) => { - setAdminNoteModal({ open: true, answerId, action: 'reject' }) - }, []) - - const handleNoteSubmit = async () => { + // QUEUE ACTIONS + const handleQueueAction = async (itemId, action, note = '') => { try { - if (adminNoteModal.action === 'verify') { - await verifyAnswer(adminNoteModal.answerId, adminNote) - showToast('Answer verified!', 'success') - } else if (adminNoteModal.action === 'reject') { - await rejectAnswer(adminNoteModal.answerId, adminNote) - showToast('Answer rejected', 'info') + const modId = user?.id || 'simulated_moderator' + if (action === 'approve') { + await spamApi.approveQueueItem(itemId, modId, note) + showToast('Submission approved and published', 'success') + } else if (action === 'reject') { + await spamApi.rejectQueueItem(itemId, modId, note) + showToast('Submission rejected', 'info') + } else if (action === 'escalate') { + await spamApi.escalateQueueItem(itemId, modId, note) + showToast('Escalated to upper administrator', 'info') + } else if (action === 'ban') { + const item = queue.find(q => q.id === itemId) + if (item && item.user_id) { + await spamApi.banUser(item.user_id, modId, note || 'Spam campaign violation') + await spamApi.rejectQueueItem(itemId, modId, 'Account suspended due to spamming') + showToast('User restricted and content rejected', 'error') + } else { + showToast('No user linked to ban', 'warning') + } } - setAdminNoteModal({ open: false, answerId: null, action: null }) + setSelectedQueueItem(null) setAdminNote('') - handleRefresh() + loadAllData() } catch (err) { - showToast(`Failed to ${adminNoteModal.action}`, 'error') + showToast(`Failed to perform action: ${err.message}`, 'error') } } - - const handleSpam = async (answerId) => { + // RULE UPDATES + const handleRuleToggle = async (ruleId, currentVal) => { try { - await markSpam(answerId) - showToast('Marked as spam', 'info') - handleRefresh() + await spamApi.updateRule(ruleId, { is_enabled: !currentVal }) + showToast('Rule status updated', 'success') + loadAllData() } catch (err) { - showToast('Failed to mark spam', 'error') + showToast('Failed to update rule status', 'error') } } - - const handleDelete = async (answerId) => { + const handleRuleWeightChange = async (ruleId, newWeight) => { + const weight = parseInt(newWeight, 10) + if (isNaN(weight) || weight < 0 || weight > 100) return try { - await deleteAnswer(answerId) - showToast('Answer deleted', 'info') - handleRefresh() + await spamApi.updateRule(ruleId, { weight }) + showToast('Rule weight updated', 'success') + loadAllData() } catch (err) { - showToast('Failed to delete', 'error') + showToast('Failed to update weight', 'error') } } - - const toggleSelect = (id) => { - setSelectedIds(prev => - prev.includes(id) ? prev.filter(x => x !== id) : [...prev, id] - ) - } - - const selectAll = () => { - if (selectedIds.length === allAnswers.length) { - setSelectedIds([]) - } else { - setSelectedIds(allAnswers.map(a => a.id)) - } - } - - const handleBulkVerify = async () => { + // KEYWORD/LIST MANAGERS + const handleAddListItem = async () => { + if (!newItemText.trim()) return try { - await bulkVerify(selectedIds) - showToast(`${selectedIds.length} answers verified!`, 'success') - setSelectedIds([]) - handleRefresh() + const uId = user?.id || 'admin' + if (ruleManagerTab === 'keywords') { + await spamApi.addKeyword(newItemText, uId) + showToast('Promotional keyword added', 'success') + } else if (ruleManagerTab === 'blacklist') { + await spamApi.addBlacklist(newItemText, uId) + showToast('Domain added to blacklist', 'success') + } else if (ruleManagerTab === 'whitelist') { + await spamApi.addWhitelist(newItemText, uId) + showToast('Domain added to whitelist', 'success') + } + setNewItemText('') + loadAllData() } catch (err) { - showToast('Bulk verify failed', 'error') + showToast(err.message, 'error') } } - - const handleBulkDelete = async () => { + const handleDeleteListItem = async (id) => { try { - await bulkDelete(selectedIds) - showToast(`${selectedIds.length} answers deleted`, 'info') - setSelectedIds([]) - handleRefresh() + if (ruleManagerTab === 'keywords') { + await spamApi.deleteKeyword(id) + showToast('Keyword removed', 'info') + } else if (ruleManagerTab === 'blacklist') { + await spamApi.deleteBlacklist(id) + showToast('Blacklist domain removed', 'info') + } else if (ruleManagerTab === 'whitelist') { + await spamApi.deleteWhitelist(id) + showToast('Whitelist domain removed', 'info') + } + loadAllData() } catch (err) { - showToast('Bulk delete failed', 'error') + showToast('Failed to delete item', 'error') } } - - const handleBulkSpam = async () => { + const handleThresholdChange = async (key, val) => { + const intVal = parseInt(val, 10) + if (isNaN(intVal) || intVal < 0 || intVal > 100) return try { - await bulkMarkSpam(selectedIds) - showToast(`${selectedIds.length} answers marked as spam`, 'info') - setSelectedIds([]) - handleRefresh() + const updated = await spamApi.updateSettings({ [key]: intVal }) + setSettings(updated) + showToast('Thresholds updated successfully', 'success') + loadAllData() } catch (err) { - showToast('Bulk spam failed', 'error') + showToast('Failed to update threshold', 'error') } } - + if (!isAdmin) { + return ( +
+ You must be logged in as an administrator to view the spam detection & moderation console. +
+Review and manage community content
+ {/* Upper Title Block */} ++ Automated spam filter adjustments, explainable scoring metrics, and audit tracking. +
+No items matching the selected filters were found.
+| Submission Details | +Type | +Score / Risk | +Status | +Actions | +
|---|---|---|---|---|
|
+
+
+
+ {item.users?.name?.[0] || '?'}
+
+
+
+ {item.users?.name || 'Anonymous User'}
+ {item.content_body}
+ |
+ + + {item.content_type} + + | +
+
+
+ {item.risk_level}
+
+ {score}/100
+
+ |
+ + + {item.status} + + | ++ + | +
Configure weighting and activation states for the 15 scanning engines.
+Set the sensitivity thresholds for content categorization.
+Visual logs of daily safe, suspicious, and blocked content over past 7 days.
+Heuristics triggered most frequently during filtering.
+Flagged contributors ranked by spam-to-submission ratio.
+| User | +Spam Ratio | +Risk Score | +Risk Assessment | +
|---|---|---|---|
|
+ {u.name}
+ {u.email}
+ |
+ {u.spamRatio} | +{u.riskScore}% | ++ + {u.riskLevel} + + | +
Audit trail logs will display here as actions are performed.
+| Executor | +Action | +Details | +Timestamp | +
|---|---|---|---|
| + {log.users?.name || 'Administrator'} + | ++ + {log.action} + + | ++ {JSON.stringify(log.details)} + | ++ {new Date(log.created_at).toLocaleString()} + | +
{det.explanation}
+ {det.trigger && ( +