607 lines
25 KiB
TypeScript
607 lines
25 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { FileText, User, Airplane, WarningCircle, CaretRight, Lightning } from '@phosphor-icons/react';
|
|
import {
|
|
CustomModal,
|
|
CustomInput,
|
|
CustomDropdown,
|
|
CustomCheckBox,
|
|
} from "../../../components/custom";
|
|
import { createRecoveryIncident, updateRecoveryIncident, getRecoveryIncidents } from '../RecoveryIncidentsApi';
|
|
import { getMembershipTiers, getCategoryValues } from '../../configuration/masterData/MasterDataApi';
|
|
import {
|
|
getMockFlightNumbers,
|
|
searchDisruptionOrPassenger,
|
|
type MockPassenger,
|
|
} from '../disruptionMockService';
|
|
import type { RecoveryIncident } from '../RecoveryIncidentsTypes';
|
|
|
|
interface AddRecoveryIncidentsProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
incident?: RecoveryIncident | null;
|
|
}
|
|
|
|
const SECTION_TITLE_CLASS = "flex items-center gap-2 mb-4 text-[#4A5568] font-bold text-xs tracking-wider uppercase";
|
|
const SECTION_CONTAINER_CLASS = "bg-[#F9FAFB] rounded-[14px] p-5 border border-gray-100";
|
|
|
|
const DEFAULT_CATEGORY_OPTIONS = [
|
|
{ label: "Flight Ops", value: "flight_ops" },
|
|
{ label: "Travel Exp", value: "travel_exp" },
|
|
{ label: "Weather", value: "weather" },
|
|
{ label: "Technical Fault", value: "technical_fault" },
|
|
];
|
|
|
|
const DEFAULT_SCENARIO_OPTIONS = [
|
|
{ label: "Delayed Flight", value: "delayed_flight" },
|
|
{ label: "Cancelled Flight", value: "cancelled_flight" },
|
|
{ label: "Missed Connection", value: "missed_connection" },
|
|
];
|
|
|
|
const DEFAULT_JURISDICTION_OPTIONS = [
|
|
{ label: "EU261 (European Union)", value: "EU261" },
|
|
{ label: "US DOT (United States)", value: "US_DOT" },
|
|
{ label: "UK261 (United Kingdom)", value: "UK261" },
|
|
{ label: "CAA SG (Singapore)", value: "CAA_SG" },
|
|
];
|
|
|
|
export default function AddRecoveryIncidents({ isOpen, onClose, incident }: AddRecoveryIncidentsProps) {
|
|
const [, setLoyaltyTierOptions] = useState<{ label: string; value: string }[]>([]);
|
|
const [jurisdictionOptions, setJurisdictionOptions] = useState<{ label: string; value: string }[]>(DEFAULT_JURISDICTION_OPTIONS);
|
|
const [scenarioOptions, setScenarioOptions] = useState<{ label: string; value: string }[]>(DEFAULT_SCENARIO_OPTIONS);
|
|
|
|
const [query, setQuery] = useState<string>('');
|
|
const [passengersList, setPassengersList] = useState<MockPassenger[]>([]);
|
|
const [selectedPassengerIds, setSelectedPassengerIds] = useState<string[]>([]);
|
|
const [autoFilledNotice, setAutoFilledNotice] = useState<string | null>(null);
|
|
const [perksClaimedMap, setPerksClaimedMap] = useState<Record<string, boolean>>({});
|
|
const [existingIncidents, setExistingIncidents] = useState<RecoveryIncident[]>([]);
|
|
|
|
const [formData, setFormData] = useState({
|
|
passengerName: "",
|
|
pnr: "",
|
|
loyaltyTier: "",
|
|
flightNumber: "",
|
|
date: "",
|
|
origin: "",
|
|
destination: "",
|
|
category: "",
|
|
scenario: "",
|
|
jurisdiction: "",
|
|
delayDuration: "",
|
|
isPerksClaimed: false,
|
|
});
|
|
|
|
const flightNumberOptions = getMockFlightNumbers();
|
|
|
|
// Flexible option matcher to bridge master data codes/labels with mock response codes
|
|
const matchOption = (val: string | undefined, options: { label: string; value: string }[]) => {
|
|
if (!val) return '';
|
|
const lowerVal = val.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
const found = options.find((opt) => {
|
|
const lowerValOpt = opt.value.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
const lowerLabelOpt = opt.label.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
return lowerValOpt === lowerVal || lowerLabelOpt === lowerVal || lowerValOpt.includes(lowerVal) || lowerVal.includes(lowerValOpt);
|
|
});
|
|
return found ? found.value : val;
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (isOpen) {
|
|
getMembershipTiers()
|
|
.then((items) => {
|
|
if (Array.isArray(items) && items.length > 0) {
|
|
const activeOptions = items
|
|
.filter((m) => m.isActive !== false)
|
|
.map((m) => ({
|
|
label: m.label || m.value,
|
|
value: m.value || m.id || m.label,
|
|
}));
|
|
if (activeOptions.length > 0) setLoyaltyTierOptions(activeOptions);
|
|
}
|
|
})
|
|
.catch(() => { });
|
|
|
|
getCategoryValues('jurisdiction')
|
|
.then((items) => {
|
|
if (Array.isArray(items) && items.length > 0) {
|
|
const activeOptions = items
|
|
.filter((m) => m.isActive !== false)
|
|
.map((m) => ({
|
|
label: m.label || m.name || m.value,
|
|
value: m.value || m.code || m.id || m.label,
|
|
}));
|
|
if (activeOptions.length > 0) setJurisdictionOptions(activeOptions);
|
|
}
|
|
})
|
|
.catch(() => { });
|
|
|
|
getCategoryValues('flight-disruption-type')
|
|
.then((items) => {
|
|
if (Array.isArray(items) && items.length > 0) {
|
|
const activeOptions = items
|
|
.filter((m) => m.isActive !== false)
|
|
.map((m) => ({
|
|
label: m.label || m.name || m.value,
|
|
value: m.value || m.code || m.id || m.label,
|
|
}));
|
|
if (activeOptions.length > 0) setScenarioOptions(activeOptions);
|
|
}
|
|
})
|
|
.catch(() => { });
|
|
}
|
|
}, [isOpen]);
|
|
|
|
useEffect(() => {
|
|
if (incident && isOpen) {
|
|
const [origin, destination] = incident.flightRoute ? incident.flightRoute.split(' → ') : ["", ""];
|
|
setFormData({
|
|
passengerName: incident.passengerName || "",
|
|
pnr: incident.pnr || "",
|
|
loyaltyTier: (incident as any).loyaltyTier || "",
|
|
flightNumber: incident.flightNumber || "",
|
|
date: incident.date ? incident.date.split('T')[0] : "",
|
|
origin: origin?.trim() || "",
|
|
destination: destination?.trim() || "",
|
|
category: incident.category || "",
|
|
scenario: (incident as any).scenario || "",
|
|
jurisdiction: (incident as any).jurisdiction || "",
|
|
delayDuration: (incident as any).delayDuration ? String((incident as any).delayDuration) : "",
|
|
isPerksClaimed: incident.isPerksClaimed || false,
|
|
});
|
|
const initialQuery = incident.flightNumber || incident.pnr || "";
|
|
setQuery(initialQuery);
|
|
|
|
const res = searchDisruptionOrPassenger(initialQuery);
|
|
if (res.disruption) {
|
|
const passengers = res.disruption.passengers || [];
|
|
setPassengersList(passengers);
|
|
|
|
getRecoveryIncidents().then(all => {
|
|
const flightIncidents = all.filter(i => i.flightNumber === res.disruption!.flightNumber);
|
|
setExistingIncidents(flightIncidents);
|
|
|
|
const newPerks: Record<string, boolean> = {};
|
|
const newSelected: string[] = [];
|
|
|
|
flightIncidents.forEach(fi => {
|
|
const p = passengers.find(pass => pass.pnr === fi.pnr && pass.passengerName === fi.passengerName);
|
|
if (p) {
|
|
newSelected.push(p.id);
|
|
if (fi.isPerksClaimed) newPerks[p.id] = true;
|
|
}
|
|
});
|
|
|
|
const p = passengers.find(pass => pass.pnr === incident.pnr && pass.passengerName === incident.passengerName);
|
|
if (p) {
|
|
if (!newSelected.includes(p.id)) newSelected.push(p.id);
|
|
if (incident.isPerksClaimed) newPerks[p.id] = true;
|
|
}
|
|
|
|
setSelectedPassengerIds(newSelected.length > 0 ? newSelected : passengers.map(p => p.id));
|
|
setPerksClaimedMap(newPerks);
|
|
}).catch(() => {
|
|
setSelectedPassengerIds(passengers.map(p => p.id));
|
|
});
|
|
}
|
|
setAutoFilledNotice(null);
|
|
} else if (isOpen) {
|
|
setFormData({
|
|
passengerName: "",
|
|
pnr: "",
|
|
loyaltyTier: "",
|
|
flightNumber: "",
|
|
date: "",
|
|
origin: "",
|
|
destination: "",
|
|
category: "",
|
|
scenario: "",
|
|
jurisdiction: "",
|
|
delayDuration: "",
|
|
isPerksClaimed: false,
|
|
});
|
|
setQuery('');
|
|
setPassengersList([]);
|
|
setSelectedPassengerIds([]);
|
|
setAutoFilledNotice(null);
|
|
setPerksClaimedMap({});
|
|
setExistingIncidents([]);
|
|
}
|
|
}, [incident, isOpen]);
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
const handleInputChange = (field: string, value: string) => {
|
|
setFormData((prev) => ({ ...prev, [field]: value }));
|
|
};
|
|
|
|
|
|
// Helper when user types or selects flight number or PNR
|
|
const handleQueryChange = (val: string) => {
|
|
setQuery(val);
|
|
const res = searchDisruptionOrPassenger(val);
|
|
if (res.disruption) {
|
|
const d = res.disruption;
|
|
const passengers = d.passengers || [];
|
|
setPassengersList(passengers);
|
|
|
|
if (res.matchedPassengerId) {
|
|
setSelectedPassengerIds([res.matchedPassengerId]);
|
|
} else {
|
|
// Select all passengers by default when loading a flight manifest
|
|
setSelectedPassengerIds(passengers.map((p) => p.id));
|
|
}
|
|
|
|
const matchedCategory = matchOption(d.category, DEFAULT_CATEGORY_OPTIONS);
|
|
const matchedScenario = matchOption(d.scenario, scenarioOptions);
|
|
const matchedJurisdiction = matchOption(d.jurisdiction, jurisdictionOptions);
|
|
|
|
const firstP = passengers[0];
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
flightNumber: d.flightNumber,
|
|
date: d.date,
|
|
origin: d.origin,
|
|
destination: d.destination,
|
|
category: matchedCategory,
|
|
scenario: matchedScenario,
|
|
jurisdiction: matchedJurisdiction,
|
|
delayDuration: String(d.delayDuration),
|
|
passengerName: firstP ? firstP.passengerName : prev.passengerName,
|
|
pnr: firstP ? firstP.pnr : prev.pnr,
|
|
loyaltyTier: firstP ? firstP.loyaltyTier : prev.loyaltyTier,
|
|
}));
|
|
|
|
const categoryLabel = DEFAULT_CATEGORY_OPTIONS.find((c) => c.value === matchedCategory)?.label || matchedCategory;
|
|
const scenarioLabel = scenarioOptions.find((s) => s.value === matchedScenario)?.label || matchedScenario;
|
|
const jurisdictionLabel = jurisdictionOptions.find((j) => j.value === matchedJurisdiction)?.label || matchedJurisdiction;
|
|
|
|
setAutoFilledNotice(
|
|
`Disruption Mock Synced • Flight ${d.flightNumber} (${d.origin} → ${d.destination}) • Jurisdiction: ${jurisdictionLabel} • Category: ${categoryLabel} • Scenario: ${scenarioLabel} • Delay: ${d.delayDuration} mins • ${passengers.length} passenger(s) on manifest.`
|
|
);
|
|
} else {
|
|
setPassengersList([]);
|
|
setSelectedPassengerIds([]);
|
|
setAutoFilledNotice(null);
|
|
setFormData((prev) => ({ ...prev, flightNumber: val }));
|
|
}
|
|
};
|
|
|
|
// Select all / Deselect all passengers
|
|
const handleToggleSelectAll = () => {
|
|
if (selectedPassengerIds.length === passengersList.length) {
|
|
setSelectedPassengerIds([]);
|
|
} else {
|
|
setSelectedPassengerIds(passengersList.map((p) => p.id));
|
|
}
|
|
};
|
|
|
|
const handleTogglePerksClaimed = (passengerId: string, checked: boolean) => {
|
|
setPerksClaimedMap(prev => ({ ...prev, [passengerId]: checked }));
|
|
};
|
|
|
|
// Toggle individual passenger row selection
|
|
const handleTogglePassengerRow = (passengerId: string) => {
|
|
setSelectedPassengerIds((prev) => {
|
|
const exists = prev.includes(passengerId);
|
|
let updated: string[];
|
|
if (exists) {
|
|
updated = prev.filter((id) => id !== passengerId);
|
|
} else {
|
|
updated = [...prev, passengerId];
|
|
}
|
|
|
|
const primaryP = passengersList.find((p) => p.id === (updated[0] || passengerId));
|
|
if (primaryP) {
|
|
setFormData((f) => ({
|
|
...f,
|
|
passengerName: updated.length > 1 ? `${primaryP.passengerName} (+${updated.length - 1} more)` : primaryP.passengerName,
|
|
pnr: primaryP.pnr,
|
|
loyaltyTier: primaryP.loyaltyTier,
|
|
}));
|
|
}
|
|
return updated;
|
|
});
|
|
};
|
|
|
|
const handleSubmit = async () => {
|
|
setLoading(true);
|
|
try {
|
|
const selectedPassengers = passengersList.filter((p) => selectedPassengerIds.includes(p.id));
|
|
|
|
if (selectedPassengers.length > 0) {
|
|
const promises = selectedPassengers.map((p) => {
|
|
const existingInc = existingIncidents.find(fi => fi.pnr === p.pnr && fi.passengerName === p.passengerName)
|
|
|| (incident && incident.pnr === p.pnr && incident.passengerName === p.passengerName ? incident : null);
|
|
|
|
const payload = {
|
|
recoveryCode: existingInc ? existingInc.recoveryCode : "REC-" + Math.floor(Math.random() * 100000),
|
|
passengerName: p.passengerName,
|
|
pnr: p.pnr,
|
|
loyaltyTier: p.loyaltyTier,
|
|
passengerType: p.passengerType,
|
|
nationality: p.nationality,
|
|
specialAssistance: p.specialAssistance,
|
|
cabinClass: p.cabinClass,
|
|
originalCabin: p.originalCabin,
|
|
actualCabin: p.actualCabin,
|
|
flightNumber: formData.flightNumber || "TBD",
|
|
flightRoute: `${formData.origin || 'UNK'} → ${formData.destination || 'UNK'}`,
|
|
origin: formData.origin || undefined,
|
|
destination: formData.destination || undefined,
|
|
date: formData.date ? new Date(formData.date).toISOString() : new Date().toISOString(),
|
|
category: formData.category || "General",
|
|
status: existingInc ? (existingInc.status || "Pending") : "Pending",
|
|
value: existingInc ? existingInc.value : "$0",
|
|
isPerksClaimed: perksClaimedMap[p.id] || false,
|
|
jurisdiction: formData.jurisdiction || undefined,
|
|
delayDuration: formData.delayDuration ? Number(formData.delayDuration) : undefined,
|
|
scenario: formData.scenario || undefined,
|
|
recoverySource: existingInc ? (existingInc.recoverySource || 'Manual Disruption Entry') : 'Manual Disruption Entry',
|
|
};
|
|
|
|
if (existingInc) {
|
|
return updateRecoveryIncident(existingInc.id, payload);
|
|
} else {
|
|
return createRecoveryIncident(payload);
|
|
}
|
|
});
|
|
|
|
await Promise.all(promises);
|
|
}
|
|
onClose();
|
|
} catch (error: any) {
|
|
console.error("Error saving incident(s):", error.response?.data || error.message || error);
|
|
alert(`Failed to save incident(s): ${error.response?.data?.message || 'Unknown error'}`);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const isAllSelected = passengersList.length > 0 && selectedPassengerIds.length === passengersList.length;
|
|
|
|
return (
|
|
<CustomModal
|
|
isOpen={isOpen}
|
|
onClose={onClose}
|
|
title={incident ? "Edit Recovery Incident" : "New Recovery Incident"}
|
|
description="Log disruption cases and assess against policy frameworks"
|
|
icon={<FileText className="text-[#1B9869]" />}
|
|
size="xl"
|
|
primaryAction={{
|
|
label: loading
|
|
? "Saving..."
|
|
: incident
|
|
? "Save Changes"
|
|
: selectedPassengerIds.length > 1
|
|
? `Assess & Log (${selectedPassengerIds.length}) Incidents`
|
|
: "Assess & Log Incident",
|
|
onClick: handleSubmit,
|
|
icon: <CaretRight size={16} />,
|
|
}}
|
|
secondaryAction={{
|
|
label: "Discard",
|
|
onClick: onClose,
|
|
}}
|
|
>
|
|
<div className="flex flex-col gap-5">
|
|
{/* SEARCH FLIGHT NUMBER / PNR CARD */}
|
|
<div className={SECTION_CONTAINER_CLASS}>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label className="block text-xs font-semibold text-[#4A5568] mb-1.5">
|
|
Flight Number or PNR Reference
|
|
</label>
|
|
<CustomDropdown
|
|
placeholder="e.g. FT7687T9I or B7687YT"
|
|
value={query}
|
|
onChange={(val) => handleQueryChange(val as string)}
|
|
options={flightNumberOptions}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<CustomInput
|
|
type="date"
|
|
label="Date"
|
|
placeholder="Selected Option"
|
|
value={formData.date}
|
|
onChange={(e) => handleInputChange("date", e.target.value)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* AUTO FILLED NOTICE BANNER */}
|
|
{autoFilledNotice && (
|
|
<div className="flex items-center gap-3 p-3 bg-emerald-50 border border-emerald-200 text-emerald-800 rounded-xl text-xs font-medium">
|
|
<Lightning size={18} weight="fill" className="text-emerald-600 shrink-0" />
|
|
<span className="flex-1">{autoFilledNotice}</span>
|
|
</div>
|
|
)}
|
|
|
|
{/* PASSENGERS TABLE (SUPPORTING SELECT ALL & MULTI-SELECT) */}
|
|
{passengersList.length > 0 && (
|
|
<div className="bg-white rounded-xl border border-gray-200 overflow-hidden shadow-xs">
|
|
<div className="px-4 py-3 bg-[#F9FAFB] border-b border-gray-200 flex items-center justify-between">
|
|
<span className="text-xs font-bold text-gray-700 tracking-wider uppercase flex items-center gap-2">
|
|
<User size={16} className="text-[#1B9869]" />
|
|
Passenger Manifest Details (Selected: {selectedPassengerIds.length} of {passengersList.length})
|
|
</span>
|
|
<button
|
|
type="button"
|
|
onClick={handleToggleSelectAll}
|
|
className="text-xs text-[#1B9869] hover:underline font-semibold cursor-pointer"
|
|
>
|
|
{isAllSelected ? "Deselect All" : "Select All Passengers"}
|
|
</button>
|
|
</div>
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full text-left text-xs border-collapse">
|
|
<thead>
|
|
<tr className="bg-[#F8FAFC] border-b border-gray-200 text-[#718096] font-semibold">
|
|
<th className="py-3 px-4 w-10 text-center">
|
|
<input
|
|
type="checkbox"
|
|
checked={isAllSelected}
|
|
onChange={handleToggleSelectAll}
|
|
className="w-4 h-4 text-[#1B9869] rounded border-gray-300 focus:ring-[#1B9869] cursor-pointer"
|
|
title="Select All Passengers"
|
|
/>
|
|
</th>
|
|
<th className="py-3 px-4">PNR Number</th>
|
|
<th className="py-3 px-4">Passanger name</th>
|
|
<th className="py-3 px-4">Passanger type</th>
|
|
<th className="py-3 px-4">Booked Cabin</th>
|
|
<th className="py-3 px-4">Assigned Cabin</th>
|
|
<th className="py-3 px-4">Nationality</th>
|
|
<th className="py-3 px-4">loyality type</th>
|
|
<th className="py-3 px-4">Special assisstance</th>
|
|
<th className="py-3 px-4 text-center">Perks Claimed</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-gray-100">
|
|
{passengersList.map((p) => {
|
|
const isSelected = selectedPassengerIds.includes(p.id);
|
|
const isDowngraded = Boolean(p.originalCabin && p.actualCabin && p.originalCabin !== p.actualCabin);
|
|
return (
|
|
<tr
|
|
key={p.id}
|
|
onClick={() => handleTogglePassengerRow(p.id)}
|
|
className={`cursor-pointer transition-colors ${isSelected
|
|
? 'bg-emerald-50/70 border-l-4 border-l-[#1B9869]'
|
|
: 'hover:bg-gray-50/80'
|
|
}`}
|
|
>
|
|
<td className="py-3 px-4 text-center" onClick={(e) => e.stopPropagation()}>
|
|
<input
|
|
type="checkbox"
|
|
checked={isSelected}
|
|
onChange={() => handleTogglePassengerRow(p.id)}
|
|
className="w-4 h-4 text-[#1B9869] rounded border-gray-300 focus:ring-[#1B9869] cursor-pointer"
|
|
/>
|
|
</td>
|
|
<td className="py-3 px-4 font-mono font-medium text-gray-900">{p.pnr}</td>
|
|
<td className="py-3 px-4 font-semibold text-gray-800">{p.passengerName}</td>
|
|
<td className="py-3 px-4 text-gray-600">{p.passengerType}</td>
|
|
<td className="py-3 px-4 text-gray-700 font-medium">{p.originalCabin || p.cabinClass || 'Economy'}</td>
|
|
<td className="py-3 px-4">
|
|
<span
|
|
className={`inline-flex items-center px-2 py-0.5 rounded text-[11px] font-semibold ${isDowngraded ? 'bg-amber-100 text-amber-900 border border-amber-300' : 'text-gray-700'
|
|
}`}
|
|
>
|
|
{p.actualCabin || p.cabinClass || 'Economy'}
|
|
{isDowngraded && <span className="ml-1 text-[10px] text-amber-700 font-bold">(Downgraded)</span>}
|
|
</span>
|
|
</td>
|
|
<td className="py-3 px-4 text-gray-600">{p.nationality}</td>
|
|
<td className="py-3 px-4">
|
|
<span
|
|
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-[11px] font-semibold ${p.loyaltyTier === 'Platinum'
|
|
? 'bg-purple-100 text-purple-800'
|
|
: p.loyaltyTier === 'Gold'
|
|
? 'bg-amber-100 text-amber-800'
|
|
: p.loyaltyTier === 'Silver'
|
|
? 'bg-slate-100 text-slate-700'
|
|
: 'bg-orange-100 text-orange-800'
|
|
}`}
|
|
>
|
|
{p.loyaltyTier}
|
|
</span>
|
|
</td>
|
|
<td className="py-3 px-4 text-gray-600">{p.specialAssistance}</td>
|
|
<td className="py-3 px-4 text-center" onClick={(e) => e.stopPropagation()}>
|
|
<CustomCheckBox
|
|
checked={perksClaimedMap[p.id] || false}
|
|
onChange={(e) => handleTogglePerksClaimed(p.id, e.target.checked)}
|
|
/>
|
|
</td>
|
|
</tr>
|
|
);
|
|
})}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* FLIGHT CONTEXT DETAILS */}
|
|
<div className={SECTION_CONTAINER_CLASS}>
|
|
<div className={SECTION_TITLE_CLASS}>
|
|
<Airplane size={16} />
|
|
<span>Flight Context & Route</span>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<CustomDropdown
|
|
label="Origin (IATA)"
|
|
placeholder="Selected Option"
|
|
value={formData.origin}
|
|
onChange={(val) => handleInputChange("origin", val as string)}
|
|
options={[
|
|
{ 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" },
|
|
]}
|
|
/>
|
|
<CustomDropdown
|
|
label="Destination (IATA)"
|
|
placeholder="Selected Option"
|
|
value={formData.destination}
|
|
onChange={(val) => handleInputChange("destination", val as string)}
|
|
options={[
|
|
{ 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" },
|
|
]}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* DISRUPTION & JURISDICTION */}
|
|
<div className={SECTION_CONTAINER_CLASS}>
|
|
<div className={SECTION_TITLE_CLASS}>
|
|
<WarningCircle size={16} />
|
|
<span>Disruption & Jurisdiction</span>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<CustomDropdown
|
|
label="Category"
|
|
placeholder="Selected Option"
|
|
value={formData.category}
|
|
onChange={(val) => handleInputChange("category", val as string)}
|
|
options={DEFAULT_CATEGORY_OPTIONS}
|
|
/>
|
|
<CustomDropdown
|
|
label="Scenario"
|
|
placeholder="Selected Option"
|
|
value={formData.scenario}
|
|
onChange={(val) => handleInputChange("scenario", val as string)}
|
|
options={scenarioOptions}
|
|
/>
|
|
<CustomDropdown
|
|
label="Jurisdiction"
|
|
placeholder="Selected Option"
|
|
value={formData.jurisdiction}
|
|
onChange={(val) => handleInputChange("jurisdiction", val as string)}
|
|
options={jurisdictionOptions}
|
|
/>
|
|
<CustomInput
|
|
type="number"
|
|
label="Delay Duration (Mins)"
|
|
placeholder="0"
|
|
value={formData.delayDuration}
|
|
onChange={(e) => handleInputChange("delayDuration", e.target.value)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</CustomModal>
|
|
);
|
|
}
|