Add search and event filters to admin email logs.
Let admins find logs by recipient or subject and narrow results by event on the Email Logs tab.
This commit is contained in:
@@ -22,6 +22,7 @@ import {
|
||||
ChevronRightIcon,
|
||||
XMarkIcon,
|
||||
ArrowPathIcon,
|
||||
MagnifyingGlassIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
import toast from 'react-hot-toast';
|
||||
import clsx from 'clsx';
|
||||
@@ -55,6 +56,9 @@ export default function AdminEmailsPage() {
|
||||
const [logsOffset, setLogsOffset] = useState(0);
|
||||
const [logsTotal, setLogsTotal] = useState(0);
|
||||
const [logsSubTab, setLogsSubTab] = useState<'all' | 'failed'>('all');
|
||||
const [logsSearch, setLogsSearch] = useState('');
|
||||
const [debouncedSearch, setDebouncedSearch] = useState('');
|
||||
const [logsEventFilter, setLogsEventFilter] = useState('');
|
||||
const [resendingLogId, setResendingLogId] = useState<string | null>(null);
|
||||
const [selectedLog, setSelectedLog] = useState<EmailLog | null>(null);
|
||||
|
||||
@@ -214,11 +218,20 @@ export default function AdminEmailsPage() {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handle = setTimeout(() => setDebouncedSearch(logsSearch), 300);
|
||||
return () => clearTimeout(handle);
|
||||
}, [logsSearch]);
|
||||
|
||||
useEffect(() => {
|
||||
setLogsOffset(0);
|
||||
}, [debouncedSearch, logsEventFilter]);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTab === 'logs') {
|
||||
loadLogs();
|
||||
}
|
||||
}, [activeTab, logsOffset, logsSubTab]);
|
||||
}, [activeTab, logsOffset, logsSubTab, debouncedSearch, logsEventFilter]);
|
||||
|
||||
const loadData = async () => {
|
||||
try {
|
||||
@@ -241,6 +254,8 @@ export default function AdminEmailsPage() {
|
||||
limit: 20,
|
||||
offset: logsOffset,
|
||||
...(logsSubTab === 'failed' ? { status: 'failed' } : {}),
|
||||
...(debouncedSearch.trim() ? { search: debouncedSearch.trim() } : {}),
|
||||
...(logsEventFilter ? { eventId: logsEventFilter } : {}),
|
||||
});
|
||||
setLogs(res.logs);
|
||||
setLogsTotal(res.pagination.total);
|
||||
@@ -757,6 +772,41 @@ export default function AdminEmailsPage() {
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* Filters: search + event */}
|
||||
<div className="flex flex-col md:flex-row gap-3 mb-4">
|
||||
<div className="relative flex-1">
|
||||
<MagnifyingGlassIcon className="w-5 h-5 text-gray-400 absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none" />
|
||||
<input
|
||||
type="text"
|
||||
value={logsSearch}
|
||||
onChange={(e) => setLogsSearch(e.target.value)}
|
||||
placeholder="Search by recipient or subject..."
|
||||
className="w-full pl-10 pr-10 py-3 rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow"
|
||||
/>
|
||||
{logsSearch && (
|
||||
<button
|
||||
onClick={() => setLogsSearch('')}
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 p-1.5 hover:bg-gray-100 rounded-btn"
|
||||
title="Clear search"
|
||||
>
|
||||
<XMarkIcon className="w-4 h-4 text-gray-400" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<select
|
||||
value={logsEventFilter}
|
||||
onChange={(e) => setLogsEventFilter(e.target.value)}
|
||||
className="w-full md:w-64 px-4 py-3 rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow"
|
||||
>
|
||||
<option value="">All events</option>
|
||||
{events.map((event) => (
|
||||
<option key={event.id} value={event.id}>
|
||||
{event.title}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Desktop: Table */}
|
||||
<Card className="overflow-hidden hidden md:block">
|
||||
<div className="overflow-x-auto">
|
||||
@@ -772,7 +822,7 @@ export default function AdminEmailsPage() {
|
||||
</thead>
|
||||
<tbody className="divide-y divide-secondary-light-gray">
|
||||
{logs.length === 0 ? (
|
||||
<tr><td colSpan={5} className="px-4 py-12 text-center text-gray-500 text-sm">{logsSubTab === 'failed' ? 'No failed emails' : 'No emails sent yet'}</td></tr>
|
||||
<tr><td colSpan={5} className="px-4 py-12 text-center text-gray-500 text-sm">{(debouncedSearch.trim() || logsEventFilter) ? 'No emails match your filters' : logsSubTab === 'failed' ? 'No failed emails' : 'No emails sent yet'}</td></tr>
|
||||
) : (
|
||||
logs.map((log) => (
|
||||
<tr key={log.id} className="hover:bg-gray-50">
|
||||
@@ -829,7 +879,7 @@ export default function AdminEmailsPage() {
|
||||
{/* Mobile: Card List */}
|
||||
<div className="md:hidden space-y-2">
|
||||
{logs.length === 0 ? (
|
||||
<div className="text-center py-10 text-gray-500 text-sm">{logsSubTab === 'failed' ? 'No failed emails' : 'No emails sent yet'}</div>
|
||||
<div className="text-center py-10 text-gray-500 text-sm">{(debouncedSearch.trim() || logsEventFilter) ? 'No emails match your filters' : logsSubTab === 'failed' ? 'No failed emails' : 'No emails sent yet'}</div>
|
||||
) : (
|
||||
logs.map((log) => (
|
||||
<Card key={log.id} className="p-3" onClick={() => setSelectedLog(log)}>
|
||||
|
||||
Reference in New Issue
Block a user