716 lines
22 KiB
TypeScript
716 lines
22 KiB
TypeScript
import { useState, useEffect, useCallback, useMemo } from "react";
|
|
import { useNavigate, useLocation } from "react-router-dom";
|
|
import {
|
|
EyeIcon,
|
|
PencilSimpleIcon,
|
|
CheckCircleIcon,
|
|
XCircleIcon,
|
|
ClockIcon,
|
|
FunnelSimpleIcon,
|
|
SquaresFourIcon,
|
|
PlusIcon,
|
|
CaretDownIcon,
|
|
CaretUpIcon,
|
|
MagnifyingGlassIcon,
|
|
} from "@phosphor-icons/react";
|
|
import {
|
|
CustomTable,
|
|
CustomInput,
|
|
CustomButton,
|
|
CustomStatus,
|
|
CustomCheckBox,
|
|
Skeleton,
|
|
CustomActionMenu,
|
|
CustomActionItem,
|
|
CustomAlertBanner,
|
|
CustomSuccessModal,
|
|
} from "../../../components/custom";
|
|
import Can from "../../../components/common/Can";
|
|
import type { Column } from "../../../components/custom/CustomTable";
|
|
import type {
|
|
RecoveryIncident,
|
|
MetricCardData,
|
|
} from "../RecoveryIncidentsTypes";
|
|
import AddRecoveryIncidents from "./AddRecoveryIncidents";
|
|
import { MetricCard } from "./MetricCard";
|
|
import {
|
|
getRecoveryIncidents,
|
|
getRecoveryMetrics,
|
|
} from "../RecoveryIncidentsApi";
|
|
import { formatDate } from "../../../utils/formatDate";
|
|
|
|
const PAGE_SIZE = 10;
|
|
|
|
function HeaderLabel({
|
|
text,
|
|
rightIcon,
|
|
}: {
|
|
text: string;
|
|
rightIcon?: React.ReactNode;
|
|
}) {
|
|
return (
|
|
<div className="flex items-center gap-1">
|
|
<span className="text-[13px] font-semibold text-[#6C766D] tracking-[0px]">
|
|
{text}
|
|
</span>
|
|
{rightIcon && <span className="text-[#6C766D]">{rightIcon}</span>}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function PrimaryText({ text }: { text: string }) {
|
|
return (
|
|
<div className="text-[13px] font-semibold text-[#0F172B] leading-[18px] tracking-[0px]">
|
|
{text}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function SecondaryText({ text }: { text: string }) {
|
|
return (
|
|
<div className="text-[12px] font-medium text-[#6C766D] leading-[16px] tracking-[0px] mt-0.5">
|
|
{text}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function BadgeLabel({ text }: { text: string }) {
|
|
return (
|
|
<span className="px-3 py-1 bg-gray-100 text-gray-500 rounded-full text-xs font-semibold">
|
|
{text}
|
|
</span>
|
|
);
|
|
}
|
|
|
|
function getStatusVariant(
|
|
status?: string,
|
|
): "success" | "error" | "warning" | "info" | "neutral" | "brand" {
|
|
if (!status) return "neutral";
|
|
const s = status.toLowerCase();
|
|
if (s.includes("appr") || s.includes("active") || s.includes("success"))
|
|
return "success";
|
|
if (s.includes("reject") || s.includes("denied") || s.includes("error"))
|
|
return "error";
|
|
if (s.includes("pend") || s.includes("review") || s.includes("warn"))
|
|
return "warning";
|
|
if (s.includes("new") || s.includes("info")) return "info";
|
|
return "neutral";
|
|
}
|
|
|
|
// ─── Component ───────────────────────────────────────────────────────────────
|
|
|
|
export default function RecoveryIncidentsList() {
|
|
const navigate = useNavigate();
|
|
const location = useLocation();
|
|
const [incidents, setIncidents] = useState<RecoveryIncident[]>([]);
|
|
const [metrics, setMetrics] = useState<MetricCardData[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [successMsg, setSuccessMsg] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
if (location.state?.successMsg) {
|
|
setSuccessMsg(location.state.successMsg);
|
|
window.history.replaceState({}, document.title);
|
|
}
|
|
}, [location]);
|
|
|
|
// Pagination
|
|
const [currentPage, setCurrentPage] = useState(1);
|
|
|
|
// Filters
|
|
const [search, setSearch] = useState("");
|
|
const [isGrouped, setIsGrouped] = useState(() => {
|
|
return sessionStorage.getItem("recovery_isGrouped") === "true";
|
|
});
|
|
const [collapsedGroups, setCollapsedGroups] = useState<Set<string>>(() => {
|
|
const saved = sessionStorage.getItem("recovery_collapsedGroups");
|
|
return saved ? new Set(JSON.parse(saved)) : new Set();
|
|
});
|
|
|
|
useEffect(() => {
|
|
sessionStorage.setItem("recovery_isGrouped", String(isGrouped));
|
|
}, [isGrouped]);
|
|
|
|
useEffect(() => {
|
|
sessionStorage.setItem(
|
|
"recovery_collapsedGroups",
|
|
JSON.stringify(Array.from(collapsedGroups)),
|
|
);
|
|
}, [collapsedGroups]);
|
|
|
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
const [editingIncident, setEditingIncident] =
|
|
useState<RecoveryIncident | null>(null);
|
|
|
|
// Selection
|
|
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
|
|
|
// Success Modal
|
|
const [successModalData, setSuccessModalData] = useState<{
|
|
isOpen: boolean;
|
|
count: number;
|
|
flightNumber: string;
|
|
passengerName: string;
|
|
isEdit?: boolean;
|
|
}>({ isOpen: false, count: 0, flightNumber: "", passengerName: "", isEdit: false });
|
|
|
|
// ─── Fetch data ─────────────────────────────────────────────
|
|
|
|
const fetchIncidents = useCallback(async () => {
|
|
setLoading(true);
|
|
try {
|
|
const data = await getRecoveryIncidents();
|
|
setIncidents(data);
|
|
getRecoveryMetrics()
|
|
.then((m) => setMetrics(m))
|
|
.catch((err) =>
|
|
console.error("Failed to fetch recovery metrics:", err),
|
|
);
|
|
} catch (error) {
|
|
console.error("Failed to fetch recovery incidents", error);
|
|
setIncidents([]);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
fetchIncidents();
|
|
}, [fetchIncidents]);
|
|
|
|
useEffect(() => {
|
|
if (incidents.length > 0) {
|
|
setCollapsedGroups((prev) => {
|
|
const hasSaved = sessionStorage.getItem("recovery_hasSavedGroups");
|
|
if (hasSaved) return prev;
|
|
|
|
sessionStorage.setItem("recovery_hasSavedGroups", "true");
|
|
return new Set(incidents.map((i) => i.flightNumber || "Other"));
|
|
});
|
|
}
|
|
}, [incidents]);
|
|
|
|
// ─── Handlers ──────────────────────────────────────────────────────────────
|
|
|
|
const handlePageChange = (page: number) => {
|
|
setCurrentPage(page);
|
|
};
|
|
|
|
const handleSearchChange = (val: string) => {
|
|
setSearch(val);
|
|
setCurrentPage(1);
|
|
};
|
|
|
|
const toggleSelectAll = () => {
|
|
if (selectedIds.size === incidents.length && incidents.length > 0) {
|
|
setSelectedIds(new Set());
|
|
} else {
|
|
setSelectedIds(new Set(incidents.map((i) => i.id)));
|
|
}
|
|
};
|
|
|
|
const toggleSelectOne = (id: string) => {
|
|
const newSelected = new Set(selectedIds);
|
|
if (newSelected.has(id)) {
|
|
newSelected.delete(id);
|
|
} else {
|
|
newSelected.add(id);
|
|
}
|
|
setSelectedIds(newSelected);
|
|
};
|
|
|
|
const toggleGroup = (groupKey: string, e?: React.MouseEvent) => {
|
|
if (e) e.stopPropagation();
|
|
setCollapsedGroups((prev) => {
|
|
const next = new Set(prev);
|
|
if (next.has(groupKey)) {
|
|
next.delete(groupKey);
|
|
} else {
|
|
next.add(groupKey);
|
|
}
|
|
return next;
|
|
});
|
|
};
|
|
|
|
const handleStatusChange = async (
|
|
incident: RecoveryIncident,
|
|
text: string,
|
|
) => {
|
|
try {
|
|
setError(null);
|
|
setSuccessMsg(null);
|
|
const { updateIncidentStatus } = await import("../RecoveryIncidentsApi");
|
|
await updateIncidentStatus(incident.id, text);
|
|
fetchIncidents();
|
|
setSuccessMsg(`Incident status updated to ${text} successfully.`);
|
|
} catch (error) {
|
|
console.error("Failed to update status", error);
|
|
setError("Failed to update status. Please try again.");
|
|
}
|
|
};
|
|
|
|
const filteredIncidents = useMemo(() => {
|
|
return incidents.filter((p) => {
|
|
const q = search.toLowerCase();
|
|
return (
|
|
(p.recoveryCode || "").toLowerCase().includes(q) ||
|
|
(p.flightNumber || "").toLowerCase().includes(q) ||
|
|
(p.passengerName || "").toLowerCase().includes(q) ||
|
|
(p.pnr || "").toLowerCase().includes(q)
|
|
);
|
|
});
|
|
}, [incidents, search]);
|
|
|
|
const displayData = useMemo(() => {
|
|
if (!isGrouped) {
|
|
return filteredIncidents;
|
|
}
|
|
|
|
// Group items by flight number
|
|
const groupMap = new Map<string, RecoveryIncident[]>();
|
|
filteredIncidents.forEach((item) => {
|
|
const key = item.flightNumber || "Other";
|
|
if (!groupMap.has(key)) {
|
|
groupMap.set(key, []);
|
|
}
|
|
groupMap.get(key)!.push(item);
|
|
});
|
|
|
|
const result: (RecoveryIncident & { groupKey?: string })[] = [];
|
|
|
|
groupMap.forEach((groupItems, flightNo) => {
|
|
const firstItem = groupItems[0];
|
|
|
|
// Calculate status summary for group header
|
|
const pendingCount = groupItems.filter((i) => {
|
|
const s = (i.status || "").toLowerCase();
|
|
return (
|
|
s.includes("pending") || s.includes("review") || s.includes("new")
|
|
);
|
|
}).length;
|
|
const approvedCount = groupItems.filter((i) => {
|
|
const s = (i.status || "").toLowerCase();
|
|
return s.includes("approved") || s.includes("active");
|
|
}).length;
|
|
|
|
const headerStatuses = [
|
|
{ text: `${pendingCount} Pending`, variant: "warning" as const },
|
|
{ text: `${approvedCount} App`, variant: "success" as const },
|
|
];
|
|
|
|
// Calculate sum of values
|
|
const sumVal = groupItems.reduce((acc, curr) => {
|
|
const num = parseFloat((curr.value || "").replace(/[^0-9.]/g, "")) || 0;
|
|
return acc + num;
|
|
}, 0);
|
|
|
|
const groupKey = flightNo;
|
|
|
|
// Group Header Row
|
|
result.push({
|
|
id: `header-${groupKey}`,
|
|
recoveryCode: firstItem.recoveryCode,
|
|
date: firstItem.date,
|
|
flightNumber: flightNo,
|
|
flightRoute: firstItem.flightRoute,
|
|
category: firstItem.category,
|
|
statuses: headerStatuses,
|
|
value: `$${sumVal}`,
|
|
isGroupHeader: true,
|
|
groupKey: groupKey,
|
|
});
|
|
|
|
// Child Rows (if group is expanded)
|
|
if (!collapsedGroups.has(groupKey)) {
|
|
groupItems.forEach((child) => {
|
|
result.push({
|
|
...child,
|
|
groupKey: groupKey,
|
|
} as any);
|
|
});
|
|
}
|
|
});
|
|
|
|
return result.map((r, i) => ({ ...r, globalIndex: i }));
|
|
}, [filteredIncidents, isGrouped, collapsedGroups]);
|
|
|
|
const totalItems = displayData.length;
|
|
const totalPages = Math.ceil(totalItems / PAGE_SIZE) || 1;
|
|
const startIndex = totalItems > 0 ? (currentPage - 1) * PAGE_SIZE + 1 : 0;
|
|
const endIndex = Math.min(currentPage * PAGE_SIZE, totalItems);
|
|
|
|
const paginatedDisplayData = useMemo(() => {
|
|
const start = (currentPage - 1) * PAGE_SIZE;
|
|
return displayData.slice(start, start + PAGE_SIZE);
|
|
}, [displayData, currentPage]);
|
|
|
|
// ─── Table columns ─────────────────────────────────────────────────────────
|
|
|
|
const columns: Column<RecoveryIncident>[] = [
|
|
{
|
|
header: (
|
|
<CustomCheckBox
|
|
checked={
|
|
incidents.length > 0 && selectedIds.size === incidents.length
|
|
}
|
|
onChange={toggleSelectAll}
|
|
/>
|
|
),
|
|
className: "w-[40px] pr-0",
|
|
accessor: (row) =>
|
|
row.isGroupHeader ? null : (
|
|
<CustomCheckBox
|
|
checked={selectedIds.has(row.id)}
|
|
onChange={() => toggleSelectOne(row.id)}
|
|
onClick={(e) => e.stopPropagation()}
|
|
/>
|
|
),
|
|
},
|
|
{
|
|
header: <HeaderLabel text="Recovery ID" />,
|
|
accessor: (row) => (
|
|
<div className={row.isGroupHeader ? "-ml-[40px]" : ""}>
|
|
<PrimaryText text={row.recoveryCode} />
|
|
<SecondaryText text={formatDate(row.date)} />
|
|
</div>
|
|
),
|
|
},
|
|
{
|
|
header: (
|
|
<HeaderLabel
|
|
text="Passenger / PNR"
|
|
rightIcon={<FunnelSimpleIcon size={14} weight="bold" />}
|
|
/>
|
|
),
|
|
accessor: (row) =>
|
|
row.passengerName ? (
|
|
<div>
|
|
<PrimaryText text={row.passengerName} />
|
|
<SecondaryText text={row.pnr || ""} />
|
|
</div>
|
|
) : null,
|
|
},
|
|
{
|
|
header: (
|
|
<HeaderLabel
|
|
text="Flight"
|
|
rightIcon={<FunnelSimpleIcon size={14} weight="bold" />}
|
|
/>
|
|
),
|
|
accessor: (row) => (
|
|
<div>
|
|
<PrimaryText text={row.flightNumber} />
|
|
<SecondaryText text={row.flightRoute} />
|
|
</div>
|
|
),
|
|
},
|
|
{
|
|
header: <HeaderLabel text="Category" />,
|
|
accessor: (row) =>
|
|
row.isGroupHeader ? null : row.category ? (
|
|
<BadgeLabel text={row.category} />
|
|
) : null,
|
|
},
|
|
{
|
|
header: <HeaderLabel text="Status" />,
|
|
accessor: (row) => {
|
|
if (row.isGroupHeader) {
|
|
return (
|
|
<div className="flex items-center gap-2">
|
|
{(row.statuses || []).map((status, idx) => (
|
|
<div
|
|
key={idx}
|
|
className={`px-2.5 py-1 rounded-full text-[11px] font-semibold leading-[16.5px] ${status.variant === "warning" ? "bg-[#FFFBE6] text-[#D48806]" : "bg-[#E4FAE7] text-[#1B9869]"}`}
|
|
>
|
|
{status.text}
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
const statusText = row.status || "Pending";
|
|
return (
|
|
<CustomStatus
|
|
status={statusText}
|
|
variant={getStatusVariant(statusText)}
|
|
/>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
header: (
|
|
<HeaderLabel
|
|
text="Value"
|
|
rightIcon={<FunnelSimpleIcon size={14} weight="bold" />}
|
|
/>
|
|
),
|
|
accessor: (row) => <PrimaryText text={row.value} />,
|
|
},
|
|
{
|
|
header: <HeaderLabel text="Perks Claimed" />,
|
|
accessor: (row) =>
|
|
row.isGroupHeader ? null : (
|
|
<BadgeLabel text={row.isPerksClaimed ? "Yes" : "No"} />
|
|
),
|
|
},
|
|
{
|
|
header: <HeaderLabel text="Action" />,
|
|
accessor: (row) => {
|
|
const key =
|
|
(row as any).groupKey || row.flightNumber || row.recoveryCode;
|
|
return (
|
|
<div className="flex items-center pl-2">
|
|
{row.isGroupHeader ? (
|
|
<div
|
|
className="cursor-pointer text-gray-500 hover:text-gray-900 transition-colors p-1"
|
|
onClick={(e) => toggleGroup(key, e)}
|
|
>
|
|
{collapsedGroups.has(key) ? (
|
|
<CaretDownIcon size={20} />
|
|
) : (
|
|
<CaretUpIcon size={20} />
|
|
)}
|
|
</div>
|
|
) : (
|
|
<CustomActionMenu>
|
|
<CustomActionItem
|
|
onClick={() => navigate(`/recovery/${row.id}`)}
|
|
icon={<EyeIcon size={16} className="text-blue-500" />}
|
|
>
|
|
View Details
|
|
</CustomActionItem>
|
|
<Can permission="recovery:edit">
|
|
<CustomActionItem
|
|
onClick={() => {
|
|
setEditingIncident(row);
|
|
setIsModalOpen(true);
|
|
}}
|
|
icon={
|
|
<PencilSimpleIcon size={16} className="text-yellow-500" />
|
|
}
|
|
>
|
|
Edit Incident
|
|
</CustomActionItem>
|
|
</Can>
|
|
<Can permission="recovery:override">
|
|
<CustomActionItem
|
|
icon={
|
|
<CheckCircleIcon size={16} className="text-green-500" />
|
|
}
|
|
onClick={() => handleStatusChange(row, "Approved")}
|
|
>
|
|
Approve
|
|
</CustomActionItem>
|
|
</Can>
|
|
<Can permission="recovery:override">
|
|
<CustomActionItem
|
|
variant="danger"
|
|
icon={<XCircleIcon size={16} className="text-red-500" />}
|
|
onClick={() => handleStatusChange(row, "Rejected")}
|
|
>
|
|
Reject
|
|
</CustomActionItem>
|
|
</Can>
|
|
<Can permission="recovery:edit">
|
|
<CustomActionItem
|
|
icon={<ClockIcon size={16} className="text-yellow-600" />}
|
|
onClick={() => handleStatusChange(row, "Under Review")}
|
|
>
|
|
Mark for Review
|
|
</CustomActionItem>
|
|
</Can>
|
|
</CustomActionMenu>
|
|
)}
|
|
</div>
|
|
);
|
|
},
|
|
},
|
|
];
|
|
|
|
const isAnyGroupExpanded = useMemo(() => {
|
|
if (!isGrouped) return true;
|
|
const allGroupKeys = Array.from(
|
|
new Set(incidents.map((i) => i.flightNumber || "Other")),
|
|
);
|
|
return allGroupKeys.some((key) => !collapsedGroups.has(key));
|
|
}, [isGrouped, incidents, collapsedGroups]);
|
|
|
|
const activeColumns = useMemo(() => {
|
|
if (!isGrouped || isAnyGroupExpanded) {
|
|
return columns;
|
|
}
|
|
return columns.filter((col) => {
|
|
const headerText =
|
|
typeof col.header === "object" &&
|
|
col.header !== null &&
|
|
"props" in col.header
|
|
? (col.header as any).props.text
|
|
: "";
|
|
return (
|
|
headerText !== "Passenger / PNR" &&
|
|
headerText !== "Category" &&
|
|
headerText !== "Perks Claimed"
|
|
);
|
|
});
|
|
}, [columns, isGrouped, isAnyGroupExpanded]);
|
|
|
|
// ─── Loading skeleton ──────────────────────────────────────────────────────
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="w-full flex flex-col bg-white rounded-[20px] shadow-sm border border-gray-100 overflow-hidden">
|
|
<div className="flex items-center justify-between p-4 border-b border-gray-100">
|
|
<Skeleton width={320} height={36} />
|
|
<div className="flex items-center gap-3">
|
|
<Skeleton width={148} height={36} />
|
|
</div>
|
|
</div>
|
|
<div className="p-6 flex flex-col gap-4">
|
|
{Array.from({ length: 6 }).map((_, i) => (
|
|
<Skeleton key={i} height={52} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// ─── Render ────────────────────────────────────────────────────────────────
|
|
|
|
return (
|
|
<div className="w-full flex flex-col gap-6">
|
|
{error && (
|
|
<CustomAlertBanner
|
|
message={error}
|
|
type="error"
|
|
onClose={() => setError(null)}
|
|
/>
|
|
)}
|
|
{successMsg && (
|
|
<CustomAlertBanner
|
|
message={successMsg}
|
|
type="success"
|
|
onClose={() => setSuccessMsg(null)}
|
|
/>
|
|
)}
|
|
{/* Metrics Row */}
|
|
<div className="flex gap-4 w-full">
|
|
{metrics.map((metric) => (
|
|
<MetricCard key={metric.id || metric.title} {...metric} />
|
|
))}
|
|
</div>
|
|
|
|
{/* Table Section */}
|
|
<CustomTable<RecoveryIncident>
|
|
columns={activeColumns}
|
|
data={paginatedDisplayData}
|
|
leftHeaderActions={
|
|
<div className="w-[320px]">
|
|
<CustomInput
|
|
placeholder="Search framework registry..."
|
|
value={search}
|
|
onChange={(e) => handleSearchChange(e.target.value)}
|
|
leftIcon={<MagnifyingGlassIcon size={16} />}
|
|
className="!bg-[#F3F6F5] !rounded-[10px] !h-[40px] !border !border-[#E5E7EB]"
|
|
containerClassName="!gap-0"
|
|
/>
|
|
</div>
|
|
}
|
|
rightHeaderActions={
|
|
<>
|
|
<CustomButton
|
|
variant="outlined"
|
|
size="md"
|
|
leftIcon={<SquaresFourIcon size={16} />}
|
|
className="!rounded-[10px] !gap-[8px] !h-[40px] !border-primary !text-primary hover:!bg-primary/5"
|
|
onClick={() => setIsGrouped(!isGrouped)}
|
|
>
|
|
{isGrouped ? "Ungroup" : "Group by Flight"}
|
|
</CustomButton>
|
|
<Can permission="recovery:create">
|
|
<CustomButton
|
|
variant="primary"
|
|
size="md"
|
|
leftIcon={<PlusIcon size={16} />}
|
|
className="!rounded-[10px] !gap-[8px] !h-[40px] !bg-[#1B9869] hover:!bg-[#14704E]"
|
|
onClick={() => {
|
|
setEditingIncident(null);
|
|
setIsModalOpen(true);
|
|
}}
|
|
>
|
|
New Incident
|
|
</CustomButton>
|
|
</Can>
|
|
</>
|
|
}
|
|
currentPage={currentPage}
|
|
totalPages={totalPages}
|
|
totalItems={totalItems}
|
|
startIndex={startIndex}
|
|
endIndex={endIndex}
|
|
onPageChange={handlePageChange}
|
|
itemName="Recovery Incidents"
|
|
onRowClick={(row) => {
|
|
if (row.isGroupHeader) {
|
|
const key =
|
|
(row as any).groupKey || row.flightNumber || row.recoveryCode;
|
|
toggleGroup(key);
|
|
} else {
|
|
navigate(`/recovery/${row.id}`);
|
|
}
|
|
}}
|
|
rowClassName={(row: any) => {
|
|
if (row.isGroupHeader) {
|
|
return "bg-white border-b border-gray-100 cursor-pointer hover:bg-gray-50/60";
|
|
}
|
|
return "bg-[#F9FAFB] border-transparent cursor-pointer hover:bg-[#F3F4F6]";
|
|
}}
|
|
/>
|
|
|
|
{/* Modal */}
|
|
<AddRecoveryIncidents
|
|
isOpen={isModalOpen}
|
|
incident={editingIncident}
|
|
onClose={() => {
|
|
setIsModalOpen(false);
|
|
setEditingIncident(null);
|
|
fetchIncidents(); // Refresh list
|
|
}}
|
|
onSuccess={(createdCount, flightNumber, passengerName, isEdit) => {
|
|
setSuccessModalData({
|
|
isOpen: true,
|
|
count: createdCount,
|
|
flightNumber,
|
|
passengerName,
|
|
isEdit,
|
|
});
|
|
}}
|
|
/>
|
|
|
|
{/* Success Modal */}
|
|
<CustomSuccessModal
|
|
isOpen={successModalData.isOpen}
|
|
onClose={() =>
|
|
setSuccessModalData((prev) => ({ ...prev, isOpen: false }))
|
|
}
|
|
title={
|
|
successModalData.isEdit
|
|
? (successModalData.count > 1 ? "Recovery Incidents Updated." : "Recovery Incident Updated.")
|
|
: (successModalData.count > 1 ? "Recovery Incidents Created." : "Recovery Incident Created.")
|
|
}
|
|
label="FLIGHT & PASSENGER DETAILS"
|
|
cohortName={`Flight ${successModalData.flightNumber}`}
|
|
cohortDescription={
|
|
successModalData.isEdit
|
|
? (successModalData.count > 1
|
|
? `Successfully updated incidents for ${successModalData.count} passengers, including ${successModalData.passengerName}.`
|
|
: `Successfully updated incident for ${successModalData.passengerName}.`)
|
|
: (successModalData.count > 1
|
|
? `Successfully logged incidents for ${successModalData.count} passengers, including ${successModalData.passengerName}.`
|
|
: `Successfully logged incident for ${successModalData.passengerName}.`)
|
|
}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|