PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.17
Yatra – Travel Booking & Tour Operator Software v3.0.17
3.0.17 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 All 85 releases
← All changes | resources/js/pages/ViewBooking.tsx +44 -1 3.0.15 → 3.0.17 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,
@@ -35,8 +36,9 @@
35 36 CardTitle,
36 37 } from "../components/ui/card";
37 38 import { ConditionalRender } from "../components/ui/conditional-render";
38 39 import { ResendEmailMenu } from "../components/bookings/ResendEmailMenu";
40 +import { downloadAdminBookingInvoice } from "../lib/invoice-download";
39 41 import { Skeleton } from "../components/ui/skeleton";
40 42 import { formatYatraMoney } from "../lib/currency-display";
41 43
42 44 interface GoogleCalendarSyncInfo {
@@ -352,8 +354,29 @@
352 354 const handleEdit = () => {
353 355 window.location.href = `${window.yatraAdmin?.siteUrl || ""}/wp-admin/admin.php?page=yatra&subpage=bookings&action=edit&id=${bookingId}`;
354 356 };
355 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 +
356 379 if (isLoading) {
357 380 return (
358 381 <div className="space-y-3">
359 382 {/* Header Skeleton */}
@@ -503,8 +526,18 @@
503 526 title={__("Booking Details", "yatra")}
504 527 description={__("View complete booking information", "yatra")}
505 528 actions={
506 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>
507 540 <ConditionalRender capability="yatra_edit_bookings">
508 541 <ResendEmailMenu
509 542 bookingId={Number(booking.id)}
510 543 status={booking.booking_status}
@@ -526,8 +559,18 @@
526 559 </Button>
527 560 </div>
528 561 }
529 562 />
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 + )}
530 573
531 574 <ConditionalRender capability="yatra_view_bookings">
532 575 <div className="grid grid-cols-1 lg:grid-cols-3 gap-3">
533 576 {/* Main Content */}