diff --git a/frontend/src/app/(client)/(tabs)/tickets.tsx b/frontend/src/app/(client)/(tabs)/tickets.tsx index e3297ce..70b79ce 100644 --- a/frontend/src/app/(client)/(tabs)/tickets.tsx +++ b/frontend/src/app/(client)/(tabs)/tickets.tsx @@ -1,6 +1,6 @@ import React, { useState, useCallback } from 'react'; -import { View, Text, ScrollView, TouchableOpacity, ActivityIndicator, Alert, RefreshControl } from 'react-native'; -import { Ionicons } from '@expo/vector-icons'; +import { View, Text, ScrollView, TouchableOpacity, ActivityIndicator, Alert, RefreshControl, Modal, TextInput } from 'react-native'; +import { Ionicons, Feather } from '@expo/vector-icons'; import { useRouter, useFocusEffect } from 'expo-router'; import { api, useAuth } from '@/contexts/AuthContext'; import { @@ -10,7 +10,6 @@ import { TicketStatus, } from '@/components/tickets/TicketCard'; - export default function Tickets() { const router = useRouter(); const { user } = useAuth(); @@ -19,6 +18,9 @@ export default function Tickets() { const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [activeFilter, setActiveFilter] = useState('Todos'); + const [showCloseModal, setShowCloseModal] = useState(false); + const [ticketToClose, setTicketToClose] = useState(null); + const [closeReason, setCloseReason] = useState(''); const fetchTickets = async () => { try { @@ -62,6 +64,35 @@ export default function Tickets() { fetchTickets(); }; + const confirmClosing = async () => { + if (!closeReason.trim()) { + Alert.alert('Aviso', 'Por favor, descreva o motivo para encerrar o chamado.'); + return; + } + + try { + await api.put(`/tickets/${ticketToClose}/status`, { + status: 'CLOSED', + solution: closeReason, + }); + + setShowCloseModal(false); + setCloseReason(''); + setTicketToClose(null); + Alert.alert('Sucesso', 'Chamado encerrado com sucesso!'); + + fetchTickets(); + + } catch (error: any) { + const apiMessage = error?.response?.data?.message; + const errorMessage = Array.isArray(apiMessage) + ? apiMessage.join('\n') + : (apiMessage || 'Falha ao encerrar chamado.'); + + Alert.alert('Erro', errorMessage); + } + }; + const filteredTickets = tickets.filter(ticket => { if (activeFilter === 'Todos') return true; if (activeFilter === 'Abertos') return ticket.status === 'OPEN'; @@ -118,32 +149,46 @@ export default function Tickets() { ) : ( filteredTickets.map(ticket => ( - { - try { - const res = await api.get(`/chat/ticket/${ticket._id}`); - const chatData = res.data; - const chatId = chatData?.id || chatData?._id; - - if (chatId) { - router.push({ - pathname: '/(client)/ticket/[id]', - params: { - id: chatId, - ticketId: ticket._id - } - }); - } else { - Alert.alert('Aviso', 'O chat deste chamado ainda não foi criado.'); + + { + try { + const res = await api.get(`/chat/ticket/${ticket._id}`); + const chatData = res.data; + const chatId = chatData?.id || chatData?._id; + + if (chatId) { + router.push({ + pathname: '/(client)/ticket/[id]', + params: { + id: chatId, + ticketId: ticket._id + } + }); + } else { + Alert.alert('Aviso', 'O chat deste chamado ainda não foi criado.'); + } + } catch (e: any) { + console.log("ERRO API CHAT CLIENTE:", e?.response?.data || e.message); + Alert.alert('Erro', 'Falha ao conectar na sala do chamado.'); } - } catch (e: any) { - console.log("ERRO API CHAT CLIENTE:", e?.response?.data || e.message); - Alert.alert('Erro', 'Falha ao conectar na sala do chamado.'); - } - }} - /> + }} + /> + + {/* BOTÃO DE 3 PONTINHOS SOBREPOSTO AO CARD */} + {ticket.status !== 'CLOSED' && ( + { + setTicketToClose(ticket._id); + setShowCloseModal(true); + }} + className="absolute top-4 right-4 w-8 h-8 bg-red-50 rounded-full items-center justify-center z-10 border border-red-100 shadow-sm" + > + + + )} + )) )} @@ -155,6 +200,67 @@ export default function Tickets() { + + {/* MODAL DE ENCERRAR CHAMADO (CLIENTE) */} + setShowCloseModal(false)} + > + + + + {/* Tracinho de arrastar */} + + + + + + + + Encerrar Chamado + + + + + Por que você está encerrando este chamado? + + + + {/* BOTÕES DE AÇÃO */} + + { + setShowCloseModal(false); + setCloseReason(''); + setTicketToClose(null); + }} + className="flex-1 bg-gray-100 py-4 rounded-xl items-center mr-2" + > + Cancelar + + + + Confirmar Encerramento + + + + + + + ); } \ No newline at end of file diff --git a/frontend/src/components/tickets/TicketCard.tsx b/frontend/src/components/tickets/TicketCard.tsx index 2f78f46..1fb815a 100644 --- a/frontend/src/components/tickets/TicketCard.tsx +++ b/frontend/src/components/tickets/TicketCard.tsx @@ -37,19 +37,19 @@ const STATUS_MAP = { [TicketStatus.OPEN]: { label: 'ABERTO', color: 'text-amber-600', - bg: 'bg-amber-50', + bg: 'bg-amber-50 mr-8', icon: 'clock-outline', }, [TicketStatus.IN_PROGRESS]: { label: 'EM ATENDIMENTO', color: 'text-blue-600', - bg: 'bg-blue-50', + bg: 'bg-blue-50 mr-8', icon: 'message-text-outline', }, [TicketStatus.ESCALATED]: { label: 'ESCALADO', color: 'text-orange-600', - bg: 'bg-orange-50', + bg: 'bg-orange-50 mr-8', icon: 'alert-circle-outline', }, [TicketStatus.CLOSED]: {