/** * View Customer Page * Display customer details with dynamic data from API */ import React, { useMemo } from "react"; import { useQuery } from "@tanstack/react-query"; import { ArrowLeft, Mail, Phone, MapPin, Calendar, Users, DollarSign, FileText, Edit, Award, Globe, User, AlertCircle, } from "lucide-react"; import { __ } from "../lib/i18n"; import { apiService } from "../lib/api-client"; import { formatDate as formatDateUtil } from "../lib/dateFormat"; 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 { CustomerSummaryCard } from "../components/ai/CustomerSummaryCard"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "../components/ui/table"; import { Skeleton } from "../components/ui/skeleton"; import { formatYatraMoney } from "../lib/currency-display"; interface Customer { id: number; user_id: number; first_name: string; last_name: string; name: string; email: string; phone: string; secondary_phone?: string; country: string; city?: string; state?: string; address?: string; postal_code?: string; nationality?: string; date_of_birth?: string; gender?: string; emergency_name?: string; emergency_phone?: string; emergency_relationship?: string; dietary_requirements?: string; medical_conditions?: string; special_needs?: string; newsletter_optin: boolean; marketing_optin: boolean; total_bookings: number; total_spent: number; total_travelers: number; loyalty_tier: string; loyalty_points: number; status: string; notes?: string; created_at: string; last_booking_date?: string; last_travel_date?: string; } interface Booking { id: number; reference: string; trip_id: number; trip_title: string; trip_slug?: string; travel_date: string; travelers_count: number; total_amount: number; currency: string; status: string; payment_status: string; created_at: string; } const ViewCustomer: React.FC = () => { const { can, isPro } = usePermissions(); const baseAdminUrl = (window as any).yatraAdmin?.adminUrl || ""; // Get customer id from URL const customerId = useMemo(() => { const params = new URLSearchParams(window.location.search); return params.get("id") ? parseInt(params.get("id") || "0") : null; }, []); // Fetch customer data from API const { data: customer, isLoading, error, } = useQuery({ queryKey: ["customer", customerId], queryFn: async () => { if (!customerId) throw new Error("No customer ID"); const response = await apiService.getCustomer(customerId); const data = (response as any)?.data ?? response; // Normalize nested emergency_contact structure to flat fields expected by the UI const emergency = (data as any).emergency_contact || {}; return { ...data, emergency_name: (data as any).emergency_name ?? emergency.name ?? null, emergency_phone: (data as any).emergency_phone ?? emergency.phone ?? null, emergency_relationship: (data as any).emergency_relationship ?? emergency.relationship ?? null, } as Customer; }, enabled: !!customerId && can("yatra_view_customers"), }); // Fetch customer bookings const { data: bookings, isLoading: isLoadingBookings } = useQuery({ queryKey: ["customer-bookings", customerId], queryFn: async () => { if (!customerId) return []; const json = await apiService.getCustomerBookings(customerId); // API returns an object with data property; ensure we always return an array if (Array.isArray(json)) { return json; } if (json && Array.isArray(json.data)) { return json.data; } return []; }, enabled: !!customerId && can("yatra_view_bookings"), }); const formatDate = (dateString: string | null | undefined) => { return formatDateUtil(dateString); }; const formatShortDate = (dateString: string | null | undefined) => { return formatDateUtil(dateString); }; const formatPrice = (price: number, currencyCode: string = "USD") => formatYatraMoney(Number(price) || 0, currencyCode, { zeroAsUnknown: false, }); const getStatusBadge = (status: string) => { const statusMap: Record = { active: { className: "bg-green-100 text-green-700 dark:bg-green-900/20 dark:text-green-400", label: __("Active", "yatra"), }, inactive: { className: "bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-400", label: __("Inactive", "yatra"), }, blocked: { className: "bg-red-100 text-red-700 dark:bg-red-900/20 dark:text-red-400", label: __("Blocked", "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 getBookingStatusBadge = (status: string) => { const statusMap: Record = { confirmed: { className: "bg-green-100 text-green-700 dark:bg-green-900/20 dark:text-green-400", label: __("Confirmed", "yatra"), }, completed: { className: "bg-blue-100 text-blue-700 dark:bg-blue-900/20 dark:text-blue-400", label: __("Completed", "yatra"), }, pending: { className: "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/20 dark:text-yellow-400", label: __("Pending", "yatra"), }, cancelled: { className: "bg-red-100 text-red-700 dark:bg-red-900/20 dark:text-red-400", label: __("Cancelled", "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 getLoyaltyBadge = (tier: string) => { const tierMap: Record = { bronze: { className: "bg-amber-100 text-amber-700 dark:bg-amber-900/20 dark:text-amber-400", icon: "🥉", }, silver: { className: "bg-gray-200 text-gray-700 dark:bg-gray-600 dark:text-gray-300", icon: "🥈", }, gold: { className: "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/20 dark:text-yellow-400", icon: "🥇", }, platinum: { className: "bg-purple-100 text-purple-700 dark:bg-purple-900/20 dark:text-purple-400", icon: "💎", }, }; const tierInfo = tierMap[tier] || tierMap["bronze"]; return ( {tierInfo.icon} {tier} ); }; const handleBack = () => { window.location.href = `${baseAdminUrl}?page=yatra&subpage=customers`; }; const handleEdit = () => { window.location.href = `${baseAdminUrl}?page=yatra&subpage=customers&action=edit&id=${customerId}`; }; const handleViewBooking = (bookingId: number) => { window.location.href = `${baseAdminUrl}?page=yatra&subpage=bookings&action=view&id=${bookingId}`; }; // Skeleton loader const renderSkeleton = () => (
); if (isLoading) { return (
{__("Back", "yatra")} } /> {renderSkeleton()}
); } if (error || !customer) { return (
{__("Back to Customers", "yatra")} } />

{__("Error loading customer or customer not found", "yatra")}

); } return (
} />
{/* Main Content */}
{/* AI Customer Summary — operator-triggered 3-line snapshot. Self-gates on AI eligibility + module state, so renders nothing in non-eligible tiers. */} {customer?.id && } {/* Customer Overview */}
{__("Customer Overview", "yatra")}
{getStatusBadge(customer.status)} {getLoyaltyBadge(customer.loyalty_tier || "bronze")}
{__("Email Address", "yatra")}
{customer.email}
{customer.phone && (
{__("Phone Number", "yatra")}
{customer.phone} {customer.secondary_phone && ( / {customer.secondary_phone} )}
)}
{customer.address && (
{__("Address", "yatra")}
{customer.address} {customer.city && `, ${customer.city}`} {customer.state && `, ${customer.state}`} {customer.postal_code && ` ${customer.postal_code}`}
)}
{customer.country && (
{__("Country", "yatra")}
{customer.country}
)} {customer.nationality && (
{__("Nationality", "yatra")}
{customer.nationality}
)} {customer.date_of_birth && (
{__("Date of Birth", "yatra")}
{formatShortDate(customer.date_of_birth)}
)}
{/* Emergency Contact */} {customer.emergency_name && ( {__("Emergency Contact", "yatra")}
{__("Name", "yatra")}
{customer.emergency_name}
{customer.emergency_phone && (
{__("Phone", "yatra")}
{customer.emergency_phone}
)} {customer.emergency_relationship && (
{__("Relationship", "yatra")}
{customer.emergency_relationship}
)}
)} {/* Recent Bookings */} {__("Booking History", "yatra")} {isLoadingBookings ? (
{[...Array(3)].map((_, i) => (
))}
) : !bookings || bookings.length === 0 ? (
{__("No bookings yet", "yatra")}
) : ( {__("Reference", "yatra")} {__("Trip", "yatra")} {__("Travel Date", "yatra")} {__("Amount", "yatra")} {__("Status", "yatra")} {__("Actions", "yatra")} {bookings.map((booking) => ( {booking.reference} {booking.trip_title} {formatShortDate(booking.travel_date)} {formatPrice( booking.total_amount, booking.currency, )} {getBookingStatusBadge(booking.status)} ))}
)}
{/* Notes */} {customer.notes && ( {__("Internal Notes", "yatra")}

{customer.notes}

)} {/* Special Requirements */} {(customer.dietary_requirements || customer.medical_conditions || customer.special_needs) && ( {__("Special Requirements", "yatra")} {customer.dietary_requirements && (
{__("Dietary Requirements", "yatra")}
{customer.dietary_requirements}
)} {customer.medical_conditions && (
{__("Medical Conditions", "yatra")}
{customer.medical_conditions}
)} {customer.special_needs && (
{__("Special Needs", "yatra")}
{customer.special_needs}
)}
)}
{/* Sidebar */}
{/* Statistics */} {isPro && ( {__("Statistics", "yatra")}
{__("Total Bookings", "yatra")}
{customer.total_bookings || 0}
{__("Total Spent", "yatra")}
{formatPrice(customer.total_spent || 0)}
{__("Total Travelers", "yatra")}
{customer.total_travelers || 0}
)} {/* Loyalty */} {__("Loyalty", "yatra")}
{__("Current Tier", "yatra")}
{getLoyaltyBadge(customer.loyalty_tier || "bronze")}
{__("Points", "yatra")}
{customer.loyalty_points || 0}
{/* Timeline */} {__("Timeline", "yatra")}
{__("Registered", "yatra")}
{formatDate(customer.created_at)}
{customer.last_booking_date && (
{__("Last Booking", "yatra")}
{formatShortDate(customer.last_booking_date)}
)} {customer.last_travel_date && (
{__("Last Travel", "yatra")}
{formatShortDate(customer.last_travel_date)}
)}
{/* Preferences */} {__("Preferences", "yatra")}
{__("Newsletter", "yatra")} {customer.newsletter_optin ? __("Subscribed", "yatra") : __("Not subscribed", "yatra")}
{__("Marketing", "yatra")} {customer.marketing_optin ? __("Opted in", "yatra") : __("Opted out", "yatra")}
); }; export default ViewCustomer;