'use client'; import { useState, useEffect } from 'react'; import dynamic from 'next/dynamic'; import { api } from '@/lib/api'; import { FormModal } from '@/components/ui/form-modal'; import { Badge, statusBadgeVariant } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { useToast } from '@/components/ui/toast'; import { LocationPickerModal } from '@/components/maps/location-picker-modal'; const LeafletMap = dynamic(() => import('@/components/maps/leaflet-map').then((m) => ({ default: m.LeafletMap })), { ssr: false }); interface TicketDetailModalProps { open: boolean; onClose: () => void; onUpdated: () => void; ticketId: string | null; } export function TicketDetailModal({ open, onClose, onUpdated, ticketId }: TicketDetailModalProps) { const { toast } = useToast(); const [ticket, setTicket] = useState(null); const [loading, setLoading] = useState(true); const [status, setStatus] = useState(''); const [priority, setPriority] = useState(''); const [comment, setComment] = useState(''); const [saving, setSaving] = useState(false); const [resolving, setResolving] = useState(false); const [selectedLat, setSelectedLat] = useState(null); const [selectedLng, setSelectedLng] = useState(null); const [showLocationPicker, setShowLocationPicker] = useState(false); useEffect(() => { if (!open || !ticketId) return; setLoading(true); api.get(`/tickets/${ticketId}`).then((r) => { const t = r.data.data; setTicket(t); setStatus(t.status); setPriority(t.priority); if (t.client?.latitude != null && t.client?.longitude != null) { setSelectedLat(t.client.latitude); setSelectedLng(t.client.longitude); } }).catch(() => toast('Failed to load ticket', 'error')) .finally(() => setLoading(false)); }, [open, ticketId, toast]); async function handleUpdate() { if (!ticket) return; setSaving(true); try { const updates: any = {}; if (status !== ticket.status) updates.status = status; if (priority !== ticket.priority) updates.priority = priority; if (comment.trim()) { const existingDesc = ticket.description || ''; const timestamp = new Date().toLocaleString(); const newDesc = existingDesc ? `${existingDesc}\n\n--- Comment (${timestamp}) ---\n${comment.trim()}` : `--- Comment (${timestamp}) ---\n${comment.trim()}`; updates.description = newDesc; } if (Object.keys(updates).length === 0) { toast('No changes to save', 'info'); setSaving(false); return; } await api.patch(`/tickets/${ticketId}`, updates); toast('Ticket updated', 'success'); setComment(''); onUpdated(); onClose(); } catch (err: any) { toast(err.response?.data?.error || 'Failed to update', 'error'); } finally { setSaving(false); } } async function handleResolve() { setResolving(true); try { const body: any = {}; if (selectedLat !== null && selectedLng !== null) { body.latitude = selectedLat; body.longitude = selectedLng; } await api.patch(`/tickets/${ticketId}/resolve`, body); toast('Ticket resolved', 'success'); onUpdated(); onClose(); } catch (err: any) { toast(err.response?.data?.error || 'Failed to resolve', 'error'); } finally { setResolving(false); } } const inputClass = 'mt-1 block w-full rounded-lg border border-surface-200 dark:border-surface-600 bg-white dark:bg-surface-800 px-3.5 py-2.5 text-sm text-surface-900 dark:text-surface-100 focus:border-primary-500 dark:focus:border-primary-400 focus:outline-none focus:ring-2 focus:ring-primary-500/20 transition-all duration-200'; if (!open) return null; return ( {loading ? (
Loading ticket details...
) : ticket ? (
{/* Ticket info */}
Type
Status
Client

{ticket.client ? `${ticket.client.firstName} ${ticket.client.lastName}` : '—'}

Assignee

{ticket.assignee ? `${ticket.assignee.firstName} ${ticket.assignee.lastName}` : 'Unassigned'}

Created

{new Date(ticket.createdAt).toLocaleString()}

{ticket.resolvedAt && (
Resolved

{new Date(ticket.resolvedAt).toLocaleString()}

)}
{/* Notes/Comments history */} {ticket.description && (
Notes & Comments
{ticket.description}
)} {/* Update form — only if not resolved/cancelled */} {ticket.status !== 'resolved' && ticket.status !== 'cancelled' && ( <>

Update Ticket