| @@ -12,11 +12,14 @@ | ||
| 12 | 12 | Calendar, |
| 13 | 13 | CreditCard, |
| 14 | 14 | Edit, |
| 15 | 15 | ExternalLink, |
| 16 | + Download, | |
| 16 | 17 | } from "lucide-react"; |
| 17 | 18 | import { __ } from "../lib/i18n"; |
| 18 | 19 | import { apiService } from "../lib/api-client"; |
| 20 | +import { downloadAdminInvoice } from "../lib/invoice-download"; | |
| 21 | +import { useToast } from "../components/ui/toast"; | |
| 19 | 22 | import { usePermissions } from "../hooks/usePermissions"; |
| 20 | 23 | import { Button } from "../components/ui/button"; |
| 21 | 24 | import { PageHeader } from "../components/common/PageHeader"; |
| 22 | 25 | import { |
| @@ -26,9 +29,9 @@ | ||
| 26 | 29 | CardTitle, |
| 27 | 30 | } from "../components/ui/card"; |
| 28 | 31 | import { ConditionalRender } from "../components/ui/conditional-render"; |
| 29 | 32 | import { Skeleton } from "../components/ui/skeleton"; |
| 30 | -import { getCurrencySymbol, getCurrency } from "../data/currencies"; | |
| 33 | +import { formatYatraMoney } from "../lib/currency-display"; | |
| 31 | 34 | |
| 32 | 35 | function paymentStr(v: unknown, fallback = ""): string { |
| 33 | 36 | if (v == null || v === "") return fallback; |
| 34 | 37 | return String(v); |
| @@ -51,8 +54,9 @@ | ||
| 51 | 54 | trip_id: number; |
| 52 | 55 | trip_title: string; |
| 53 | 56 | amount: number; |
| 54 | 57 | payment_method: string; |
| 58 | + payment_method_label?: string; | |
| 55 | 59 | payment_status: string; |
| 56 | 60 | transaction_id: string; |
| 57 | 61 | payment_date: string; |
| 58 | 62 | notes: string; |
| @@ -61,8 +65,10 @@ | ||
| 61 | 65 | } |
| 62 | 66 | |
| 63 | 67 | const ViewPayment: React.FC = () => { |
| 64 | 68 | const { can } = usePermissions(); |
| 69 | + const { showToast } = useToast(); | |
| 70 | + const [isDownloadingInvoice, setIsDownloadingInvoice] = React.useState(false); | |
| 65 | 71 | |
| 66 | 72 | // Get payment id from URL |
| 67 | 73 | const paymentId = useMemo(() => { |
| 68 | 74 | const params = new URLSearchParams(window.location.search); |
| @@ -125,19 +131,13 @@ | ||
| 125 | 131 | day: "numeric", |
| 126 | 132 | }); |
| 127 | 133 | }; |
| 128 | 134 | |
| 129 | - const formatPrice = (price: number, currencyCode: string = "USD") => { | |
| 130 | - const symbol = getCurrencySymbol(currencyCode); | |
| 131 | - const currencyData = getCurrency(currencyCode); | |
| 132 | - const decimals = currencyData?.decimalDigits ?? 2; | |
| 135 | + const formatPrice = (price: number, currencyCode: string = "USD") => | |
| 136 | + formatYatraMoney(Number(price) || 0, currencyCode, { | |
| 137 | + zeroAsUnknown: false, | |
| 138 | + }); | |
| 133 | 139 | |
| 134 | - return `${symbol}${new Intl.NumberFormat(undefined, { | |
| 135 | - minimumFractionDigits: decimals, | |
| 136 | - maximumFractionDigits: decimals, | |
| 137 | - }).format(price)}`; | |
| 138 | - }; | |
| 139 | - | |
| 140 | 140 | const getPaymentStatusBadge = (status: string) => { |
| 141 | 141 | const statusMap: Record<string, { className: string; label: string }> = { |
| 142 | 142 | completed: { |
| 143 | 143 | className: |
| @@ -194,8 +194,29 @@ | ||
| 194 | 194 | window.location.href = `${window.yatraAdmin?.siteUrl || ""}/wp-admin/admin.php?page=yatra&subpage=bookings&action=view&id=${payment.booking_id}`; |
| 195 | 195 | } |
| 196 | 196 | }; |
| 197 | 197 | |
| 198 | + const handleDownloadInvoice = async () => { | |
| 199 | + if (!paymentId) return; | |
| 200 | + setIsDownloadingInvoice(true); | |
| 201 | + try { | |
| 202 | + await downloadAdminInvoice(paymentId); | |
| 203 | + } catch (e: any) { | |
| 204 | + showToast( | |
| 205 | + e?.message || __("Failed to download invoice", "yatra"), | |
| 206 | + "error", | |
| 207 | + ); | |
| 208 | + } finally { | |
| 209 | + setIsDownloadingInvoice(false); | |
| 210 | + } | |
| 211 | + }; | |
| 212 | + | |
| 213 | + const canDownloadInvoice = | |
| 214 | + !!payment && | |
| 215 | + ["completed", "paid"].includes( | |
| 216 | + String(payment.payment_status).toLowerCase(), | |
| 217 | + ); | |
| 218 | + | |
| 198 | 219 | if (isLoading) { |
| 199 | 220 | return ( |
| 200 | 221 | <div className="space-y-3"> |
| 201 | 222 | <PageHeader |
| @@ -297,8 +318,21 @@ | ||
| 297 | 318 | > |
| 298 | 319 | <ArrowLeft className="w-4 h-4" /> |
| 299 | 320 | {__("Back", "yatra")} |
| 300 | 321 | </Button> |
| 322 | + {canDownloadInvoice && ( | |
| 323 | + <Button | |
| 324 | + variant="outline" | |
| 325 | + onClick={handleDownloadInvoice} | |
| 326 | + disabled={isDownloadingInvoice} | |
| 327 | + className="flex items-center gap-2" | |
| 328 | + > | |
| 329 | + <Download className="w-4 h-4" /> | |
| 330 | + {isDownloadingInvoice | |
| 331 | + ? __("Downloading…", "yatra") | |
| 332 | + : __("Download Invoice", "yatra")} | |
| 333 | + </Button> | |
| 334 | + )} | |
| 301 | 335 | <ConditionalRender capability="yatra_edit_bookings"> |
| 302 | 336 | <Button onClick={handleEdit} className="flex items-center gap-2"> |
| 303 | 337 | <Edit className="w-4 h-4" /> |
| 304 | 338 | {__("Edit Payment", "yatra")} |
| @@ -346,9 +380,9 @@ | ||
| 346 | 380 | </label> |
| 347 | 381 | <div className="mt-1 flex items-center gap-2"> |
| 348 | 382 | <CreditCard className="w-4 h-4 text-gray-400" /> |
| 349 | 383 | <p className="text-sm font-medium text-gray-900 dark:text-white"> |
| 350 | - {payment.payment_method} | |
| 384 | + {payment.payment_method_label || payment.payment_method} | |
| 351 | 385 | </p> |
| 352 | 386 | </div> |
| 353 | 387 | </div> |
| 354 | 388 | <div> |