PassengerDetailPage

Passenger detail page with bookings, transactions, and unmask functionality

PassengerDetailPage

A comprehensive passenger detail page showing personal information, booking history, and transaction history. Includes secure unmask functionality for email and phone.

Features

  • Personal Information: Full passenger details with unmask capability
  • Bookings Tab: Complete booking history with filtering and sorting
  • Transactions Tab: Payment transaction history
  • Unmask Email/Phone: Secure one-time unmask with eye icons
  • Back Navigation: Navigate back to passenger list
  • Localization: Fully localized in English and Japanese
  • Tab Layout: Organized tabs for bookings and transactions

Usage

// app/passengers/[id]/page.tsx
'use client'

import { useParams } from 'next/navigation';
import { PassengerDetailPage } from '@/components/sgerp/pages/passenger-detail-page';

export default function Page() {
  const params = useParams();
  const passengerId = params.id as string;

  return <PassengerDetailPage passengerId={passengerId} />;
}

Props

PropTypeDefaultRequiredDescription
passengerIdstring-YesThe ID of the passenger to display

Screenshots

Bookings Tab

Passenger Detail - Bookings

Transactions Tab

Passenger Detail - Transactions

Features in Detail

  • Name: Passenger full name
  • ID: Passenger ID (small muted text)
  • Email: Email with unmask button
    • Masked by default
    • Click eye icon to unmask
    • Shows spinner while loading
    • Click eye-off to re-mask
  • Phone: Phone with unmask button
    • Same behavior as email
  • Gender: Male/Female/Other (localized)
  • Date of Birth: Formatted date
  • Organization: Organization name
  • Active: Active status (✓/✗)
  • Back Button: Navigate to passenger list

Right Area - Tabs

Bookings Tab

  • Full Table: Shows all bookings for this passenger
  • Columns:
    • ID
    • Date (creation date)
    • Status (localized booking state)
    • Pickup Location
    • Dropoff Location
    • Demand (passenger count + special requirements)
  • Filters:
    • Status filter (dropdown)
    • Combined "All Cancelled" option
  • Sorting: Sort by ID, date, status, pickup time
  • Full Height: Table fills available space

Transactions Tab

  • Full Table: Shows all payment transactions
  • Columns:
    • ID
    • Date
    • Type (Payment/Refund)
    • Amount (currency formatted)
    • Status
    • Payment Method
  • Filtering: Filter by type, status
  • Sorting: Sort by date, amount
  • Full Height: Table fills available space

Unmask Functionality

The unmask feature uses secure API endpoints:

const unmaskEmail = async () => {
  setUnmaskingEmail(true);
  try {
    const response = await APIClient.post(
      `/api/v2/passenger/${passengerId}/unmask_email`,
      {}
    );
    if (response.data?.email) {
      setUnmaskedEmail(response.data.email);
    }
  } catch (error) {
    console.error('Failed to unmask email:', error);
  } finally {
    setUnmaskingEmail(false);
  }
};

Security Notes:

  • Each unmask is a separate API call
  • Unmasked data stored in component state only
  • Re-masking clears the unmasked data
  • No persistence - refreshing page re-masks

Layout Structure

<div className="flex-1 flex gap-6 min-h-0">
  {/* Left Sidebar */}
  <div className="w-80 flex-shrink-0">
    <Button onClick={() => router.push('/ptapp')}>
      <ArrowLeft /> {getText('ptapp.passengers.back_to_list')}
    </Button>

    <div className="border rounded-lg p-6 bg-card">
      <h2>{passenger.name}</h2>
      <p className="text-muted-foreground">{passenger.id}</p>

      {/* Personal info fields with unmask buttons */}
    </div>
  </div>

  {/* Right Content Area */}
  <div className="flex-1 min-h-0 flex flex-col">
    <Tabs defaultValue="bookings">
      <TabsList>
        <TabsTrigger value="bookings">Bookings</TabsTrigger>
        <TabsTrigger value="transactions">Transactions</TabsTrigger>
      </TabsList>

      <TabsContent value="bookings">
        <DemandProvider>
          <SGERPTable
            collection={api?.collections.booking ?? null}
            columns={bookingColumns}
            filters={bookingFilters}
            defaultFilters={{ customer_id: passengerId }}
            initialOrderBy="-created_at"
            fullscreenHeight
            skipInitialFetch={false}
          />
        </DemandProvider>
      </TabsContent>

      <TabsContent value="transactions">
        <TransactionTable
          collection={api?.collections.transaction ?? null}
          columns={transactionColumns}
          defaultFilters={{ passenger_id: passengerId }}
          fullscreenHeight
          skipInitialFetch={false}
        />
      </TabsContent>
    </Tabs>
  </div>
</div>

State Management

The component manages:

  • passenger: The passenger object
  • loading: Loading state
  • unmaskedEmail: Unmasked email value
  • unmaskedPhone: Unmasked phone value
  • unmaskingEmail: Loading state for email unmask
  • unmaskingPhone: Loading state for phone unmask

Loading States

Initial Load

Shows "Loading..." text while fetching passenger data

Unmask Loading

Shows spinner icon while unmasking email/phone

No Data

Shows "No data" message with back button if passenger not found

Booking Status Filter

The bookings table includes a special "All Cancelled" filter:

{
  value: 'cancelled_by_user,cancelled_in_calc',
  label: getText('booking_state.all_cancelled')
}

This combines multiple cancelled states into one filter option.

Transaction Columns Customization

The passenger column is removed from transaction table since we're already viewing a specific passenger:

const transactionColumns = useMemo(
  () => getDefaultTransactionColumns(getText).filter(
    col => col.key !== 'passenger_id'
  ),
  [getText]
);

Localization

Uses localization keys:

  • ptapp.passengers.back_to_list - Back button
  • ptapp.passengers.personal_info - Section header
  • ptapp.passengers.contact_no - Phone label
  • ptapp.passengers.gender - Gender label
  • ptapp.passengers.date_of_birth - DOB label
  • ptapp.passengers.bookings - Bookings tab
  • ptapp.passengers.transactions - Transactions tab
  • column.email, column.active, etc.
  • gender.male, gender.female, gender.other
  • booking_state.* - All booking states
  • common.loading, common.no_data

Dependencies

  • react (for useState, useEffect, useMemo)
  • next (for useRouter)
  • lucide-react (for ArrowLeft, Eye, EyeOff icons)
  • sgerp-frontend-lib (collections, hooks, tables, API client)
  • @/components/ui/button, @/components/ui/tabs, @/components/ui/spinner (shadcn/ui)

Notes

  • SGERP Context: Requires SGERPProvider wrapper
  • Collections: Uses passenger, booking, and transaction collections
  • Router: Uses Next.js useRouter for back navigation
  • Security: Unmask API calls are authenticated
  • Privacy: Default state is masked, unmask is temporary
  • Collection Check: Checks collection first before fetching by ID
  • Skip Initial Fetch: Set to false for booking/transaction tables to ensure data loads