mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 06:31: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 [copiedTicket, setCopiedTicket] = useState<string | null>(null);
|
||||
const [activeTab, setActiveTab] = useState<string>('details');
|
||||
const [replyStatus, setReplyStatus] = useState<string>('');
|
||||
|
||||
// Fetch contact submissions
|
||||
const { data: submissions, isLoading } = useQuery({
|
||||
@@ -143,6 +144,7 @@ export default function AdminContact() {
|
||||
useEffect(() => {
|
||||
if (selectedSubmission) {
|
||||
setLoadingThreads(true);
|
||||
// Force fresh fetch - no caching
|
||||
supabase
|
||||
.from('contact_email_threads')
|
||||
.select('*')
|
||||
@@ -161,6 +163,7 @@ export default function AdminContact() {
|
||||
setAdminNotes(selectedSubmission.admin_notes || '');
|
||||
setReplyBody('');
|
||||
setShowReplyForm(false);
|
||||
setReplyStatus(selectedSubmission.status); // Initialize reply status
|
||||
} else {
|
||||
// Reset tab to details when dialog closes
|
||||
setActiveTab('details');
|
||||
@@ -169,19 +172,47 @@ export default function AdminContact() {
|
||||
|
||||
// Send reply mutation
|
||||
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(
|
||||
'send-admin-email-reply',
|
||||
{ submissionId, replyBody: body },
|
||||
undefined
|
||||
);
|
||||
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;
|
||||
},
|
||||
onSuccess: (_data, variables) => {
|
||||
const submission = submissions?.find(s => s.id === variables.submissionId);
|
||||
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('');
|
||||
setShowReplyForm(false);
|
||||
// Refetch threads
|
||||
@@ -287,6 +318,27 @@ export default function AdminContact() {
|
||||
setAdminNotes(submission.admin_notes || '');
|
||||
setActiveTab('thread');
|
||||
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
|
||||
@@ -601,9 +653,20 @@ export default function AdminContact() {
|
||||
<TabsTrigger value="details" className="flex-1">
|
||||
Details
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="thread" className="flex-1">
|
||||
<TabsTrigger value="thread" className="flex-1 flex items-center gap-2">
|
||||
Email Thread ({emailThreads.length})
|
||||
</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>
|
||||
|
||||
<TabsContent value="details" className="flex-1 overflow-y-auto">
|
||||
@@ -734,11 +797,34 @@ export default function AdminContact() {
|
||||
rows={6}
|
||||
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">
|
||||
<Button
|
||||
onClick={() => sendReplyMutation.mutate({
|
||||
submissionId: selectedSubmission.id,
|
||||
body: replyBody
|
||||
body: replyBody,
|
||||
newStatus: replyStatus
|
||||
})}
|
||||
disabled={sendReplyMutation.isPending || replyBody.length < 10}
|
||||
className="flex-1"
|
||||
|
||||
Reference in New Issue
Block a user