diff --git a/CHANGELOG.md b/CHANGELOG.md index acb404c..11f00c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project follows [Semantic Versioning](https://semver.org/). ### Added -- Supervisors can now unassign a donation that was previously applied to a registration, from a new "Assigned donations" list on the Payments page. This reverses the allocation (the registration's balance goes back up and the donation becomes available again), reverting the registration's status and revoking any tickets issued only because that allocation completed payment — blocked if a ticket has already been scanned. The registrant is notified by email/WhatsApp, mirroring the notification sent when a donation is first applied. +- Supervisors can now unassign a donation that was previously applied to a registration, from a new "Assigned donations" list on the Payments page. This reverses the allocation (the registration's balance goes back up and the donation becomes available again), reverting the registration's status and revoking any tickets issued only because that allocation completed payment — blocked if a ticket has already been scanned. The registrant is notified by email/WhatsApp, mirroring the notification sent when a donation is first applied. The list can be searched (registrant, donor, or event) and filtered by event. ### Fixed diff --git a/frontend/src/app/dashboard/supervisor/payments/page.tsx b/frontend/src/app/dashboard/supervisor/payments/page.tsx index 3d01b4c..e94d4de 100644 --- a/frontend/src/app/dashboard/supervisor/payments/page.tsx +++ b/frontend/src/app/dashboard/supervisor/payments/page.tsx @@ -1026,8 +1026,10 @@ function AssignedDonationsList({ payments, onDone }: AssignedDonationsListProps) const { token } = useAuth(); const [unassigningId, setUnassigningId] = useState(null); const [err, setErr] = useDismissingState(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(); + 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)
Assigned donations
{err &&
{err}
} - {legs.length === 0 ? ( + {allLegs.length > 0 && ( +
+ setQuery(e.target.value)} + /> + +
+ )} + {allLegs.length === 0 ? (
No donations have been assigned to registrations yet.
+ ) : legs.length === 0 ? ( +
No assigned donations match your search.
) : (
    {legs.map((leg: any) => {