feat: Improve email thread display and status updates

This commit is contained in:
gpt-engineer-app[bot]
2025-10-28 18:45:26 +00:00
parent f13013b8ee
commit 0257bccc45

View File

@@ -104,6 +104,7 @@ export default function AdminContact() {
const [loadingThreads, setLoadingThreads] = useState(false); const [loadingThreads, setLoadingThreads] = useState(false);
const [copiedTicket, setCopiedTicket] = useState<string | null>(null); const [copiedTicket, setCopiedTicket] = useState<string | null>(null);
const [activeTab, setActiveTab] = useState<string>('details'); const [activeTab, setActiveTab] = useState<string>('details');
const [replyStatus, setReplyStatus] = useState<string>('');
// Fetch contact submissions // Fetch contact submissions
const { data: submissions, isLoading } = useQuery({ const { data: submissions, isLoading } = useQuery({
@@ -143,6 +144,7 @@ export default function AdminContact() {
useEffect(() => { useEffect(() => {
if (selectedSubmission) { if (selectedSubmission) {
setLoadingThreads(true); setLoadingThreads(true);
// Force fresh fetch - no caching
supabase supabase
.from('contact_email_threads') .from('contact_email_threads')
.select('*') .select('*')
@@ -161,6 +163,7 @@ export default function AdminContact() {
setAdminNotes(selectedSubmission.admin_notes || ''); setAdminNotes(selectedSubmission.admin_notes || '');
setReplyBody(''); setReplyBody('');
setShowReplyForm(false); setShowReplyForm(false);
setReplyStatus(selectedSubmission.status); // Initialize reply status
} else { } else {
// Reset tab to details when dialog closes // Reset tab to details when dialog closes
setActiveTab('details'); setActiveTab('details');
@@ -169,19 +172,47 @@ export default function AdminContact() {
// Send reply mutation // Send reply mutation
const sendReplyMutation = useMutation({ const sendReplyMutation = useMutation({
mutationFn: async ({ submissionId, body }: { submissionId: string, body: string }) => { mutationFn: async ({ submissionId, body, newStatus }: { submissionId: string, body: string, newStatus?: string }) => {
const { data, error } = await invokeWithTracking( const { data, error } = await invokeWithTracking(
'send-admin-email-reply', 'send-admin-email-reply',
{ submissionId, replyBody: body }, { submissionId, replyBody: body },
undefined undefined
); );
if (error) throw error; if (error) throw error;
// Update status if changed
if (newStatus && selectedSubmission && newStatus !== selectedSubmission.status) {
const updateData: Record<string, unknown> = { status: newStatus };
if (newStatus === 'resolved' || newStatus === 'closed') {
const { data: { user } } = await supabase.auth.getUser();
updateData.resolved_at = new Date().toISOString();
updateData.resolved_by = user?.id;
}
const { error: statusError } = await supabase
.from('contact_submissions')
.update(updateData)
.eq('id', submissionId);
if (statusError) {
logger.error('Failed to update status', { error: statusError });
throw statusError;
}
}
return data; return data;
}, },
onSuccess: (_data, variables) => { onSuccess: (_data, variables) => {
const submission = submissions?.find(s => s.id === variables.submissionId); const submission = submissions?.find(s => s.id === variables.submissionId);
const ticketNumber = submission?.ticket_number || 'Unknown'; const ticketNumber = submission?.ticket_number || 'Unknown';
handleSuccess('Reply Sent', `Your email response has been sent for ticket ${ticketNumber}`);
let message = `Your email response has been sent for ticket ${ticketNumber}`;
if (variables.newStatus && selectedSubmission && variables.newStatus !== selectedSubmission.status) {
message += ` and status updated to ${variables.newStatus.replace('_', ' ')}`;
}
handleSuccess('Reply Sent', message);
setReplyBody(''); setReplyBody('');
setShowReplyForm(false); setShowReplyForm(false);
// Refetch threads // Refetch threads
@@ -287,6 +318,27 @@ export default function AdminContact() {
setAdminNotes(submission.admin_notes || ''); setAdminNotes(submission.admin_notes || '');
setActiveTab('thread'); setActiveTab('thread');
setShowReplyForm(true); setShowReplyForm(true);
setReplyStatus(submission.status);
};
const handleRefreshThreads = () => {
if (!selectedSubmission) return;
setLoadingThreads(true);
supabase
.from('contact_email_threads')
.select('*')
.eq('submission_id', selectedSubmission.id)
.order('created_at', { ascending: true })
.then(({ data, error }) => {
if (error) {
logger.error('Failed to refresh email threads', { error });
handleError(error, { action: 'Refresh Email Threads' });
} else {
setEmailThreads((data as EmailThread[]) || []);
handleSuccess('Refreshed', 'Email thread updated');
}
setLoadingThreads(false);
});
}; };
// Show loading state while roles are being fetched // Show loading state while roles are being fetched
@@ -601,9 +653,20 @@ export default function AdminContact() {
<TabsTrigger value="details" className="flex-1"> <TabsTrigger value="details" className="flex-1">
Details Details
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="thread" className="flex-1"> <TabsTrigger value="thread" className="flex-1 flex items-center gap-2">
Email Thread ({emailThreads.length}) Email Thread ({emailThreads.length})
</TabsTrigger> </TabsTrigger>
{activeTab === 'thread' && (
<Button
variant="ghost"
size="sm"
onClick={handleRefreshThreads}
disabled={loadingThreads}
className="ml-auto"
>
<RefreshCw className={`h-3 w-3 ${loadingThreads ? 'animate-spin' : ''}`} />
</Button>
)}
</TabsList> </TabsList>
<TabsContent value="details" className="flex-1 overflow-y-auto"> <TabsContent value="details" className="flex-1 overflow-y-auto">
@@ -734,11 +797,34 @@ export default function AdminContact() {
rows={6} rows={6}
className="mb-3 resize-none" className="mb-3 resize-none"
/> />
{/* Status Update Dropdown */}
<div className="mb-3">
<Label className="text-sm mb-2 block">Update Status (Optional)</Label>
<Select value={replyStatus} onValueChange={setReplyStatus}>
<SelectTrigger className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="pending">Pending</SelectItem>
<SelectItem value="in_progress">In Progress</SelectItem>
<SelectItem value="resolved">Resolved</SelectItem>
<SelectItem value="closed">Closed</SelectItem>
</SelectContent>
</Select>
{replyStatus !== selectedSubmission.status && (
<p className="text-xs text-muted-foreground mt-1">
Status will be updated to <span className="font-medium">{replyStatus.replace('_', ' ')}</span>
</p>
)}
</div>
<div className="flex gap-2"> <div className="flex gap-2">
<Button <Button
onClick={() => sendReplyMutation.mutate({ onClick={() => sendReplyMutation.mutate({
submissionId: selectedSubmission.id, submissionId: selectedSubmission.id,
body: replyBody body: replyBody,
newStatus: replyStatus
})} })}
disabled={sendReplyMutation.isPending || replyBody.length < 10} disabled={sendReplyMutation.isPending || replyBody.length < 10}
className="flex-1" className="flex-1"