Add search and event filter to the Assigned donations list
Finding a specific leg to unassign meant scanning the whole list by eye. Adds a text search (matches registrant, donor, or event) and an event dropdown (built from events that actually have assigned legs) above the list. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1026,8 +1026,10 @@ function AssignedDonationsList({ payments, onDone }: AssignedDonationsListProps)
|
||||
const { token } = useAuth();
|
||||
const [unassigningId, setUnassigningId] = useState<string | null>(null);
|
||||
const [err, setErr] = useDismissingState<string | null>(null);
|
||||
const [query, setQuery] = useState("");
|
||||
const [eventFilter, setEventFilter] = useState("");
|
||||
|
||||
const legs = useMemo(() => {
|
||||
const allLegs = useMemo(() => {
|
||||
return payments
|
||||
.filter(isDonationLeg)
|
||||
.sort((a: any, b: any) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
||||
@@ -1039,6 +1041,36 @@ function AssignedDonationsList({ payments, onDone }: AssignedDonationsListProps)
|
||||
return m;
|
||||
}, [payments]);
|
||||
|
||||
// Events with at least one assigned leg — built from the legs themselves so the dropdown
|
||||
// never shows an event with nothing to filter down to.
|
||||
const eventOptions = useMemo(() => {
|
||||
const m = new Map<string, string>();
|
||||
allLegs.forEach((leg: any) => {
|
||||
const ev = leg.registration?.event;
|
||||
if (ev?.id) m.set(String(ev.id), ev.title || String(ev.id));
|
||||
});
|
||||
return Array.from(m.entries()).sort((a, b) => a[1].localeCompare(b[1], undefined, { sensitivity: 'base' }));
|
||||
}, [allLegs]);
|
||||
|
||||
const legs = useMemo(() => {
|
||||
const q = query.trim().toLowerCase();
|
||||
return allLegs.filter((leg: any) => {
|
||||
if (eventFilter && String(leg.registration?.eventId || leg.registration?.event?.id) !== eventFilter) return false;
|
||||
if (!q) return true;
|
||||
const donation = donationById.get(leg.originalPaymentId);
|
||||
const haystack = [
|
||||
leg.registration?.user?.name,
|
||||
leg.registration?.user?.email,
|
||||
leg.registration?.event?.title,
|
||||
donation?.user?.name,
|
||||
donation?.user?.email,
|
||||
leg.registrationId,
|
||||
leg.originalPaymentId,
|
||||
].filter(Boolean).join(' ').toLowerCase();
|
||||
return haystack.includes(q);
|
||||
});
|
||||
}, [allLegs, donationById, query, eventFilter]);
|
||||
|
||||
const unassign = async (leg: any) => {
|
||||
if (!token) return;
|
||||
setErr(null);
|
||||
@@ -1063,8 +1095,28 @@ function AssignedDonationsList({ payments, onDone }: AssignedDonationsListProps)
|
||||
<div className="border rounded-xl p-4 bg-white shadow-sm">
|
||||
<div className="text-lg font-semibold mb-3">Assigned donations</div>
|
||||
{err && <div className="p-2 mb-2 text-xs bg-red-50 text-red-700 border rounded">{err}</div>}
|
||||
{legs.length === 0 ? (
|
||||
{allLegs.length > 0 && (
|
||||
<div className="flex flex-col sm:flex-row gap-2 mb-3">
|
||||
<input
|
||||
className="flex-1 border rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-brand-400"
|
||||
placeholder="Search by registrant, donor, event…"
|
||||
value={query}
|
||||
onChange={e => setQuery(e.target.value)}
|
||||
/>
|
||||
<select
|
||||
className="w-full sm:w-56 border rounded px-3 py-2 text-sm shrink-0"
|
||||
value={eventFilter}
|
||||
onChange={e => setEventFilter(e.target.value)}
|
||||
>
|
||||
<option value="">All events</option>
|
||||
{eventOptions.map(([id, title]) => (<option key={id} value={id}>{title}</option>))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
{allLegs.length === 0 ? (
|
||||
<div className="text-sm text-gray-500">No donations have been assigned to registrations yet.</div>
|
||||
) : legs.length === 0 ? (
|
||||
<div className="text-sm text-gray-500">No assigned donations match your search.</div>
|
||||
) : (
|
||||
<ul className="text-sm space-y-2 max-h-[420px] overflow-auto pr-2">
|
||||
{legs.map((leg: any) => {
|
||||
|
||||
Reference in New Issue
Block a user