diff --git a/src/components/admin/VolunteerTable.js b/src/components/admin/VolunteerTable.js index 293bd42..616c2d0 100644 --- a/src/components/admin/VolunteerTable.js +++ b/src/components/admin/VolunteerTable.js @@ -1,4 +1,4 @@ -import React, { useMemo, useState, useCallback } from "react"; +import React, { useMemo, useState, useCallback, useEffect } from "react"; import { Table, TableBody, @@ -205,6 +205,8 @@ const VolunteerTable = ({ const [copyFeedback, setCopyFeedback] = useState({ open: false, message: '' }); const [resendStatuses, setResendStatuses] = useState({}); // { resend_id: { last_event, ... } } const [loadingResendStatus, setLoadingResendStatus] = useState({}); + const [resendEmailsByRecipient, setResendEmailsByRecipient] = useState({}); // { email: [{id, subject, created_at, last_event}] } + const [resendListLoaded, setResendListLoaded] = useState(false); const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('md')); const isSmallMobile = useMediaQuery(theme.breakpoints.down('sm')); @@ -285,6 +287,49 @@ const VolunteerTable = ({ } }, [accessToken, orgId, resendStatuses, loadingResendStatus]); + // Fetch all sent emails from Resend list API, indexed by recipient + const fetchResendEmailList = useCallback(async () => { + if (!accessToken || !orgId || !volunteers?.length) return; + + const uniqueEmails = [...new Set( + volunteers + .map(v => v.email) + .filter(e => e && e.includes('@')) + )]; + + if (uniqueEmails.length === 0) return; + + try { + const response = await fetch( + `${process.env.NEXT_PUBLIC_API_SERVER_URL}/api/admin/emails/resend-list?emails=${encodeURIComponent(uniqueEmails.join(','))}`, + { + headers: { + Authorization: `Bearer ${accessToken}`, + 'X-Org-Id': orgId, + }, + } + ); + + if (response.ok) { + const data = await response.json(); + const emailsByRecipient = data?.data?.emails_by_recipient || data?.emails_by_recipient; + if (emailsByRecipient) { + setResendEmailsByRecipient(emailsByRecipient); + } + } + } catch (err) { + console.warn('Failed to fetch Resend email list:', err); + } + }, [accessToken, orgId, volunteers]); + + // Fetch Resend email list once when volunteers load + useEffect(() => { + if (!resendListLoaded && volunteers?.length > 0 && accessToken && orgId) { + setResendListLoaded(true); + fetchResendEmailList(); + } + }, [volunteers, accessToken, orgId, resendListLoaded, fetchResendEmailList]); + // Helper to get sent emails from either new sent_emails or legacy messages_sent const getSentEmails = useCallback((volunteer) => { if (Array.isArray(volunteer.sent_emails) && volunteer.sent_emails.length > 0) { @@ -571,7 +616,25 @@ const VolunteerTable = ({ const sentEmails = getSentEmails(volunteer); const messageCount = sentEmails.length; - if (messageCount === 0) { + // Look up emails from Resend list API for this volunteer + const volunteerEmail = volunteer.email?.toLowerCase(); + const resendListEmails = volunteerEmail ? (resendEmailsByRecipient[volunteerEmail] || []) : []; + + // Delivery status chip rendering helper (shared by stored and list-based emails) + const eventMap = { + delivered: { label: 'Delivered', color: 'success' }, + sent: { label: 'Sent', color: 'info' }, + bounced: { label: 'Bounced', color: 'error' }, + complained: { label: 'Complained', color: 'error' }, + delivery_delayed: { label: 'Delayed', color: 'warning' }, + }; + + const renderResendEventChip = (lastEvent) => { + const display = eventMap[lastEvent] || { label: lastEvent || 'Unknown', color: 'default' }; + return ; + }; + + if (messageCount === 0 && resendListEmails.length === 0) { return ( { - // Check for Resend live status first + // Check for Resend live status first (from per-ID fetch) if (email.resend_id && resendStatuses[email.resend_id]) { const status = resendStatuses[email.resend_id]; - const eventMap = { - delivered: { label: 'Delivered', color: 'success' }, - sent: { label: 'Sent', color: 'info' }, - bounced: { label: 'Bounced', color: 'error' }, - complained: { label: 'Complained', color: 'error' }, - delivery_delayed: { label: 'Delayed', color: 'warning' }, - }; - const display = eventMap[status.last_event] || { label: status.last_event || 'Unknown', color: 'default' }; - return ; + return renderResendEventChip(status.last_event); } // Loading state if (email.resend_id && loadingResendStatus[email.resend_id]) { return ; } + // Try matching from Resend list data by subject when no resend_id + if (!email.resend_id && email.subject && resendListEmails.length > 0) { + const match = resendListEmails.find(re => re.subject === email.subject); + if (match) { + return renderResendEventChip(match.last_event); + } + } // Legacy delivery_status fallback if (email._legacy_delivery_status) { const ds = email._legacy_delivery_status; @@ -620,13 +682,18 @@ const VolunteerTable = ({ return null; }; + // Determine display count: stored messages + any additional from Resend list + const storedResendIds = new Set(sentEmails.filter(e => e.resend_id).map(e => e.resend_id)); + const additionalResendEmails = resendListEmails.filter(re => !storedResendIds.has(re.id)); + const totalDisplayCount = messageCount + additionalResendEmails.length; + const messagesToolTipContent = ( - Recent Messages ({messageCount}) + Recent Messages ({totalDisplayCount}) {sentEmails.slice(0, 5).map((email, idx) => ( - + {new Date(email.timestamp).toLocaleString()} @@ -643,15 +710,40 @@ const VolunteerTable = ({ ))} + {additionalResendEmails.length > 0 && ( + <> + + Via Resend + + {additionalResendEmails.slice(0, 5).map((re, idx) => ( + + + {new Date(re.created_at).toLocaleString()} + + + Subject: {re.subject} + + + {renderResendEventChip(re.last_event)} + + + ))} + {additionalResendEmails.length > 5 && ( + + ...and {additionalResendEmails.length - 5} more from Resend + + )} + + )} {messageCount > 5 && ( - ...and {messageCount - 5} more messages + ...and {messageCount - 5} more stored messages )} ); - // Fetch Resend statuses when the tooltip opens + // Fetch Resend statuses when the tooltip opens (supplementary per-ID fetch) const handleTooltipOpen = () => { const resendIds = sentEmails .filter(e => e.resend_id) @@ -679,7 +771,7 @@ const VolunteerTable = ({ }} >