mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 08:11:13 -05:00
feat: Improve email thread display and status updates
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user