/** * Dashboard Page — redesigned 3.0.5 * * Layout principles: * * - One header row: page title + date-range picker. Welcome card is * dismissible (state stored in localStorage so the operator doesn't * see it every time after onboarding). * * - KPI strip: five high-signal cards in a single row with period- * over-period deltas from `/reports`. Removes the duplicate * "Pending Bookings" card the old design had at top + side, and * adds tooltips for the confusable "Booked vs Collected Revenue" * pair operators routinely conflate. * * - Main grid: charts on the left (Bookings Overview + Status + * Destinations), operational widgets on the right (Upcoming * Departures, Pending Payments). * * - Tertiary: Recent Bookings + Quick Actions full-width at the * bottom. Quick Actions also surface as compact icon buttons in * the page header for faster access from anywhere on the page. * * - AI Today's Brief is gated on AI module enabled (see * TodaysBriefCard.tsx) — never renders a "buy AI" upsell. */ import React, { useMemo, useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { MapPin, Calendar, DollarSign, Users, Plane, TrendingUp, Activity, CheckCircle, Plus, X, } from "lucide-react"; import { __ } from "../lib/i18n"; import { formatDateForInput, toDateValue } from "../lib/dateFormat"; import { usePermissions } from "../hooks/usePermissions"; import { TodaysBriefCard } from "../components/ai/TodaysBriefCard"; import { StatCard } from "../components/common/StatCard"; import { Card, CardContent, CardHeader, CardTitle, } from "../components/ui/card"; import { Button } from "../components/ui/button"; import { ConditionalRender } from "../components/ui/conditional-render"; import { Skeleton } from "../components/ui/skeleton"; import { SimpleBarChart } from "../components/charts/SimpleBarChart"; import BookingsOverviewChart from "../components/charts/BookingsOverviewChart"; import BookingStatusChart from "../components/charts/BookingStatusChart"; import { UpcomingDepartures } from "../components/dashboard/UpcomingDepartures"; import { PendingPayments } from "../components/dashboard/PendingPayments"; import { RecentBookings } from "../components/dashboard/RecentBookings"; import RoleDashboard from "./RoleDashboard"; import { apiClient } from "../lib/api-client"; import { formatYatraMoney, readYatraCurrencyPositionFromWindow, } from "../lib/currency-display"; // --------------------------------------------------------------------------- // Date-range presets // // Same options as the Reports page so operators learn the model once. // Stored in state so the rest of the dashboard can scope its queries to // the picked window. The "all_time" option keeps backwards-compat with // the previous Dashboard behaviour (no date scoping) — it's the // default until the operator picks something else. // --------------------------------------------------------------------------- type DashboardRange = | "all_time" | "today" | "last_7_days" | "last_30_days" | "last_90_days" | "this_year"; // Translatable labels for the date-range presets. // gettext extractors only pick up calls where the msgid is a literal, // so we wrap each label inside `__()` directly here rather than store // raw strings and translate them at the call site (which would emit // `__(variable, ...)` — invisible to make-pot). Renders are still // cheap; this function runs once per render of the setRange(e.target.value as DashboardRange)} className="rounded-md border border-gray-300 bg-white px-3 py-1.5 text-sm text-gray-700 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200" aria-label={__("Date range", "yatra")} > {(() => { const labels = rangeLabels(); return (Object.keys(labels) as DashboardRange[]).map((k) => ( )); })()} {/* Primary action — most common task on a travel dashboard is "I want to add a new trip". Surface it as a primary button so it's always one click away. */} {/* ── AI BRIEF (renders nothing when module disabled) ─────────── */} {/* ── WELCOME (dismissible) ──────────────────────────────────── */} {!welcomeDismissed && (

{__("Welcome to Yatra", "yatra")}

{__( "Real-time insights for your travel business. Monitor performance, track bookings, and manage operations from one place.", "yatra", )}

)} {/* ── KPI STRIP ───────────────────────────────────────────────── */}
goTo("trips")} /> = 0, label: trendLabel, } : undefined } onClick={() => goTo("bookings")} /> = 0, label: trendLabel, } : undefined } onClick={() => goTo("payments")} /> goTo("payments")} /> goTo("customers")} />
{/* ── SECONDARY KPI ROW (period-aware) ────────────────────────── */} {/* These are operational + revenue indicators. Wrap each card in * the appropriate cap — revenue-flavoured for finance roles * (Accountant), conversion / occupancy / cancellation for * operational roles (Sales Agent / Manager / Marketing). */} {hasPeriodData && (
= 0, label: trendLabel, }} />
)} {/* ── MAIN GRID: charts (left) + widgets (right) ──────────────── */}
{__("Bookings Overview", "yatra")}
{__("Booking Status", "yatra")} {__("Popular Destinations", "yatra")} {destinationsData && destinationsData.length > 0 ? ( ) : (
{__("No destination data yet.", "yatra")}
)}
{ goTo("bookings", { action: "view", id: String(booking.id) }); }} />
); }; export default Dashboard;