/** * View Enquiry Page * Display enquiry details in a clean, minimal SaaS-style design */ import React, { useMemo, useState } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { ArrowLeft, Mail, Phone, Calendar, MessageSquare, Edit, ExternalLink, MapPin, Users, Send, Loader2, } from "lucide-react"; import { __ } from "../lib/i18n"; import { toDateValue } from "../lib/dateFormat"; import { apiService } from "../lib/api-client"; import { usePermissions } from "../hooks/usePermissions"; import { Button } from "../components/ui/button"; import { PageHeader } from "../components/common/PageHeader"; import { Card, CardContent, CardHeader, CardTitle, } from "../components/ui/card"; import { ConditionalRender } from "../components/ui/conditional-render"; import { EnquiryReplyAffordance } from "../components/ai/EnquiryReplyAffordance"; import { Badge } from "../components/ui/badge"; import { useNavigate } from "../hooks/useNavigate"; import { Skeleton } from "../components/ui/skeleton"; import { Modal } from "../components/ui/modal"; import { useToast } from "../components/ui/toast"; interface Enquiry { id: number; name: string; email: string; phone?: string; trip_id?: number; trip_title?: string; trip_slug?: string; message: string; travelers_count?: number; travel_date?: string; status: | "new" | "pending" | "responded" | "closed" | "converted" | "" | string; created_at: string; responded_at?: string; response?: string; } const ViewEnquiry: React.FC = () => { const { can } = usePermissions(); const { navigate } = useNavigate(); const queryClient = useQueryClient(); const { showToast } = useToast(); const [respondDialogOpen, setRespondDialogOpen] = useState(false); const [responseMessage, setResponseMessage] = useState(""); // Get enquiry id from URL const enquiryId = useMemo(() => { const params = new URLSearchParams(window.location.search); return params.get("id") ? parseInt(params.get("id") || "0") : null; }, []); // Fetch enquiry data from API const { data: enquiry, isLoading, error, } = useQuery({ queryKey: ["enquiry", enquiryId], queryFn: async () => { if (!enquiryId) return null; const response = await apiService.getEnquiry(enquiryId!); // Some endpoints return { success, data }, others return the object directly return (response as any)?.data ?? response; }, enabled: !!enquiryId && can("yatra_view_enquiries"), }); // Respond mutation const respondMutation = useMutation({ mutationFn: async ({ id, message }: { id: number; message: string }) => { return await apiService.respondToEnquiry(id, { response: message }); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["enquiry", enquiryId] }); setRespondDialogOpen(false); setResponseMessage(""); showToast(__("Response sent successfully.", "yatra"), "success"); }, }); const formatDate = (dateString: string) => { // toDateValue: a date-only travel_date must not roll back a day in a // behind-UTC zone (datetime values like created_at pass through unchanged). return toDateValue(dateString).toLocaleDateString("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric", }); }; const getStatusBadge = (status: string) => { const statusMap: Record = { new: { className: "bg-blue-100 text-blue-700 dark:bg-blue-900/20 dark:text-blue-400", label: __("New", "yatra"), }, pending: { className: "bg-orange-100 text-orange-700 dark:bg-orange-900/20 dark:text-orange-400", label: __("Pending", "yatra"), }, responded: { className: "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/20 dark:text-yellow-400", label: __("Responded", "yatra"), }, converted: { className: "bg-green-100 text-green-700 dark:bg-green-900/20 dark:text-green-400", label: __("Converted", "yatra"), }, closed: { className: "bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-400", label: __("Closed", "yatra"), }, }; const statusInfo = statusMap[status] || { className: "bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-400", label: status, }; return ( {statusInfo.label} ); }; const handleBack = () => { navigate({ subpage: "enquiries" }); }; const handleEdit = () => { if (enquiryId) { navigate({ subpage: "enquiries", action: "edit", id: enquiryId }); } }; const handleViewTrip = () => { if (enquiry?.trip_id) { navigate({ subpage: "trips", tab: "all", action: "edit", id: enquiry.trip_id, }); } }; const handleRespond = () => { setResponseMessage(""); setRespondDialogOpen(true); }; const sendResponse = () => { if (enquiryId && responseMessage.trim()) { respondMutation.mutate({ id: enquiryId, message: responseMessage, }); } }; if (isLoading) { return (
} />
); } if (error || !enquiry) { return (
{__("Back to Enquiries", "yatra")} } /> {__( "Enquiry not found or you do not have permission to view it.", "yatra", )}
); } return (
{enquiry.status !== "responded" && enquiry.status !== "closed" && ( )}
} />
{/* Main Content */}
{/* Enquiry Information */}
{__("Enquiry Information", "yatra")} {getStatusBadge(enquiry.status)}

{enquiry.message}

{enquiry.travelers_count && (

{enquiry.travelers_count}

)} {enquiry.travel_date && (

{formatDate(enquiry.travel_date)}

)}
{enquiry.response && (

{enquiry.response}

)}
{/* Trip Information */} {enquiry.trip_title && (
{__("Related Trip", "yatra")} {enquiry.trip_id && ( )}

{enquiry.trip_title}

)}
{/* Sidebar */}
{/* Customer Information */} {__("Customer", "yatra")}

{enquiry.name}

{enquiry.email}
{enquiry.phone && (
{enquiry.phone}
)}
{/* Enquiry Timeline */} {__("Timeline", "yatra")}

{formatDate(enquiry.created_at)}

{enquiry.responded_at && (

{formatDate(enquiry.responded_at)}

)}
{/* Respond Dialog via shared Modal */} { if (!respondMutation.isPending) setRespondDialogOpen(false); }} title={__("Respond to Enquiry", "yatra")} description={ enquiry ? ( {__("Send a response to", "yatra")}{" "} {enquiry.name} ) : null } size="md" footer={
} > {enquiry && (
{/* Customer Info */}
{enquiry.email}
{enquiry.trip_title && (
{enquiry.trip_title}
)}
{__("Original Message:", "yatra")}

{enquiry.message}

{/* Response Message — label + AI sparkle on the same row so the operator sees the "Generate with AI" option right where they're about to type. Uses the same EnquiryReplyAffordance component the admin EnquiryForm uses, so behavior (Generate / Warmer / Shorter variants, sensitive-data toggle) is identical across the two surfaces. */}
{enquiryId && ( setResponseMessage(text)} /> )}