import { useState } from 'react'; import { View, Text, ScrollView, TextInput, TouchableOpacity, ActivityIndicator, Alert, Modal, } from 'react-native'; import { useLocalSearchParams, router } from 'expo-router'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { api } from '../../../services/api'; const STATUS_FLOW = ['OPEN', 'IN_PROGRESS', 'RESOLVED', 'CLOSED'] as const; type TicketStatus = typeof STATUS_FLOW[number]; const STATUS_STYLE: Record = { OPEN: { bg: '#EFF6FF', text: '#2563EB' }, IN_PROGRESS: { bg: '#FFFBEB', text: '#D97706' }, RESOLVED: { bg: '#F0FDF4', text: '#16A34A' }, CLOSED: { bg: '#F3F4F6', text: '#6B7280' }, }; const PRIORITY_COLOR: Record = { HIGH: '#DC2626', MEDIUM: '#D97706', LOW: '#6B7280', }; export default function TicketDetailScreen() { const { id } = useLocalSearchParams<{ id: string }>(); const [reply, setReply] = useState(''); const [showStatusPicker, setShowStatusPicker] = useState(false); const qc = useQueryClient(); const { data, isLoading } = useQuery({ queryKey: ['ticket', id], queryFn: () => api.get(`/api/v1/tickets/${id}`).then(r => r.data), }); const addReply = useMutation({ mutationFn: () => api.post(`/api/v1/tickets/${id}/messages`, { message: reply }), onSuccess: () => { setReply(''); qc.invalidateQueries({ queryKey: ['ticket', id] }); }, onError: () => Alert.alert('Error', 'Could not send reply.'), }); const updateStatus = useMutation({ mutationFn: (status: TicketStatus) => api.patch(`/api/v1/tickets/${id}`, { status }), onSuccess: () => { setShowStatusPicker(false); qc.invalidateQueries({ queryKey: ['ticket', id] }); qc.invalidateQueries({ queryKey: ['tickets'] }); }, onError: () => Alert.alert('Error', 'Could not update status.'), }); if (isLoading) { return ( ); } const currentStatus: string = data?.status ?? 'OPEN'; const statusStyle = STATUS_STYLE[currentStatus] ?? { bg: '#F3F4F6', text: '#6B7280' }; const priorityColor = PRIORITY_COLOR[data?.priority] ?? '#6B7280'; return ( {/* Header */} router.back()} className="mr-3"> {data?.subject} {/* Status badge - tappable */} setShowStatusPicker(true)} className="rounded-full px-3 py-1 flex-row items-center" style={{ backgroundColor: statusStyle.bg }} > {currentStatus.replace('_', ' ')} {/* Priority */} {data?.priority} {/* Client name */} {data?.client && ( {data.client.firstName} {data.client.lastName} )} {/* Messages */} {data?.description && ( Description {data.description} )} {(data?.messages ?? []).length === 0 && !data?.description && ( No messages yet. Send the first reply. )} {(data?.messages ?? []).map((m: any) => { const isAgent = m.senderType === 'AGENT' || m.senderType === 'STAFF'; return ( {m.message} {m.senderName ?? m.sender?.name ?? 'System'} ); })} {/* Reply bar — hide if ticket is closed */} {currentStatus !== 'CLOSED' ? ( reply.trim() && addReply.mutate()} disabled={addReply.isPending || !reply.trim()} style={{ opacity: !reply.trim() ? 0.5 : 1 }} > {addReply.isPending ? : Send } ) : ( This ticket is closed )} {/* Status picker modal */} setShowStatusPicker(false)} > setShowStatusPicker(false)} > Update Status Current: {currentStatus.replace('_', ' ')} {STATUS_FLOW.map((s) => { const style = STATUS_STYLE[s] ?? { bg: '#F3F4F6', text: '#6B7280' }; const isActive = s === currentStatus; return ( !isActive && updateStatus.mutate(s)} disabled={isActive || updateStatus.isPending} className={`flex-row items-center justify-between p-4 rounded-xl mb-2 ${isActive ? 'opacity-40' : ''}`} style={{ backgroundColor: style.bg }} > {s.replace('_', ' ')} {isActive && ✓ Current} {updateStatus.isPending && !isActive && } ); })} setShowStatusPicker(false)} > Cancel ); }