- {/* PASSENGER IDENTITY */}
+ {/* SEARCH FLIGHT NUMBER / PNR CARD */}
- {/* FLIGHT CONTEXT */}
+ {/* AUTO FILLED NOTICE BANNER */}
+ {autoFilledNotice && (
+
+ )}
+
+ {/* PASSENGERS TABLE (SUPPORTING SELECT ALL & MULTI-SELECT) */}
+ {passengersList.length > 0 && (
+
-
Flight Context
+
Flight Context & Route
- handleInputChange("flightNumber", val as string)}
- options={[
- { label: "B7687YT", value: "B7687YT" },
- { label: "Q23SXD", value: "Q23SXD" },
- { label: "AZ404", value: "AZ404" },
- ]}
- />
- handleInputChange("date", e.target.value)}
- />
handleInputChange("origin", val as string)}
options={[
- { label: "FRA", value: "FRA" },
- { label: "LHR", value: "LHR" },
- { label: "SFO", value: "SFO" },
+ { label: "FRA - Frankfurt", value: "FRA" },
+ { label: "LHR - London Heathrow", value: "LHR" },
+ { label: "SFO - San Francisco", value: "SFO" },
+ { label: "JFK - New York JFK", value: "JFK" },
+ { label: "CDG - Paris Charles de Gaulle", value: "CDG" },
+ { label: "NRT - Tokyo Narita", value: "NRT" },
+ { label: "DXB - Dubai International", value: "DXB" },
]}
/>
handleInputChange("destination", val as string)}
options={[
- { label: "JFK", value: "JFK" },
- { label: "CDG", value: "CDG" },
- { label: "NRT", value: "NRT" },
+ { label: "JFK - New York JFK", value: "JFK" },
+ { label: "CDG - Paris Charles de Gaulle", value: "CDG" },
+ { label: "NRT - Tokyo Narita", value: "NRT" },
+ { label: "FRA - Frankfurt", value: "FRA" },
+ { label: "LHR - London Heathrow", value: "LHR" },
+ { label: "SFO - San Francisco", value: "SFO" },
+ { label: "DXB - Dubai International", value: "DXB" },
]}
/>
@@ -281,11 +566,7 @@ export default function AddRecoveryIncidents({ isOpen, onClose, incident }: AddR
placeholder="Selected Option"
value={formData.category}
onChange={(val) => handleInputChange("category", val as string)}
- options={[
- { label: "Flight Ops", value: "flight_ops" },
- { label: "Travel Exp", value: "travel_exp" },
- { label: "Weather", value: "weather" },
- ]}
+ options={DEFAULT_CATEGORY_OPTIONS}
/>
setMetrics(data)).catch(() => {});
+ getRecoveryMetrics().then((data) => setMetrics(data)).catch(() => { });
} catch (error) {
console.error("Failed to update status", error);
}
diff --git a/src/app/recoveryIncidents/disruptionMockService.ts b/src/app/recoveryIncidents/disruptionMockService.ts
new file mode 100644
index 0000000..f67429b
--- /dev/null
+++ b/src/app/recoveryIncidents/disruptionMockService.ts
@@ -0,0 +1,392 @@
+export interface MockPassenger {
+ id: string;
+ passengerName: string;
+ pnr: string;
+ passengerType: string; // e.g. "VIP Adult", "Adult", "High Value", "Child", "Infant"
+ nationality: string; // e.g. "German", "British", "American", "Japanese", "French"
+ loyaltyTier: string; // e.g. "Platinum", "Gold", "Silver", "Bronze", "Regular"
+ specialAssistance: string; // e.g. "Wheelchair (WCHR)", "None", "Dietary Meal", "Medical"
+ originalCabin: string; // e.g. "First Class", "Business Class", "Premium Economy", "Economy"
+ actualCabin: string; // e.g. "Business Class", "Economy", "First Class" (assigned cabin after disruption)
+ cabinClass?: string; // Legacy fallback/alias
+ isHighValue?: boolean;
+ seatNumber?: string;
+ ticketNumber?: string;
+}
+
+export interface MockDisruption {
+ flightNumber: string;
+ date: string; // YYYY-MM-DD
+ origin: string; // IATA code
+ destination: string; // IATA code
+ category: string; // e.g. "flight_ops", "weather", "travel_exp"
+ scenario: string; // e.g. "delayed_flight", "cancelled_flight", "missed_connection"
+ jurisdiction: string; // e.g. "EU261", "US_DOT", "UK261"
+ delayDuration: number; // in minutes
+ status: string; // e.g. "Delayed", "Cancelled", "Diverted"
+ description?: string;
+ passengers: MockPassenger[];
+}
+
+export const MOCK_DISRUPTIONS: Record = {
+ B7687YT: {
+ flightNumber: 'B7687YT',
+ date: '2026-08-14',
+ origin: 'FRA',
+ destination: 'JFK',
+ category: 'flight_ops',
+ scenario: 'delayed_flight',
+ jurisdiction: 'EU261',
+ delayDuration: 240,
+ status: 'Delayed',
+ description: 'Technical engine maintenance delay at Frankfurt Airport.',
+ passengers: [
+ {
+ id: 'p-101',
+ passengerName: 'John Doe',
+ pnr: 'FT7687T9I',
+ passengerType: 'VIP Adult',
+ nationality: 'German',
+ loyaltyTier: 'Platinum',
+ specialAssistance: 'Wheelchair (WCHR)',
+ originalCabin: 'First Class',
+ actualCabin: 'Business Class', // Downgraded during disruption
+ cabinClass: 'First Class',
+ isHighValue: true,
+ seatNumber: '4A',
+ ticketNumber: '0162394810239',
+ },
+ {
+ id: 'p-102',
+ passengerName: 'Sarah Jenkins',
+ pnr: 'SJ98231FA',
+ passengerType: 'High Value Adult',
+ nationality: 'British',
+ loyaltyTier: 'Gold',
+ specialAssistance: 'None',
+ originalCabin: 'First Class',
+ actualCabin: 'First Class',
+ cabinClass: 'First Class',
+ isHighValue: true,
+ seatNumber: '2F',
+ ticketNumber: '0162394810240',
+ },
+ {
+ id: 'p-103',
+ passengerName: 'Michael Chen',
+ pnr: 'MC441298X',
+ passengerType: 'Adult',
+ nationality: 'American',
+ loyaltyTier: 'Silver',
+ specialAssistance: 'Dietary Meal (VGML)',
+ originalCabin: 'Business Class',
+ actualCabin: 'Economy', // Downgraded
+ cabinClass: 'Business Class',
+ isHighValue: false,
+ seatNumber: '28C',
+ ticketNumber: '0162394810241',
+ },
+ {
+ id: 'p-104',
+ passengerName: 'Amanda Lewis',
+ pnr: 'AL551029P',
+ passengerType: 'Adult',
+ nationality: 'Canadian',
+ loyaltyTier: 'Bronze',
+ specialAssistance: 'None',
+ originalCabin: 'Economy',
+ actualCabin: 'Economy',
+ cabinClass: 'Economy',
+ isHighValue: false,
+ seatNumber: '31D',
+ ticketNumber: '0162394810242',
+ },
+ ],
+ },
+ Q23SXD: {
+ flightNumber: 'Q23SXD',
+ date: '2026-08-13',
+ origin: 'LHR',
+ destination: 'CDG',
+ category: 'weather',
+ scenario: 'cancelled_flight',
+ jurisdiction: 'UK261',
+ delayDuration: 360,
+ status: 'Cancelled',
+ description: 'Severe storm and heavy fog over London Heathrow.',
+ passengers: [
+ {
+ id: 'p-201',
+ passengerName: 'Robert Vance',
+ pnr: 'RV992104K',
+ passengerType: 'VIP Adult',
+ nationality: 'British',
+ loyaltyTier: 'Platinum',
+ specialAssistance: 'None',
+ originalCabin: 'First Class',
+ actualCabin: 'Business Class',
+ cabinClass: 'First Class',
+ isHighValue: true,
+ seatNumber: '1B',
+ ticketNumber: '1259920194812',
+ },
+ {
+ id: 'p-202',
+ passengerName: 'Emma Watson',
+ pnr: 'EW771029M',
+ passengerType: 'High Value Adult',
+ nationality: 'French',
+ loyaltyTier: 'Gold',
+ specialAssistance: 'Dietary Meal',
+ originalCabin: 'Business Class',
+ actualCabin: 'Business Class',
+ cabinClass: 'Business Class',
+ isHighValue: true,
+ seatNumber: '6D',
+ ticketNumber: '1259920194813',
+ },
+ {
+ id: 'p-203',
+ passengerName: 'David Miller',
+ pnr: 'DM334190Q',
+ passengerType: 'Adult',
+ nationality: 'Australian',
+ loyaltyTier: 'Regular',
+ specialAssistance: 'Wheelchair (WCHR)',
+ originalCabin: 'Economy',
+ actualCabin: 'Economy',
+ cabinClass: 'Economy',
+ isHighValue: false,
+ seatNumber: '19A',
+ ticketNumber: '1259920194814',
+ },
+ ],
+ },
+ AZ404: {
+ flightNumber: 'AZ404',
+ date: '2026-08-13',
+ origin: 'SFO',
+ destination: 'NRT',
+ category: 'travel_exp',
+ scenario: 'missed_connection',
+ jurisdiction: 'US_DOT',
+ delayDuration: 180,
+ status: 'Delayed',
+ description: 'Late arrival of incoming aircraft causing connection breakdown.',
+ passengers: [
+ {
+ id: 'p-301',
+ passengerName: 'Kaito Tanaka',
+ pnr: 'KT883192Z',
+ passengerType: 'High Value Adult',
+ nationality: 'Japanese',
+ loyaltyTier: 'Platinum',
+ specialAssistance: 'None',
+ originalCabin: 'Business Class',
+ actualCabin: 'Business Class',
+ cabinClass: 'Business Class',
+ isHighValue: true,
+ seatNumber: '11K',
+ ticketNumber: '0571120938491',
+ },
+ {
+ id: 'p-302',
+ passengerName: 'Lisa Ray',
+ pnr: 'LR110293Y',
+ passengerType: 'Adult',
+ nationality: 'American',
+ loyaltyTier: 'Silver',
+ specialAssistance: 'Unaccompanied Minor',
+ originalCabin: 'Premium Economy',
+ actualCabin: 'Economy',
+ cabinClass: 'Premium Economy',
+ isHighValue: false,
+ seatNumber: '16C',
+ ticketNumber: '0571120938492',
+ },
+ {
+ id: 'p-303',
+ passengerName: 'Carlos Gomez',
+ pnr: 'CG559102X',
+ passengerType: 'VIP Adult',
+ nationality: 'Mexican',
+ loyaltyTier: 'Gold',
+ specialAssistance: 'Medical Assistance',
+ originalCabin: 'Business Class',
+ actualCabin: 'Business Class',
+ cabinClass: 'Business Class',
+ isHighValue: true,
+ seatNumber: '8A',
+ ticketNumber: '0571120938493',
+ },
+ ],
+ },
+ BA178: {
+ flightNumber: 'BA178',
+ date: '2026-08-15',
+ origin: 'JFK',
+ destination: 'LHR',
+ category: 'flight_ops',
+ scenario: 'delayed_flight',
+ jurisdiction: 'UK261',
+ delayDuration: 300,
+ status: 'Delayed',
+ description: 'Air traffic control delay on transatlantic sector.',
+ passengers: [
+ {
+ id: 'p-401',
+ passengerName: 'Harrison Ford',
+ pnr: 'HF908123A',
+ passengerType: 'VIP Adult',
+ nationality: 'American',
+ loyaltyTier: 'Platinum',
+ specialAssistance: 'None',
+ originalCabin: 'First Class',
+ actualCabin: 'First Class',
+ cabinClass: 'First Class',
+ isHighValue: true,
+ seatNumber: '2A',
+ ticketNumber: '1250912837192',
+ },
+ {
+ id: 'p-402',
+ passengerName: 'Clara Oswald',
+ pnr: 'CO449182B',
+ passengerType: 'High Value Adult',
+ nationality: 'British',
+ loyaltyTier: 'Gold',
+ specialAssistance: 'None',
+ originalCabin: 'Business Class',
+ actualCabin: 'Premium Economy',
+ cabinClass: 'Business Class',
+ isHighValue: true,
+ seatNumber: '12E',
+ ticketNumber: '1250912837193',
+ },
+ ],
+ },
+ EK202: {
+ flightNumber: 'EK202',
+ date: '2026-08-14',
+ origin: 'JFK',
+ destination: 'DXB',
+ category: 'weather',
+ scenario: 'delayed_flight',
+ jurisdiction: 'US_DOT',
+ delayDuration: 210,
+ status: 'Delayed',
+ description: 'Severe thunderstorm delay prior to pushback.',
+ passengers: [
+ {
+ id: 'p-501',
+ passengerName: 'Tariq Al-Mansoor',
+ pnr: 'TM771920K',
+ passengerType: 'VIP Adult',
+ nationality: 'Emirati',
+ loyaltyTier: 'Platinum',
+ specialAssistance: 'None',
+ originalCabin: 'First Class',
+ actualCabin: 'First Class',
+ cabinClass: 'First Class',
+ isHighValue: true,
+ seatNumber: '1A',
+ ticketNumber: '1769910293841',
+ },
+ {
+ id: 'p-502',
+ passengerName: 'Fatima Al-Sayed',
+ pnr: 'FA882019L',
+ passengerType: 'High Value Adult',
+ nationality: 'Emirati',
+ loyaltyTier: 'Gold',
+ specialAssistance: 'Dietary Meal',
+ originalCabin: 'Business Class',
+ actualCabin: 'Business Class',
+ cabinClass: 'Business Class',
+ isHighValue: true,
+ seatNumber: '7K',
+ ticketNumber: '1769910293842',
+ },
+ ],
+ },
+};
+
+/**
+ * Get options list of flight numbers for dropdown selection
+ */
+export function getMockFlightNumbers(): { label: string; value: string; description: string }[] {
+ return Object.values(MOCK_DISRUPTIONS).map((item) => ({
+ label: `${item.flightNumber} (${item.origin} → ${item.destination} • ${item.delayDuration}m delay)`,
+ value: item.flightNumber,
+ description: item.description || '',
+ }));
+}
+
+/**
+ * Get disruption detail for a given flight number or PNR reference
+ */
+export function getMockDisruptionByFlightNumber(query: string): MockDisruption | undefined {
+ if (!query) return undefined;
+ const upper = query.trim().toUpperCase();
+
+ // 1. Direct flight number match
+ const flightKey = Object.keys(MOCK_DISRUPTIONS).find((k) => k.toUpperCase() === upper);
+ if (flightKey) return MOCK_DISRUPTIONS[flightKey];
+
+ // 2. Direct PNR match search across flights
+ for (const item of Object.values(MOCK_DISRUPTIONS)) {
+ const match = item.passengers.some((p) => p.pnr.toUpperCase() === upper);
+ if (match) return item;
+ }
+
+ return undefined;
+}
+
+/**
+ * Get list of passengers for a flight
+ */
+export function getPassengersForFlight(flightNumber: string): MockPassenger[] {
+ const disruption = getMockDisruptionByFlightNumber(flightNumber);
+ return disruption ? disruption.passengers : [];
+}
+
+/**
+ * Get specific passenger details by flight number and PNR
+ */
+export function getMockPassengerByPnr(flightNumber: string, pnr: string): MockPassenger | undefined {
+ const passengers = getPassengersForFlight(flightNumber);
+ if (!pnr) return undefined;
+ const upperPnr = pnr.trim().toUpperCase();
+ return passengers.find((p) => p.pnr.toUpperCase() === upperPnr);
+}
+
+/**
+ * Search across flights and PNRs, returning disruption and optionally matched passenger ID
+ */
+export function searchDisruptionOrPassenger(query: string): { disruption?: MockDisruption; matchedPassengerId?: string } {
+ if (!query) return {};
+ const upper = query.trim().toUpperCase();
+
+ // Search flight number
+ const flightKey = Object.keys(MOCK_DISRUPTIONS).find((k) => k.toUpperCase() === upper);
+ if (flightKey) {
+ return { disruption: MOCK_DISRUPTIONS[flightKey] };
+ }
+
+ // Search PNR
+ for (const item of Object.values(MOCK_DISRUPTIONS)) {
+ const passenger = item.passengers.find((p) => p.pnr.toUpperCase() === upper);
+ if (passenger) {
+ return { disruption: item, matchedPassengerId: passenger.id };
+ }
+ }
+
+ return {};
+}
+
+/**
+ * Get all mock disruptions
+ */
+export function getAllMockDisruptions(): MockDisruption[] {
+ return Object.values(MOCK_DISRUPTIONS);
+}
diff --git a/src/app/recoveryIncidents/tabs/AuditTrailTab.tsx b/src/app/recoveryIncidents/tabs/AuditTrailTab.tsx
index 0ffb2ab..5e5a951 100644
--- a/src/app/recoveryIncidents/tabs/AuditTrailTab.tsx
+++ b/src/app/recoveryIncidents/tabs/AuditTrailTab.tsx
@@ -1,100 +1,144 @@
+import { useState, useEffect } from 'react';
import { ClipboardTextIcon, CheckCircleIcon } from '@phosphor-icons/react';
+import { formatDate } from '../../../utils/formatDate';
+import { getIncidentAuditTrail, type AuditTrailStepDto } from '../RecoveryIncidentsApi';
+import type { RecoveryIncident } from '../RecoveryIncidentsTypes';
+
+interface AuditTrailTabProps {
+ incident?: RecoveryIncident | null;
+}
+
+export default function AuditTrailTab({ incident }: AuditTrailTabProps) {
+ const [apiSteps, setApiSteps] = useState([]);
+ const [loading, setLoading] = useState(false);
+
+ const code = incident?.recoveryCode || 'REC-INCIDENT';
+ const passenger = incident?.passengerName || 'Passenger';
+ const flight = incident?.flightNumber || 'Flight';
+ const route = incident?.flightRoute || 'Route';
+ const status = incident?.status || 'Pending';
+ const evaluation = incident?.evaluation;
+ const policyName = evaluation?.policyName || 'Standard Policy';
+ const cohortName = evaluation?.matchedCohortName || 'General Audience';
+ const recoveryScore = evaluation?.recoveryScore || 0;
+
+ useEffect(() => {
+ if (!incident?.id) return;
+ setLoading(true);
+ getIncidentAuditTrail(incident.id)
+ .then((data) => {
+ if (Array.isArray(data)) {
+ setApiSteps(data);
+ }
+ })
+ .catch((err) => {
+ console.error('Failed to load backend audit trail:', err);
+ })
+ .finally(() => {
+ setLoading(false);
+ });
+ }, [incident?.id, incident?.status, incident?.evaluation?.updatedAt]);
+
+ const checkIcon = ;
+
+ const isApproved = status.toLowerCase().includes('appr');
+ const isRejected = status.toLowerCase().includes('reject');
+
+ // Initial default steps if API response is loading
+ const defaultInitialSteps: AuditTrailStepDto[] = [
+ {
+ id: 'step-1',
+ title: 'Flight Disruption Recorded',
+ description: `Disruption identified for ${passenger} on flight ${flight} (${route}).`,
+ timestamp: incident?.createdAt || incident?.date || new Date().toISOString(),
+ status: 'completed',
+ },
+ {
+ id: 'step-2',
+ title: 'Target Audience Cohort Evaluated',
+ description: `Automated audience eligibility assessment performed against active frameworks. Matched cohort: "${cohortName}".`,
+ timestamp: incident?.createdAt || new Date().toISOString(),
+ status: 'completed',
+ },
+ {
+ id: 'step-3',
+ title: 'Policy Evaluated',
+ description: `Evaluated against active rules under "${policyName}". Recovery Score: ${recoveryScore}/100.`,
+ timestamp: evaluation?.createdAt || incident?.createdAt || new Date().toISOString(),
+ status: 'completed',
+ },
+ {
+ id: 'step-4',
+ title: `Status: ${status}`,
+ description: `Case officer assigned. Case status updated to "${status}".`,
+ timestamp: incident?.updatedAt || new Date().toISOString(),
+ status: 'completed',
+ },
+ {
+ id: 'step-5',
+ title: 'Recovery Resolution',
+ description: isApproved
+ ? 'Recovery incident approved. Automated settlement and customer notification dispatched.'
+ : isRejected
+ ? 'Recovery incident rejected by case officer.'
+ : 'Refund and compensation settlement will initiate upon final approval.',
+ timestamp: incident?.updatedAt || new Date().toISOString(),
+ status: isApproved ? 'completed' : isRejected ? 'rejected' : 'pending',
+ },
+ ];
+
+ const displaySteps = apiSteps.length > 0 ? apiSteps : defaultInitialSteps;
-export default function AuditTrailTab() {
return (
-
-
-
Lifecycle Timeline & Audit Trail
+
+
+
+
+ Lifecycle Timeline & Audit Trail ({code})
+
+
+ {loading && (
+
Loading updates...
+ )}
- {/* Step 1: Flight Disruption Recorded */}
-
-
-
-
-
-
-
-
-
Flight Disruption Recorded
-
Denied Boarding identified for flight Q23SXD.
-
-
Assessment Point
-
-
+ {displaySteps.map((step, idx) => {
+ const isLast = idx === displaySteps.length - 1;
+ const rawTime = (step as any).createdAt || step.timestamp;
- {/* Step 2: Simulation Engine Executed */}
-
-
-
-
-
-
-
Simulation Engine Executed
-
Automated eligibility assessment performed against active frameworks.
-
-
T-10m
-
-
+ return (
+
+ {/* Connecting Line */}
+ {!isLast && (
+
+ )}
- {/* Step 3: Policy Evaluated */}
-
-
-
-
-
-
-
Policy Evaluated
-
Pending final approval from Case Officer.
-
-
T-8m
-
-
+ {/* Node Icon Indicator */}
+
- {/* Step 4: Status: Under Review */}
-
-
-
-
-
-
-
Status: Under Review
-
Tuesday, 28 May 2024
-
-
Current
-
-
+ {/* Step Details */}
+
+
+
{step.title}
+
+ {step.description}
+
+
- {/* Step 5: Policy Engine Rerun */}
-
-
-
-
-
-
-
Policy Engine Rerun
-
Manual re-assessment triggered. Applied: Standard Policy.
+
+ {formatDate(rawTime)}
+
+
-
Recent
-
-
-
- {/* Step 6: Recovery Resolution */}
-
-
-
-
-
-
Recovery Resolution
-
Refund and compensation settlement will initiate upon final approval.
-
-
Pending
-
-
-
+ );
+ })}
diff --git a/src/app/recoveryIncidents/tabs/CaseDetailsTab.tsx b/src/app/recoveryIncidents/tabs/CaseDetailsTab.tsx
index 25f8d9e..3388c34 100644
--- a/src/app/recoveryIncidents/tabs/CaseDetailsTab.tsx
+++ b/src/app/recoveryIncidents/tabs/CaseDetailsTab.tsx
@@ -8,6 +8,12 @@ interface CaseDetailsTabProps {
export default function CaseDetailsTab({ incident }: CaseDetailsTabProps) {
if (!incident) return null;
+ const isDowngraded = Boolean(
+ incident.originalCabin &&
+ incident.actualCabin &&
+ incident.originalCabin !== incident.actualCabin
+ );
+
return (
{/* PASSENGER INFORMATION */}
@@ -28,12 +34,24 @@ export default function CaseDetailsTab({ incident }: CaseDetailsTabProps) {
LOYALTY TIER
- None
+ {incident.loyaltyTier || 'Regular'}
PASSENGER TYPE
- Adult
+ {incident.passengerType || 'Adult'}
+ {incident.nationality && (
+
+ NATIONALITY
+ {incident.nationality}
+
+ )}
+ {incident.specialAssistance && (
+
+ SPECIAL ASSISTANCE
+ {incident.specialAssistance}
+
+ )}
@@ -55,19 +73,32 @@ export default function CaseDetailsTab({ incident }: CaseDetailsTabProps) {