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 +277 -249 3.0.3 → 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],
@@ -228,216 +264,12 @@
228 264 return formatDateUtil(dateString);
229 265 };
230 266
231 267 const formatPrice = (price: number, currencyCode: string = "USD") =>
232 - formatYatraMoney(Number(price) || 0, currencyCode, { zeroAsUnknown: false });
268 + formatYatraMoney(Number(price) || 0, currencyCode, {
269 + zeroAsUnknown: false,
270 + });
233 271
234 - // Country code to name mapping
235 - const COUNTRY_NAMES: Record<string, string> = {
236 - AF: "Afghanistan",
237 - AL: "Albania",
238 - DZ: "Algeria",
239 - AD: "Andorra",
240 - AO: "Angola",
241 - AG: "Antigua and Barbuda",
242 - AR: "Argentina",
243 - AM: "Armenia",
244 - AU: "Australia",
245 - AT: "Austria",
246 - AZ: "Azerbaijan",
247 - BS: "Bahamas",
248 - BH: "Bahrain",
249 - BD: "Bangladesh",
250 - BB: "Barbados",
251 - BY: "Belarus",
252 - BE: "Belgium",
253 - BZ: "Belize",
254 - BJ: "Benin",
255 - BT: "Bhutan",
256 - BO: "Bolivia",
257 - BA: "Bosnia and Herzegovina",
258 - BW: "Botswana",
259 - BR: "Brazil",
260 - BN: "Brunei",
261 - BG: "Bulgaria",
262 - BF: "Burkina Faso",
263 - BI: "Burundi",
264 - KH: "Cambodia",
265 - CM: "Cameroon",
266 - CA: "Canada",
267 - CV: "Cape Verde",
268 - CF: "Central African Republic",
269 - TD: "Chad",
270 - CL: "Chile",
271 - CN: "China",
272 - CO: "Colombia",
273 - KM: "Comoros",
274 - CG: "Congo",
275 - CD: "DR Congo",
276 - CR: "Costa Rica",
277 - CI: "Ivory Coast",
278 - HR: "Croatia",
279 - CU: "Cuba",
280 - CY: "Cyprus",
281 - CZ: "Czech Republic",
282 - DK: "Denmark",
283 - DJ: "Djibouti",
284 - DM: "Dominica",
285 - DO: "Dominican Republic",
286 - EC: "Ecuador",
287 - EG: "Egypt",
288 - SV: "El Salvador",
289 - GQ: "Equatorial Guinea",
290 - ER: "Eritrea",
291 - EE: "Estonia",
292 - SZ: "Eswatini",
293 - ET: "Ethiopia",
294 - FJ: "Fiji",
295 - FI: "Finland",
296 - FR: "France",
297 - GA: "Gabon",
298 - GM: "Gambia",
299 - GE: "Georgia",
300 - DE: "Germany",
301 - GH: "Ghana",
302 - GR: "Greece",
303 - GD: "Grenada",
304 - GT: "Guatemala",
305 - GN: "Guinea",
306 - GW: "Guinea-Bissau",
307 - GY: "Guyana",
308 - HT: "Haiti",
309 - HN: "Honduras",
310 - HU: "Hungary",
311 - IS: "Iceland",
312 - IN: "India",
313 - ID: "Indonesia",
314 - IR: "Iran",
315 - IQ: "Iraq",
316 - IE: "Ireland",
317 - IL: "Israel",
318 - IT: "Italy",
319 - JM: "Jamaica",
320 - JP: "Japan",
321 - JO: "Jordan",
322 - KZ: "Kazakhstan",
323 - KE: "Kenya",
324 - KI: "Kiribati",
325 - KP: "North Korea",
326 - KR: "South Korea",
327 - KW: "Kuwait",
328 - KG: "Kyrgyzstan",
329 - LA: "Laos",
330 - LV: "Latvia",
331 - LB: "Lebanon",
332 - LS: "Lesotho",
333 - LR: "Liberia",
334 - LY: "Libya",
335 - LI: "Liechtenstein",
336 - LT: "Lithuania",
337 - LU: "Luxembourg",
338 - MG: "Madagascar",
339 - MW: "Malawi",
340 - MY: "Malaysia",
341 - MV: "Maldives",
342 - ML: "Mali",
343 - MT: "Malta",
344 - MH: "Marshall Islands",
345 - MR: "Mauritania",
346 - MU: "Mauritius",
347 - MX: "Mexico",
348 - FM: "Micronesia",
349 - MD: "Moldova",
350 - MC: "Monaco",
351 - MN: "Mongolia",
352 - ME: "Montenegro",
353 - MA: "Morocco",
354 - MZ: "Mozambique",
355 - MM: "Myanmar",
356 - NA: "Namibia",
357 - NR: "Nauru",
358 - NP: "Nepal",
359 - NL: "Netherlands",
360 - NZ: "New Zealand",
361 - NI: "Nicaragua",
362 - NE: "Niger",
363 - NG: "Nigeria",
364 - MK: "North Macedonia",
365 - NO: "Norway",
366 - OM: "Oman",
367 - PK: "Pakistan",
368 - PW: "Palau",
369 - PS: "Palestine",
370 - PA: "Panama",
371 - PG: "Papua New Guinea",
372 - PY: "Paraguay",
373 - PE: "Peru",
374 - PH: "Philippines",
375 - PL: "Poland",
376 - PT: "Portugal",
377 - QA: "Qatar",
378 - RO: "Romania",
379 - RU: "Russia",
380 - RW: "Rwanda",
381 - KN: "Saint Kitts and Nevis",
382 - LC: "Saint Lucia",
383 - VC: "Saint Vincent and the Grenadines",
384 - WS: "Samoa",
385 - SM: "San Marino",
386 - ST: "Sao Tome and Principe",
387 - SA: "Saudi Arabia",
388 - SN: "Senegal",
389 - RS: "Serbia",
390 - SC: "Seychelles",
391 - SL: "Sierra Leone",
392 - SG: "Singapore",
393 - SK: "Slovakia",
394 - SI: "Slovenia",
395 - SB: "Solomon Islands",
396 - SO: "Somalia",
397 - ZA: "South Africa",
398 - SS: "South Sudan",
399 - ES: "Spain",
400 - LK: "Sri Lanka",
401 - SD: "Sudan",
402 - SR: "Suriname",
403 - SE: "Sweden",
404 - CH: "Switzerland",
405 - SY: "Syria",
406 - TW: "Taiwan",
407 - TJ: "Tajikistan",
408 - TZ: "Tanzania",
409 - TH: "Thailand",
410 - TL: "Timor-Leste",
411 - TG: "Togo",
412 - TO: "Tonga",
413 - TT: "Trinidad and Tobago",
414 - TN: "Tunisia",
415 - TR: "Turkey",
416 - TM: "Turkmenistan",
417 - TV: "Tuvalu",
418 - UG: "Uganda",
419 - UA: "Ukraine",
420 - AE: "United Arab Emirates",
421 - GB: "United Kingdom",
422 - US: "United States",
423 - UY: "Uruguay",
424 - UZ: "Uzbekistan",
425 - VU: "Vanuatu",
426 - VA: "Vatican City",
427 - VE: "Venezuela",
428 - VN: "Vietnam",
429 - YE: "Yemen",
430 - ZM: "Zambia",
431 - ZW: "Zimbabwe",
432 - };
433 -
434 - const getCountryName = (code: string): string => {
435 - if (!code) return "";
436 - const upperCode = code.toUpperCase();
437 - return COUNTRY_NAMES[upperCode] || code;
438 - };
439 -
440 272 const getBookingStatusBadge = (status: string) => {
441 273 const statusMap: Record<string, { className: string; label: string }> = {
442 274 confirmed: {
443 275 className:
@@ -522,8 +354,29 @@
522 354 const handleEdit = () => {
523 355 window.location.href = `${window.yatraAdmin?.siteUrl || ""}/wp-admin/admin.php?page=yatra&subpage=bookings&action=edit&id=${bookingId}`;
524 356 };
525 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 +
526 379 if (isLoading) {
527 380 return (
528 381 <div className="space-y-3">
529 382 {/* Header Skeleton */}
@@ -673,9 +526,26 @@
673 526 title={__("Booking Details", "yatra")}
674 527 description={__("View complete booking information", "yatra")}
675 528 actions={
676 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>
677 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">
678 548 <Button onClick={handleEdit} className="flex items-center gap-2">
679 549 {__("Edit Booking", "yatra")}
680 550 </Button>
681 551 </ConditionalRender>
@@ -690,8 +560,18 @@
690 560 </div>
691 561 }
692 562 />
693 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 +
694 574 <ConditionalRender capability="yatra_view_bookings">
695 575 <div className="grid grid-cols-1 lg:grid-cols-3 gap-3">
696 576 {/* Main Content */}
697 577 <div className="lg:col-span-2 space-y-3">
@@ -1079,8 +959,81 @@
1079 959 </div>
1080 960 </CardContent>
1081 961 </Card>
1082 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 +
1083 1036 {/* Customer Information */}
1084 1037 <Card>
1085 1038 <CardHeader className="pb-2">
1086 1039 <CardTitle className="text-base">
@@ -1099,13 +1052,69 @@
1099 1052 </div>
1100 1053 {booking.customer_phone && (
1101 1054 <div className="flex items-center gap-1.5">
1102 1055 <Phone className="w-4 h-4" />
1103 - {booking.customer_phone}
1056 + <PhoneDisplay value={booking.customer_phone} />
1104 1057 </div>
1105 1058 )}
1106 1059 </div>
1107 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 + })()}
1108 1117 </CardContent>
1109 1118 </Card>
1110 1119
1111 1120 {/* Travelers Information - Dynamic Fields */}
@@ -1214,22 +1223,20 @@
1214 1223 const isLongField =
1215 1224 fieldConfig?.type === "textarea" ||
1216 1225 String(fieldValue).length > 50;
1217 1226
1218 - // 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.
1219 1230 let displayValue = String(fieldValue);
1220 1231 if (
1221 1232 fieldConfig?.type === "date" ||
1222 1233 fieldId.includes("date") ||
1234 + fieldId.includes("birth") ||
1235 + fieldId.includes("dob") ||
1223 1236 fieldId.includes("expiry")
1224 1237 ) {
1225 - try {
1226 - displayValue = new Date(
1227 - fieldValue as string,
1228 - ).toLocaleDateString();
1229 - } catch {
1230 - displayValue = String(fieldValue);
1231 - }
1238 + displayValue = formatDate(fieldValue as string);
1232 1239 }
1233 1240
1234 1241 // Format country/nationality fields - convert code to full name
1235 1242 if (
@@ -1241,8 +1248,20 @@
1241 1248 ) {
1242 1249 displayValue = getCountryName(fieldValue);
1243 1250 }
1244 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 +
1245 1264 return (
1246 1265 <div
1247 1266 key={fieldId}
1248 1267 className={
@@ -1254,9 +1273,9 @@
1254 1273 <div className="text-xs text-gray-500 dark:text-gray-400 mb-0.5">
1255 1274 {label}
1256 1275 </div>
1257 1276 <div
1258 - 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"}`}
1259 1278 >
1260 1279 {displayValue}
1261 1280 </div>
1262 1281 </div>
@@ -1300,15 +1319,24 @@
1300 1319 ([_, value]) => value && String(value).trim() !== "",
1301 1320 )
1302 1321 .map(([fieldId, fieldValue]) => {
1303 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 + );
1304 1330 return (
1305 1331 <div key={fieldId}>
1306 1332 <div className="text-xs text-gray-500 dark:text-gray-400 mb-0.5">
1307 1333 {label}
1308 1334 </div>
1309 - <div className="text-sm font-medium text-gray-900 dark:text-white capitalize">
1310 - {String(fieldValue)}
1335 + <div
1336 + className={`text-sm font-medium text-gray-900 dark:text-white ${emergencyDisplay !== String(fieldValue) ? "" : "capitalize"}`}
1337 + >
1338 + {emergencyDisplay}
1311 1339 </div>
1312 1340 </div>
1313 1341 );
1314 1342 })}