PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.16
Yatra – Travel Booking & Tour Operator Software v3.0.16
3.0.16 3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 All 84 releases
← All changes | resources/js/pages/ViewBooking.tsx +274 -248 3.0.4 → 3.0.16 View file →
@@ -2,11 +2,12 @@
2 2 * View Booking Page
3 3 * Display booking details in a clean, minimal SaaS-style design
4 4 */
5 5
6 -import React, { useMemo } from "react";
6 +import React, { useMemo, useState } from "react";
7 7 import { useQuery } from "@tanstack/react-query";
8 8 import {
9 + Download,
9 10 ArrowLeft,
10 11 Mail,
11 12 Phone,
12 13 Calendar,
@@ -22,9 +23,11 @@
22 23 } from "lucide-react";
23 24 import { apiClient, apiService } from "../lib/api-client";
24 25 import { __ } from "../lib/i18n";
25 26 import { formatDate as formatDateUtil } from "../lib/dateFormat";
27 +import PhoneDisplay from "../components/common/PhoneDisplay";
26 28 import { usePermissions } from "../hooks/usePermissions";
29 +import { getCountryName } from "../lib/countries";
27 30 import { Button } from "../components/ui/button";
28 31 import { PageHeader } from "../components/common/PageHeader";
29 32 import {
30 33 Card,
@@ -32,8 +35,10 @@
32 35 CardHeader,
33 36 CardTitle,
34 37 } from "../components/ui/card";
35 38 import { ConditionalRender } from "../components/ui/conditional-render";
39 +import { ResendEmailMenu } from "../components/bookings/ResendEmailMenu";
40 +import { downloadAdminBookingInvoice } from "../lib/invoice-download";
36 41 import { Skeleton } from "../components/ui/skeleton";
37 42 import { formatYatraMoney } from "../lib/currency-display";
38 43
39 44 interface GoogleCalendarSyncInfo {
@@ -52,10 +57,28 @@
52 57 label: string;
53 58 enabled: boolean;
54 59 order: number;
55 60 section?: string;
61 + options?: { value: string; label: string }[];
56 62 }
57 63
64 +/**
65 + * Dropdown answers are stored as the option VALUE (e.g. "spouse"); show the
66 + * option's label from the form the trip used ("Spouse/Partner") when we have
67 + * it, otherwise the raw value as before.
68 + */
69 +const optionLabel = (
70 + field: FormFieldConfig | undefined,
71 + value: unknown,
72 +): string => {
73 + const raw = String(value ?? "");
74 + if (field?.type === "select" && Array.isArray(field.options)) {
75 + const match = field.options.find((o) => String(o.value) === raw);
76 + if (match?.label) return match.label;
77 + }
78 + return raw;
79 +};
80 +
58 81 interface FormSectionConfig {
59 82 title: string;
60 83 enabled: boolean;
61 84 fields: FormFieldConfig[];
@@ -75,37 +98,8 @@
75 98 const params = new URLSearchParams(window.location.search);
76 99 return params.get("id") ? parseInt(params.get("id") || "0") : null;
77 100 }, []);
78 101
79 - // Fetch booking form configuration for dynamic field labels
80 - const { data: formConfig } = useQuery<BookingFormConfig>({
81 - queryKey: ["booking-form-config"],
82 - queryFn: async () => {
83 - const response = await apiService.getSettings();
84 - return (
85 - response?.data?.booking_form_config ||
86 - response?.booking_form_config ||
87 - null
88 - );
89 - },
90 - });
91 -
92 - // Get enabled traveler fields from config
93 - const travelerFields = useMemo(() => {
94 - if (!formConfig?.traveler_form?.fields) return [];
95 - return formConfig.traveler_form.fields
96 - .filter((field) => field.enabled)
97 - .sort((a, b) => a.order - b.order);
98 - }, [formConfig]);
99 -
100 - // Get enabled emergency contact fields
101 - const emergencyFields = useMemo(() => {
102 - if (!formConfig?.emergency_contact_form?.fields) return [];
103 - return formConfig.emergency_contact_form.fields
104 - .filter((field) => field.enabled)
105 - .sort((a, b) => a.order - b.order);
106 - }, [formConfig]);
107 -
108 102 // Helper to get field label by ID
109 103 const getFieldLabel = (
110 104 fieldId: string,
111 105 fields: FormFieldConfig[],
@@ -209,8 +203,50 @@
209 203 },
210 204 enabled: !!bookingId && can("yatra_view_bookings"),
211 205 });
212 206
207 + // Fetch booking form configuration for dynamic field labels, resolved for
208 + // this booking's trip so per-trip form versions (Pro conditions) label the
209 + // fields the customer actually saw. Waits for the booking to know its trip.
210 + const bookingTripId = booking?.trip_id ? Number(booking.trip_id) : null;
211 + const { data: formConfig } = useQuery<BookingFormConfig>({
212 + queryKey: ["booking-form-config", bookingTripId],
213 + queryFn: async () => {
214 + const response = await apiService.getBookingFormConfig(bookingTripId);
215 + return (
216 + response?.data?.booking_form_config ||
217 + response?.booking_form_config ||
218 + null
219 + );
220 + },
221 + enabled: !!booking,
222 + });
223 +
224 + // Get enabled traveler fields from config
225 + const travelerFields = useMemo(() => {
226 + if (!formConfig?.traveler_form?.fields) return [];
227 + return formConfig.traveler_form.fields
228 + .filter((field) => field.enabled)
229 + .sort((a, b) => a.order - b.order);
230 + }, [formConfig]);
231 +
232 + // Get enabled emergency contact fields
233 + const emergencyFields = useMemo(() => {
234 + if (!formConfig?.emergency_contact_form?.fields) return [];
235 + return formConfig.emergency_contact_form.fields
236 + .filter((field) => field.enabled)
237 + .sort((a, b) => a.order - b.order);
238 + }, [formConfig]);
239 +
240 + // Get enabled contact (lead traveler) fields — used to label and format the
241 + // country / nationality / address / custom contact fields in the summary.
242 + const contactFields = useMemo(() => {
243 + if (!formConfig?.contact_form?.fields) return [];
244 + return formConfig.contact_form.fields
245 + .filter((field) => field.enabled)
246 + .sort((a, b) => a.order - b.order);
247 + }, [formConfig]);
248 +
213 249 // Fetch consent status for this booking (only if Pro is active)
214 250 const isPro = !!(window as any).yatraAdmin?.isPro;
215 251 const { data: consentStatus } = useQuery({
216 252 queryKey: ["booking-consent-status", bookingId],
@@ -232,214 +268,8 @@
232 268 formatYatraMoney(Number(price) || 0, currencyCode, {
233 269 zeroAsUnknown: false,
234 270 });
235 271
236 - // Country code to name mapping
237 - const COUNTRY_NAMES: Record<string, string> = {
238 - AF: "Afghanistan",
239 - AL: "Albania",
240 - DZ: "Algeria",
241 - AD: "Andorra",
242 - AO: "Angola",
243 - AG: "Antigua and Barbuda",
244 - AR: "Argentina",
245 - AM: "Armenia",
246 - AU: "Australia",
247 - AT: "Austria",
248 - AZ: "Azerbaijan",
249 - BS: "Bahamas",
250 - BH: "Bahrain",
251 - BD: "Bangladesh",
252 - BB: "Barbados",
253 - BY: "Belarus",
254 - BE: "Belgium",
255 - BZ: "Belize",
256 - BJ: "Benin",
257 - BT: "Bhutan",
258 - BO: "Bolivia",
259 - BA: "Bosnia and Herzegovina",
260 - BW: "Botswana",
261 - BR: "Brazil",
262 - BN: "Brunei",
263 - BG: "Bulgaria",
264 - BF: "Burkina Faso",
265 - BI: "Burundi",
266 - KH: "Cambodia",
267 - CM: "Cameroon",
268 - CA: "Canada",
269 - CV: "Cape Verde",
270 - CF: "Central African Republic",
271 - TD: "Chad",
272 - CL: "Chile",
273 - CN: "China",
274 - CO: "Colombia",
275 - KM: "Comoros",
276 - CG: "Congo",
277 - CD: "DR Congo",
278 - CR: "Costa Rica",
279 - CI: "Ivory Coast",
280 - HR: "Croatia",
281 - CU: "Cuba",
282 - CY: "Cyprus",
283 - CZ: "Czech Republic",
284 - DK: "Denmark",
285 - DJ: "Djibouti",
286 - DM: "Dominica",
287 - DO: "Dominican Republic",
288 - EC: "Ecuador",
289 - EG: "Egypt",
290 - SV: "El Salvador",
291 - GQ: "Equatorial Guinea",
292 - ER: "Eritrea",
293 - EE: "Estonia",
294 - SZ: "Eswatini",
295 - ET: "Ethiopia",
296 - FJ: "Fiji",
297 - FI: "Finland",
298 - FR: "France",
299 - GA: "Gabon",
300 - GM: "Gambia",
301 - GE: "Georgia",
302 - DE: "Germany",
303 - GH: "Ghana",
304 - GR: "Greece",
305 - GD: "Grenada",
306 - GT: "Guatemala",
307 - GN: "Guinea",
308 - GW: "Guinea-Bissau",
309 - GY: "Guyana",
310 - HT: "Haiti",
311 - HN: "Honduras",
312 - HU: "Hungary",
313 - IS: "Iceland",
314 - IN: "India",
315 - ID: "Indonesia",
316 - IR: "Iran",
317 - IQ: "Iraq",
318 - IE: "Ireland",
319 - IL: "Israel",
320 - IT: "Italy",
321 - JM: "Jamaica",
322 - JP: "Japan",
323 - JO: "Jordan",
324 - KZ: "Kazakhstan",
325 - KE: "Kenya",
326 - KI: "Kiribati",
327 - KP: "North Korea",
328 - KR: "South Korea",
329 - KW: "Kuwait",
330 - KG: "Kyrgyzstan",
331 - LA: "Laos",
332 - LV: "Latvia",
333 - LB: "Lebanon",
334 - LS: "Lesotho",
335 - LR: "Liberia",
336 - LY: "Libya",
337 - LI: "Liechtenstein",
338 - LT: "Lithuania",
339 - LU: "Luxembourg",
340 - MG: "Madagascar",
341 - MW: "Malawi",
342 - MY: "Malaysia",
343 - MV: "Maldives",
344 - ML: "Mali",
345 - MT: "Malta",
346 - MH: "Marshall Islands",
347 - MR: "Mauritania",
348 - MU: "Mauritius",
349 - MX: "Mexico",
350 - FM: "Micronesia",
351 - MD: "Moldova",
352 - MC: "Monaco",
353 - MN: "Mongolia",
354 - ME: "Montenegro",
355 - MA: "Morocco",
356 - MZ: "Mozambique",
357 - MM: "Myanmar",
358 - NA: "Namibia",
359 - NR: "Nauru",
360 - NP: "Nepal",
361 - NL: "Netherlands",
362 - NZ: "New Zealand",
363 - NI: "Nicaragua",
364 - NE: "Niger",
365 - NG: "Nigeria",
366 - MK: "North Macedonia",
367 - NO: "Norway",
368 - OM: "Oman",
369 - PK: "Pakistan",
370 - PW: "Palau",
371 - PS: "Palestine",
372 - PA: "Panama",
373 - PG: "Papua New Guinea",
374 - PY: "Paraguay",
375 - PE: "Peru",
376 - PH: "Philippines",
377 - PL: "Poland",
378 - PT: "Portugal",
379 - QA: "Qatar",
380 - RO: "Romania",
381 - RU: "Russia",
382 - RW: "Rwanda",
383 - KN: "Saint Kitts and Nevis",
384 - LC: "Saint Lucia",
385 - VC: "Saint Vincent and the Grenadines",
386 - WS: "Samoa",
387 - SM: "San Marino",
388 - ST: "Sao Tome and Principe",
389 - SA: "Saudi Arabia",
390 - SN: "Senegal",
391 - RS: "Serbia",
392 - SC: "Seychelles",
393 - SL: "Sierra Leone",
394 - SG: "Singapore",
395 - SK: "Slovakia",
396 - SI: "Slovenia",
397 - SB: "Solomon Islands",
398 - SO: "Somalia",
399 - ZA: "South Africa",
400 - SS: "South Sudan",
401 - ES: "Spain",
402 - LK: "Sri Lanka",
403 - SD: "Sudan",
404 - SR: "Suriname",
405 - SE: "Sweden",
406 - CH: "Switzerland",
407 - SY: "Syria",
408 - TW: "Taiwan",
409 - TJ: "Tajikistan",
410 - TZ: "Tanzania",
411 - TH: "Thailand",
412 - TL: "Timor-Leste",
413 - TG: "Togo",
414 - TO: "Tonga",
415 - TT: "Trinidad and Tobago",
416 - TN: "Tunisia",
417 - TR: "Turkey",
418 - TM: "Turkmenistan",
419 - TV: "Tuvalu",
420 - UG: "Uganda",
421 - UA: "Ukraine",
422 - AE: "United Arab Emirates",
423 - GB: "United Kingdom",
424 - US: "United States",
425 - UY: "Uruguay",
426 - UZ: "Uzbekistan",
427 - VU: "Vanuatu",
428 - VA: "Vatican City",
429 - VE: "Venezuela",
430 - VN: "Vietnam",
431 - YE: "Yemen",
432 - ZM: "Zambia",
433 - ZW: "Zimbabwe",
434 - };
435 -
436 - const getCountryName = (code: string): string => {
437 - if (!code) return "";
438 - const upperCode = code.toUpperCase();
439 - return COUNTRY_NAMES[upperCode] || code;
440 - };
441 -
442 272 const getBookingStatusBadge = (status: string) => {
443 273 const statusMap: Record<string, { className: string; label: string }> = {
444 274 confirmed: {
445 275 className:
@@ -524,8 +354,29 @@
524 354 const handleEdit = () => {
525 355 window.location.href = `${window.yatraAdmin?.siteUrl || ""}/wp-admin/admin.php?page=yatra&subpage=bookings&action=edit&id=${bookingId}`;
526 356 };
527 357
358 + // The booking invoice works for unpaid bookings too — it renders as a
359 + // pro-forma invoice with the gateway's payment instructions. This page has
360 + // no toast provider, so a failure is rendered as an inline banner under the
361 + // header; a tooltip alone would leave a failed download looking like nothing
362 + // happened.
363 + const [invoiceError, setInvoiceError] = useState<string>("");
364 + const handleDownloadInvoice = async () => {
365 + const id = Number(booking?.id ?? 0);
366 + if (!id) {
367 + return;
368 + }
369 + setInvoiceError("");
370 + try {
371 + await downloadAdminBookingInvoice(id);
372 + } catch (error: any) {
373 + setInvoiceError(
374 + error?.message || __("Failed to download invoice", "yatra"),
375 + );
376 + }
377 + };
378 +
528 379 if (isLoading) {
529 380 return (
530 381 <div className="space-y-3">
531 382 {/* Header Skeleton */}
@@ -675,9 +526,26 @@
675 526 title={__("Booking Details", "yatra")}
676 527 description={__("View complete booking information", "yatra")}
677 528 actions={
678 529 <div className="flex gap-2">
530 + <ConditionalRender capability="yatra_view_bookings">
531 + <Button
532 + variant="outline"
533 + onClick={handleDownloadInvoice}
534 + className="flex items-center gap-2"
535 + >
536 + <Download className="w-4 h-4" />
537 + {__("Download invoice", "yatra")}
538 + </Button>
539 + </ConditionalRender>
679 540 <ConditionalRender capability="yatra_edit_bookings">
541 + <ResendEmailMenu
542 + bookingId={Number(booking.id)}
543 + status={booking.booking_status}
544 + amountPaid={Number(booking.amount_paid) || 0}
545 + />
546 + </ConditionalRender>
547 + <ConditionalRender capability="yatra_edit_bookings">
680 548 <Button onClick={handleEdit} className="flex items-center gap-2">
681 549 {__("Edit Booking", "yatra")}
682 550 </Button>
683 551 </ConditionalRender>
@@ -692,8 +560,18 @@
692 560 </div>
693 561 }
694 562 />
695 563
564 + {invoiceError && (
565 + <div
566 + role="alert"
567 + className="flex items-center gap-2 rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700 dark:border-red-800 dark:bg-red-900/20 dark:text-red-400"
568 + >
569 + <AlertCircle className="w-4 h-4 shrink-0" />
570 + {invoiceError}
571 + </div>
572 + )}
573 +
696 574 <ConditionalRender capability="yatra_view_bookings">
697 575 <div className="grid grid-cols-1 lg:grid-cols-3 gap-3">
698 576 {/* Main Content */}
699 577 <div className="lg:col-span-2 space-y-3">
@@ -1081,8 +959,81 @@
1081 959 </div>
1082 960 </CardContent>
1083 961 </Card>
1084 962
963 + {/* Payments recorded against this booking (gateway captures and
964 + manual entries). Only completed rows count towards Amount
965 + Paid — a pending row is money expected, not received. */}
966 + {Array.isArray(booking.payments) && booking.payments.length > 0 && (
967 + <Card data-testid="booking-payments-card">
968 + <CardHeader className="pb-2">
969 + <CardTitle className="text-base">
970 + {__("Payments", "yatra")}
971 + </CardTitle>
972 + </CardHeader>
973 + <CardContent>
974 + <div className="divide-y divide-gray-100 dark:divide-gray-800">
975 + {booking.payments.map((payment: any) => {
976 + const status = String(
977 + payment.status || payment.payment_status || "pending",
978 + );
979 + const statusClass =
980 + status === "completed"
981 + ? "bg-green-100 text-green-700 dark:bg-green-900/20 dark:text-green-400"
982 + : status === "pending"
983 + ? "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/20 dark:text-yellow-400"
984 + : status === "refunded"
985 + ? "bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-400"
986 + : "bg-red-100 text-red-700 dark:bg-red-900/20 dark:text-red-400";
987 + const statusLabel: Record<string, string> = {
988 + completed: __("Completed", "yatra"),
989 + pending: __("Pending", "yatra"),
990 + failed: __("Failed", "yatra"),
991 + refunded: __("Refunded", "yatra"),
992 + cancelled: __("Cancelled", "yatra"),
993 + };
994 + return (
995 + <div
996 + key={payment.id}
997 + className="py-2 flex items-start justify-between gap-3 text-sm"
998 + >
999 + <div className="min-w-0">
1000 + <div className="font-medium text-gray-900 dark:text-white">
1001 + {formatPrice(
1002 + Number(payment.amount || 0),
1003 + payment.currency || booking.currency,
1004 + )}
1005 + <span className="ml-2 font-normal text-gray-500 dark:text-gray-400">
1006 + {payment.payment_method_label ||
1007 + payment.gateway ||
1008 + ""}
1009 + </span>
1010 + </div>
1011 + <div className="text-xs text-gray-500 dark:text-gray-400 truncate">
1012 + {payment.payment_date
1013 + ? formatDate(payment.payment_date)
1014 + : ""}
1015 + {payment.transaction_id
1016 + ? ` · ${payment.transaction_id}`
1017 + : ""}
1018 + {payment.payment_number
1019 + ? ` · ${payment.payment_number}`
1020 + : ""}
1021 + </div>
1022 + </div>
1023 + <span
1024 + className={`inline-flex items-center px-2 py-0.5 rounded-md text-xs font-medium whitespace-nowrap ${statusClass}`}
1025 + >
1026 + {statusLabel[status] || status}
1027 + </span>
1028 + </div>
1029 + );
1030 + })}
1031 + </div>
1032 + </CardContent>
1033 + </Card>
1034 + )}
1035 +
1085 1036 {/* Customer Information */}
1086 1037 <Card>
1087 1038 <CardHeader className="pb-2">
1088 1039 <CardTitle className="text-base">
@@ -1101,13 +1052,69 @@
1101 1052 </div>
1102 1053 {booking.customer_phone && (
1103 1054 <div className="flex items-center gap-1.5">
1104 1055 <Phone className="w-4 h-4" />
1105 - {booking.customer_phone}
1056 + <PhoneDisplay value={booking.customer_phone} />
1106 1057 </div>
1107 1058 )}
1108 1059 </div>
1109 1060 </div>
1061 +
1062 + {/* Country + dynamic/custom contact fields (nationality,
1063 + address, and any custom contact field). Mirrors the
1064 + Emergency/Traveler dynamic-field display. */}
1065 + {(() => {
1066 + const cd =
1067 + booking.contact_data &&
1068 + typeof booking.contact_data === "object"
1069 + ? (booking.contact_data as Record<string, unknown>)
1070 + : {};
1071 + const CORE = ["first_name", "last_name", "email", "phone"];
1072 + const extras = Object.entries(cd).filter(
1073 + ([k, v]) =>
1074 + !CORE.includes(k) &&
1075 + k !== "country" &&
1076 + v != null &&
1077 + String(v).trim() !== "",
1078 + );
1079 + const countryCode =
1080 + booking.customer_country ||
1081 + (cd.country ? String(cd.country) : "");
1082 + if (!countryCode && extras.length === 0) return null;
1083 + return (
1084 + <div className="grid grid-cols-1 md:grid-cols-3 gap-3 pt-1 border-t border-gray-100 dark:border-gray-700">
1085 + {countryCode && (
1086 + <div>
1087 + <div className="text-xs text-gray-500 dark:text-gray-400 mb-0.5">
1088 + {getFieldLabel("country", contactFields)}
1089 + </div>
1090 + <div className="text-sm font-medium text-gray-900 dark:text-white">
1091 + {getCountryName(countryCode)}
1092 + </div>
1093 + </div>
1094 + )}
1095 + {extras.map(([fieldId, value]) => {
1096 + const field = contactFields.find(
1097 + (f) => f.id === fieldId,
1098 + );
1099 + const display =
1100 + field?.type === "country"
1101 + ? getCountryName(String(value))
1102 + : optionLabel(field, value);
1103 + return (
1104 + <div key={fieldId}>
1105 + <div className="text-xs text-gray-500 dark:text-gray-400 mb-0.5">
1106 + {getFieldLabel(fieldId, contactFields)}
1107 + </div>
1108 + <div className="text-sm font-medium text-gray-900 dark:text-white">
1109 + {display}
1110 + </div>
1111 + </div>
1112 + );
1113 + })}
1114 + </div>
1115 + );
1116 + })()}
1110 1117 </CardContent>
1111 1118 </Card>
1112 1119
1113 1120 {/* Travelers Information - Dynamic Fields */}
@@ -1216,22 +1223,20 @@
1216 1223 const isLongField =
1217 1224 fieldConfig?.type === "textarea" ||
1218 1225 String(fieldValue).length > 50;
1219 1226
1220 - // Format date fields
1227 + // Format date fields (e.g. Date of Birth) with the
1228 + // global WP date-format setting via the shared
1229 + // formatter — not the browser locale.
1221 1230 let displayValue = String(fieldValue);
1222 1231 if (
1223 1232 fieldConfig?.type === "date" ||
1224 1233 fieldId.includes("date") ||
1234 + fieldId.includes("birth") ||
1235 + fieldId.includes("dob") ||
1225 1236 fieldId.includes("expiry")
1226 1237 ) {
1227 - try {
1228 - displayValue = new Date(
1229 - fieldValue as string,
1230 - ).toLocaleDateString();
1231 - } catch {
1232 - displayValue = String(fieldValue);
1233 - }
1238 + displayValue = formatDate(fieldValue as string);
1234 1239 }
1235 1240
1236 1241 // Format country/nationality fields - convert code to full name
1237 1242 if (
@@ -1243,8 +1248,20 @@
1243 1248 ) {
1244 1249 displayValue = getCountryName(fieldValue);
1245 1250 }
1246 1251
1252 + // Dropdown answers: option label instead of value.
1253 + // The label is the operator's own text, so it is
1254 + // shown as written (no title-casing).
1255 + let asWritten = false;
1256 + if (fieldConfig?.type === "select") {
1257 + displayValue = optionLabel(
1258 + fieldConfig,
1259 + fieldValue,
1260 + );
1261 + asWritten = displayValue !== String(fieldValue);
1262 + }
1263 +
1247 1264 return (
1248 1265 <div
1249 1266 key={fieldId}
1250 1267 className={
@@ -1256,9 +1273,9 @@
1256 1273 <div className="text-xs text-gray-500 dark:text-gray-400 mb-0.5">
1257 1274 {label}
1258 1275 </div>
1259 1276 <div
1260 - className={`text-sm text-gray-900 dark:text-white ${fieldId === "passport" ? "font-mono" : ""} capitalize`}
1277 + className={`text-sm text-gray-900 dark:text-white ${fieldId === "passport" ? "font-mono" : ""} ${asWritten ? "" : "capitalize"}`}
1261 1278 >
1262 1279 {displayValue}
1263 1280 </div>
1264 1281 </div>
@@ -1302,15 +1319,24 @@
1302 1319 ([_, value]) => value && String(value).trim() !== "",
1303 1320 )
1304 1321 .map(([fieldId, fieldValue]) => {
1305 1322 const label = getFieldLabel(fieldId, emergencyFields);
1323 + const emergencyField = emergencyFields.find(
1324 + (f) => f.id === fieldId,
1325 + );
1326 + const emergencyDisplay = optionLabel(
1327 + emergencyField,
1328 + fieldValue,
1329 + );
1306 1330 return (
1307 1331 <div key={fieldId}>
1308 1332 <div className="text-xs text-gray-500 dark:text-gray-400 mb-0.5">
1309 1333 {label}
1310 1334 </div>
1311 - <div className="text-sm font-medium text-gray-900 dark:text-white capitalize">
1312 - {String(fieldValue)}
1335 + <div
1336 + className={`text-sm font-medium text-gray-900 dark:text-white ${emergencyDisplay !== String(fieldValue) ? "" : "capitalize"}`}
1337 + >
1338 + {emergencyDisplay}
1313 1339 </div>
1314 1340 </div>
1315 1341 );
1316 1342 })}