import { useState } from 'react'; import { Title, Text, Card, Table, Group, Stack, Badge, Loader, Center, Select, TextInput, Textarea, Button, Modal, SimpleGrid, ActionIcon, Tooltip, Paper, } from '@mantine/core'; import { useDisclosure } from '@mantine/hooks'; import { notifications } from '@mantine/notifications'; import { IconBulb, IconSearch, IconNote, IconFilter, } from '@tabler/icons-react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import api from '../../services/api'; interface AdminIdea { id: string; title: string; description: string | null; status: string; createdAt: string; adminNote: string | null; orgId: string; orgName: string; userId: string; userEmail: string; userFirstName: string; userLastName: string; } const statusColor: Record = { new: 'blue', reviewed: 'yellow', accepted: 'green', rejected: 'red', }; const statusOptions = [ { value: 'new', label: 'New' }, { value: 'reviewed', label: 'Reviewed' }, { value: 'accepted', label: 'Accepted' }, { value: 'rejected', label: 'Rejected' }, ]; function formatDate(dateStr: string | null | undefined): string { if (!dateStr) return '—'; return new Date(dateStr).toLocaleDateString(); } function formatDateTime(dateStr: string | null | undefined): string { if (!dateStr) return '—'; return new Date(dateStr).toLocaleString(); } export function AdminIdeasPage() { const [search, setSearch] = useState(''); const [statusFilter, setStatusFilter] = useState(null); const [selectedIdea, setSelectedIdea] = useState(null); const [detailOpened, { open: openDetail, close: closeDetail }] = useDisclosure(false); const [noteText, setNoteText] = useState(''); const queryClient = useQueryClient(); const { data: ideas, isLoading } = useQuery({ queryKey: ['admin-ideas'], queryFn: async () => { const { data } = await api.get('/admin/ideas'); return data; }, }); const updateStatus = useMutation({ mutationFn: async ({ id, status }: { id: string; status: string }) => { await api.put(`/admin/ideas/${id}/status`, { status }); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin-ideas'] }); notifications.show({ message: 'Status updated', color: 'green' }); }, }); const updateNote = useMutation({ mutationFn: async ({ id, adminNote }: { id: string; adminNote: string }) => { await api.put(`/admin/ideas/${id}/note`, { adminNote }); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin-ideas'] }); notifications.show({ message: 'Note saved', color: 'green' }); }, }); const openIdeaDetail = (idea: AdminIdea) => { setSelectedIdea(idea); setNoteText(idea.adminNote || ''); openDetail(); }; const handleSaveNote = () => { if (selectedIdea) { updateNote.mutate({ id: selectedIdea.id, adminNote: noteText }); } }; const filtered = (ideas || []).filter((idea) => { const matchesSearch = !search || idea.title.toLowerCase().includes(search.toLowerCase()) || idea.description?.toLowerCase().includes(search.toLowerCase()) || idea.orgName.toLowerCase().includes(search.toLowerCase()) || idea.userEmail.toLowerCase().includes(search.toLowerCase()); const matchesStatus = !statusFilter || idea.status === statusFilter; return matchesSearch && matchesStatus; }); const counts = { total: ideas?.length || 0, new: ideas?.filter(i => i.status === 'new').length || 0, reviewed: ideas?.filter(i => i.status === 'reviewed').length || 0, accepted: ideas?.filter(i => i.status === 'accepted').length || 0, rejected: ideas?.filter(i => i.status === 'rejected').length || 0, }; if (isLoading) { return
; } return ( Idea Submissions {counts.total} total {/* Summary cards */} New {counts.new} Reviewed {counts.reviewed} Accepted {counts.accepted} Rejected {counts.rejected} {/* Filters */} } value={search} onChange={(e) => setSearch(e.currentTarget.value)} style={{ flex: 1 }} /> { if (val && val !== selectedIdea.status) { updateStatus.mutate({ id: selectedIdea.id, status: val }, { onSuccess: () => { setSelectedIdea({ ...selectedIdea, status: val }); }, }); } }} w={200} /> Private Admin Note Only visible to super admins