Merge pull request 'azeem' (#52) from azeem into development

Reviewed-on: https://gitea.maskantech.in/gitea_admin/aeroresolve_frontend/pulls/52
This commit is contained in:
Syed Waseem khadri Rafai
2026-08-24 06:24:44 +00:00
7 changed files with 199 additions and 117 deletions
@@ -280,11 +280,12 @@ export default function AuditLogsList() {
// ─── Render ───────────────────────────────────────────────────────────────── // ─── Render ─────────────────────────────────────────────────────────────────
const displayedLogs = search const displayedLogs = search
? logs.filter((l) => ? logs.filter(
l.entityLabel?.toLowerCase().includes(search.toLowerCase()) || (l) =>
l.entityId?.toLowerCase().includes(search.toLowerCase()) || l.entityLabel?.toLowerCase().includes(search.toLowerCase()) ||
l.module?.toLowerCase().includes(search.toLowerCase()) || l.entityId?.toLowerCase().includes(search.toLowerCase()) ||
l.performedBy?.toLowerCase().includes(search.toLowerCase()) l.module?.toLowerCase().includes(search.toLowerCase()) ||
l.performedBy?.toLowerCase().includes(search.toLowerCase()),
) )
: logs; : logs;
+55 -35
View File
@@ -1,6 +1,13 @@
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from "react";
import AddCohart from './AddCohart'; import AddCohart from "./AddCohart";
import { PlusIcon, TrashIcon, MagnifyingGlassIcon, CheckIcon, XIcon, PencilSimpleIcon } from '@phosphor-icons/react'; import {
PlusIcon,
TrashIcon,
MagnifyingGlassIcon,
CheckIcon,
XIcon,
PencilSimpleIcon,
} from "@phosphor-icons/react";
import { import {
CustomTable, CustomTable,
CustomInput, CustomInput,
@@ -12,38 +19,40 @@ import {
CustomConfirmationModal, CustomConfirmationModal,
CustomAlertBanner, CustomAlertBanner,
Skeleton, Skeleton,
} from '../../../components/custom'; } from "../../../components/custom";
import type { Column } from '../../../components/custom/CustomTable'; import type { Column } from "../../../components/custom/CustomTable";
import { import {
listCoharts, listCoharts,
deleteCohart, deleteCohart,
updateCohartStatus, updateCohartStatus,
} from '../CohartManageApi'; } from "../CohartManageApi";
import type { CohartResponse } from '../CohartManageTypes'; import type { CohartResponse } from "../CohartManageTypes";
// ─── Constants ─────────────────────────────────────────────────────────────── // ─── Constants ───────────────────────────────────────────────────────────────
const PAGE_SIZE = 10; const PAGE_SIZE = 10;
const STATUS_OPTIONS = [ const STATUS_OPTIONS = [
{ label: 'All Statuses', value: '' }, { label: "All Statuses", value: "" },
{ label: 'Active', value: 'Active' }, { label: "Active", value: "Active" },
{ label: 'Inactive', value: 'Inactive' }, { label: "Inactive", value: "Inactive" },
{ label: 'Draft', value: 'Draft' }, { label: "Draft", value: "Draft" },
]; ];
// ─── Helpers ───────────────────────────────────────────────────────────────── // ─── Helpers ─────────────────────────────────────────────────────────────────
function HeaderLabel({ text }: { text: string }) { function HeaderLabel({ text }: { text: string }) {
return ( return (
<span className="text-[14px] font-semibold text-[#6C766D] tracking-[0px]">{text}</span> <span className="text-[14px] font-semibold text-[#6C766D] tracking-[0px]">
{text}
</span>
); );
} }
function CellText({ text }: { text: string | null | undefined }) { function CellText({ text }: { text: string | null | undefined }) {
return ( return (
<span style={{ fontSize: '14px', color: '#676767', fontWeight: 500 }}> <span style={{ fontSize: "14px", color: "#676767", fontWeight: 500 }}>
{text || '—'} {text || "—"}
</span> </span>
); );
} }
@@ -62,12 +71,13 @@ export default function CohortList() {
const [totalPages, setTotalPages] = useState(1); const [totalPages, setTotalPages] = useState(1);
// Client-side filters (applied on top of server data) // Client-side filters (applied on top of server data)
const [search, setSearch] = useState(''); const [search, setSearch] = useState("");
const [statusFilter, setStatusFilter] = useState(''); const [statusFilter, setStatusFilter] = useState("");
// Modal state // Modal state
const [deleteTarget, setDeleteTarget] = useState<CohartResponse | null>(null); const [deleteTarget, setDeleteTarget] = useState<CohartResponse | null>(null);
const [deactivateTarget, setDeactivateTarget] = useState<CohartResponse | null>(null); const [deactivateTarget, setDeactivateTarget] =
useState<CohartResponse | null>(null);
const [editTarget, setEditTarget] = useState<CohartResponse | null>(null); const [editTarget, setEditTarget] = useState<CohartResponse | null>(null);
const [deleteLoading, setDeleteLoading] = useState(false); const [deleteLoading, setDeleteLoading] = useState(false);
const [statusLoading, setStatusLoading] = useState(false); const [statusLoading, setStatusLoading] = useState(false);
@@ -84,7 +94,7 @@ export default function CohortList() {
setTotalItems(res.total); setTotalItems(res.total);
setTotalPages(res.totalPages); setTotalPages(res.totalPages);
} catch { } catch {
setError('Failed to load cohorts. Please try again.'); setError("Failed to load cohorts. Please try again.");
setCohorts([]); setCohorts([]);
} finally { } finally {
setLoading(false); setLoading(false);
@@ -122,7 +132,7 @@ export default function CohortList() {
setDeleteTarget(null); setDeleteTarget(null);
fetchCohorts(currentPage); fetchCohorts(currentPage);
} catch { } catch {
setError('Failed to delete cohort. Please try again.'); setError("Failed to delete cohort. Please try again.");
} finally { } finally {
setDeleteLoading(false); setDeleteLoading(false);
} }
@@ -134,13 +144,14 @@ export default function CohortList() {
setError(null); setError(null);
setSuccessMsg(null); setSuccessMsg(null);
try { try {
const newStatus = deactivateTarget.status === 'Active' ? 'Inactive' : 'Active'; const newStatus =
deactivateTarget.status === "Active" ? "Inactive" : "Active";
await updateCohartStatus(deactivateTarget.id, { status: newStatus }); await updateCohartStatus(deactivateTarget.id, { status: newStatus });
setSuccessMsg(`Cohort "${deactivateTarget.name}" is now ${newStatus}.`); setSuccessMsg(`Cohort "${deactivateTarget.name}" is now ${newStatus}.`);
setDeactivateTarget(null); setDeactivateTarget(null);
fetchCohorts(currentPage); fetchCohorts(currentPage);
} catch { } catch {
setError('Failed to update cohort status. Please try again.'); setError("Failed to update cohort status. Please try again.");
} finally { } finally {
setStatusLoading(false); setStatusLoading(false);
} }
@@ -149,8 +160,8 @@ export default function CohortList() {
// ─── Client-side filter on top of server data ────────────────────────────── // ─── Client-side filter on top of server data ──────────────────────────────
const displayedCohorts = cohorts const displayedCohorts = cohorts
.filter(c => c.name.toLowerCase().includes(search.toLowerCase())) .filter((c) => c.name.toLowerCase().includes(search.toLowerCase()))
.filter(c => (statusFilter ? c.status === statusFilter : true)); .filter((c) => (statusFilter ? c.status === statusFilter : true));
const startIndex = totalItems > 0 ? (currentPage - 1) * PAGE_SIZE + 1 : 0; const startIndex = totalItems > 0 ? (currentPage - 1) * PAGE_SIZE + 1 : 0;
const endIndex = Math.min(currentPage * PAGE_SIZE, totalItems); const endIndex = Math.min(currentPage * PAGE_SIZE, totalItems);
@@ -160,7 +171,7 @@ export default function CohortList() {
const columns: Column<CohartResponse>[] = [ const columns: Column<CohartResponse>[] = [
{ {
header: <HeaderLabel text="Cohort Name" />, header: <HeaderLabel text="Cohort Name" />,
accessor: row => ( accessor: (row) => (
<span className="text-[13px] font-semibold text-[#0F172B] leading-[18px] tracking-[0px]"> <span className="text-[13px] font-semibold text-[#0F172B] leading-[18px] tracking-[0px]">
{row.name} {row.name}
</span> </span>
@@ -168,18 +179,18 @@ export default function CohortList() {
}, },
{ {
header: <HeaderLabel text="Description" />, header: <HeaderLabel text="Description" />,
accessor: row => <CellText text={row.description} />, accessor: (row) => <CellText text={row.description} />,
}, },
{ {
header: <HeaderLabel text="Status" />, header: <HeaderLabel text="Status" />,
accessor: row => <CustomStatus status={row.status} />, accessor: (row) => <CustomStatus status={row.status} />,
}, },
{ {
header: <HeaderLabel text="Action" />, header: <HeaderLabel text="Action" />,
className: 'text-right', className: "text-right",
accessor: row => ( accessor: (row) => (
<CustomActionMenu> <CustomActionMenu>
{row.status !== 'Active' && ( {row.status !== "Active" && (
<CustomActionItem <CustomActionItem
icon={<CheckIcon size={15} />} icon={<CheckIcon size={15} />}
variant="success" variant="success"
@@ -188,7 +199,7 @@ export default function CohortList() {
Activate Activate
</CustomActionItem> </CustomActionItem>
)} )}
{row.status === 'Active' && ( {row.status === "Active" && (
<CustomActionItem <CustomActionItem
icon={<XIcon size={15} />} icon={<XIcon size={15} />}
onClick={() => setDeactivateTarget(row)} onClick={() => setDeactivateTarget(row)}
@@ -262,7 +273,7 @@ export default function CohortList() {
<CustomInput <CustomInput
placeholder="Search cohorts..." placeholder="Search cohorts..."
value={search} value={search}
onChange={e => handleSearchChange(e.target.value)} onChange={(e) => handleSearchChange(e.target.value)}
leftIcon={<MagnifyingGlassIcon size={16} />} leftIcon={<MagnifyingGlassIcon size={16} />}
className="!bg-[#F3F6F5] !rounded-[10px] !h-[38px] !border !border-[#E5E7EB]" className="!bg-[#F3F6F5] !rounded-[10px] !h-[38px] !border !border-[#E5E7EB]"
containerClassName="!gap-0" containerClassName="!gap-0"
@@ -318,13 +329,19 @@ export default function CohortList() {
isOpen={!!deactivateTarget} isOpen={!!deactivateTarget}
onClose={() => setDeactivateTarget(null)} onClose={() => setDeactivateTarget(null)}
onConfirm={handleToggleStatus} onConfirm={handleToggleStatus}
title={deactivateTarget?.status === 'Active' ? 'Deactivate Cohort' : 'Activate Cohort'} title={
deactivateTarget?.status === "Active"
? "Deactivate Cohort"
: "Activate Cohort"
}
description={ description={
deactivateTarget?.status === 'Active' deactivateTarget?.status === "Active"
? `"${deactivateTarget?.name}" will be deactivated and removed from active targeting.` ? `"${deactivateTarget?.name}" will be deactivated and removed from active targeting.`
: `"${deactivateTarget?.name}" will be reactivated and available for targeting.` : `"${deactivateTarget?.name}" will be reactivated and available for targeting.`
} }
confirmText={deactivateTarget?.status === 'Active' ? 'Deactivate' : 'Activate'} confirmText={
deactivateTarget?.status === "Active" ? "Deactivate" : "Activate"
}
cancelText="Cancel" cancelText="Cancel"
variant="warning" variant="warning"
isLoading={statusLoading} isLoading={statusLoading}
@@ -346,7 +363,10 @@ export default function CohortList() {
<AddCohart <AddCohart
isOpen={!!editTarget} isOpen={!!editTarget}
onClose={() => setEditTarget(null)} onClose={() => setEditTarget(null)}
onSuccess={() => { setEditTarget(null); fetchCohorts(currentPage); }} onSuccess={() => {
setEditTarget(null);
fetchCohorts(currentPage);
}}
editData={editTarget} editData={editTarget}
/> />
)} )}
@@ -7,7 +7,9 @@ interface RecentIncidentsTableProps {
incidents: RecentIncident[]; incidents: RecentIncident[];
} }
export const RecentIncidentsTable: React.FC<RecentIncidentsTableProps> = ({ incidents }) => { export const RecentIncidentsTable: React.FC<RecentIncidentsTableProps> = ({
incidents,
}) => {
const columns: Column<RecentIncident>[] = [ const columns: Column<RecentIncident>[] = [
{ {
header: "Recovery ID", header: "Recovery ID",
@@ -48,9 +50,7 @@ export const RecentIncidentsTable: React.FC<RecentIncidentsTableProps> = ({ inci
{ {
header: "Value", header: "Value",
accessor: (row) => ( accessor: (row) => (
<span className="text-[13px] font-bold text-gray-900"> <span className="text-[13px] font-bold text-gray-900">{row.value}</span>
{row.value}
</span>
), ),
}, },
]; ];
@@ -17,6 +17,7 @@ import {
CustomLoader, CustomLoader,
Skeleton, Skeleton,
} from '../../../components/custom'; } from '../../../components/custom';
import CustomSuccessModal from '../../../components/custom/CustomSuccessModal';
import { import {
getJurisdictionOptions, getJurisdictionOptions,
getCohortOptions, getCohortOptions,
@@ -257,6 +258,8 @@ export default function AddPolicyEngine() {
const [loadingPolicy, setLoadingPolicy] = useState(false); const [loadingPolicy, setLoadingPolicy] = useState(false);
const [isSaving, setIsSaving] = useState(false); const [isSaving, setIsSaving] = useState(false);
const [showSuccessModal, setShowSuccessModal] = useState(false);
const [successTitle, setSuccessTitle] = useState('');
// Dynamic Options State (loaded directly from PolicyEngineApi) // Dynamic Options State (loaded directly from PolicyEngineApi)
const [jurisdictionOptions, setJurisdictionOptions] = useState<OptionItem[]>([]); const [jurisdictionOptions, setJurisdictionOptions] = useState<OptionItem[]>([]);
@@ -776,14 +779,13 @@ export default function AddPolicyEngine() {
await createPolicy(payload); await createPolicy(payload);
} }
let actionWord = isDeploy ? 'created' : 'saved as draft';
if (isEditMode) { if (isEditMode) {
actionWord = isDeploy ? 'updated' : 'saved as draft';
} }
const successMsg = `Policy "${payload.policyName}" ${actionWord} successfully!`; setSuccessTitle(isDeploy
? (isEditMode ? "Policy Updated Successfully." : "Policy Created Successfully.")
navigate('/policy-engine', { state: { successMsg } }); : "Policy Saved as Draft.");
setShowSuccessModal(true);
} catch (err) { } catch (err) {
console.error('Failed to save policy', err); console.error('Failed to save policy', err);
alert('Failed to save policy. Please check input values and try again.'); alert('Failed to save policy. Please check input values and try again.');
@@ -1549,6 +1551,18 @@ export default function AddPolicyEngine() {
</div> </div>
</div> </div>
<CustomSuccessModal
isOpen={showSuccessModal}
onClose={() => {
setShowSuccessModal(false);
navigate('/policy-engine');
}}
title={successTitle}
label="POLICY NAME"
cohortName={policyName}
cohortStatus={status}
cohortDescription={description}
/>
</div> </div>
); );
} }
@@ -91,27 +91,24 @@ export default function PolicyEngineList() {
// ─── Fetch data ───────────────────────────────────────────── // ─── Fetch data ─────────────────────────────────────────────
const fetchPolicies = useCallback( const fetchPolicies = useCallback((page: number) => {
(page: number) => { setLoading(true);
setLoading(true); getPolicies(page, PAGE_SIZE)
getPolicies(page, PAGE_SIZE) .then((res) => {
.then((res) => { setPolicies(res.data || []);
setPolicies(res.data || []); setTotalItems(res.total || 0);
setTotalItems(res.total || 0); setTotalPages(res.totalPages || 1);
setTotalPages(res.totalPages || 1); })
}) .catch((err) => {
.catch((err) => { console.error("Failed to fetch policies:", err);
console.error("Failed to fetch policies:", err); setPolicies([]);
setPolicies([]); setTotalItems(0);
setTotalItems(0); setTotalPages(1);
setTotalPages(1); })
}) .finally(() => {
.finally(() => { setLoading(false);
setLoading(false); });
}); }, []);
},
[],
);
useEffect(() => { useEffect(() => {
fetchPolicies(currentPage); fetchPolicies(currentPage);
@@ -134,7 +131,9 @@ export default function PolicyEngineList() {
setSuccessMsg(null); setSuccessMsg(null);
try { try {
await deletePolicy(deleteTarget.id); await deletePolicy(deleteTarget.id);
setSuccessMsg(`Policy "${deleteTarget.policyName}" deleted successfully.`); setSuccessMsg(
`Policy "${deleteTarget.policyName}" deleted successfully.`,
);
setDeleteTarget(null); setDeleteTarget(null);
fetchPolicies(currentPage); fetchPolicies(currentPage);
} catch (err) { } catch (err) {
@@ -151,7 +150,9 @@ export default function PolicyEngineList() {
const nextStatus = const nextStatus =
deactivateTarget.status === "Active" ? "inactive" : "active"; deactivateTarget.status === "Active" ? "inactive" : "active";
await updatePolicyStatus(deactivateTarget.id, nextStatus); await updatePolicyStatus(deactivateTarget.id, nextStatus);
setSuccessMsg(`Policy "${deactivateTarget.policyName}" is now ${nextStatus === 'active' ? 'Active' : 'Inactive'}.`); setSuccessMsg(
`Policy "${deactivateTarget.policyName}" is now ${nextStatus === "active" ? "Active" : "Inactive"}.`,
);
setDeactivateTarget(null); setDeactivateTarget(null);
fetchPolicies(currentPage); fetchPolicies(currentPage);
} catch (err) { } catch (err) {
@@ -160,10 +161,11 @@ export default function PolicyEngineList() {
} }
}; };
const displayedPolicies = search const displayedPolicies = search
? policies.filter(p => ? policies.filter(
p.policyName?.toLowerCase().includes(search.toLowerCase()) || (p) =>
p.jurisdiction?.toLowerCase().includes(search.toLowerCase()) p.policyName?.toLowerCase().includes(search.toLowerCase()) ||
p.jurisdiction?.toLowerCase().includes(search.toLowerCase()),
) )
: policies; : policies;
@@ -18,7 +18,7 @@ import type { RecoveryIncident } from '../RecoveryIncidentsTypes';
interface AddRecoveryIncidentsProps { interface AddRecoveryIncidentsProps {
isOpen: boolean; isOpen: boolean;
onClose: () => void; onClose: () => void;
onSuccess?: (createdCount: number, flightNumber: string, passengerName: string) => void; onSuccess?: (createdCount: number, flightNumber: string, passengerName: string, isEdit?: boolean) => void;
incident?: RecoveryIncident | null; incident?: RecoveryIncident | null;
} }
@@ -353,7 +353,8 @@ export default function AddRecoveryIncidents({ isOpen, onClose, onSuccess, incid
onSuccess( onSuccess(
selectedPassengers.length, selectedPassengers.length,
formData.flightNumber || "TBD", formData.flightNumber || "TBD",
selectedPassengers[0]?.passengerName || "Unknown" selectedPassengers[0]?.passengerName || "Unknown",
!!incident
); );
} }
} }
@@ -26,10 +26,16 @@ import {
CustomSuccessModal, CustomSuccessModal,
} from "../../../components/custom"; } from "../../../components/custom";
import type { Column } from "../../../components/custom/CustomTable"; import type { Column } from "../../../components/custom/CustomTable";
import type { RecoveryIncident, MetricCardData } from "../RecoveryIncidentsTypes"; import type {
RecoveryIncident,
MetricCardData,
} from "../RecoveryIncidentsTypes";
import AddRecoveryIncidents from "./AddRecoveryIncidents"; import AddRecoveryIncidents from "./AddRecoveryIncidents";
import { MetricCard } from "./MetricCard"; import { MetricCard } from "./MetricCard";
import { getRecoveryIncidents, getRecoveryMetrics } from "../RecoveryIncidentsApi"; import {
getRecoveryIncidents,
getRecoveryMetrics,
} from "../RecoveryIncidentsApi";
import { formatDate } from "../../../utils/formatDate"; import { formatDate } from "../../../utils/formatDate";
const PAGE_SIZE = 10; const PAGE_SIZE = 10;
@@ -75,12 +81,17 @@ function BadgeLabel({ text }: { text: string }) {
); );
} }
function getStatusVariant(status?: string): "success" | "error" | "warning" | "info" | "neutral" | "brand" { function getStatusVariant(
status?: string,
): "success" | "error" | "warning" | "info" | "neutral" | "brand" {
if (!status) return "neutral"; if (!status) return "neutral";
const s = status.toLowerCase(); const s = status.toLowerCase();
if (s.includes("appr") || s.includes("active") || s.includes("success")) return "success"; if (s.includes("appr") || s.includes("active") || s.includes("success"))
if (s.includes("reject") || s.includes("denied") || s.includes("error")) return "error"; return "success";
if (s.includes("pend") || s.includes("review") || s.includes("warn")) return "warning"; 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"; if (s.includes("new") || s.includes("info")) return "info";
return "neutral"; return "neutral";
} }
@@ -113,11 +124,15 @@ export default function RecoveryIncidentsList() {
}, [isGrouped]); }, [isGrouped]);
useEffect(() => { useEffect(() => {
sessionStorage.setItem("recovery_collapsedGroups", JSON.stringify(Array.from(collapsedGroups))); sessionStorage.setItem(
"recovery_collapsedGroups",
JSON.stringify(Array.from(collapsedGroups)),
);
}, [collapsedGroups]); }, [collapsedGroups]);
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
const [editingIncident, setEditingIncident] = useState<RecoveryIncident | null>(null); const [editingIncident, setEditingIncident] =
useState<RecoveryIncident | null>(null);
// Selection // Selection
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set()); const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
@@ -128,7 +143,8 @@ export default function RecoveryIncidentsList() {
count: number; count: number;
flightNumber: string; flightNumber: string;
passengerName: string; passengerName: string;
}>({ isOpen: false, count: 0, flightNumber: '', passengerName: '' }); isEdit?: boolean;
}>({ isOpen: false, count: 0, flightNumber: "", passengerName: "", isEdit: false });
// ─── Fetch data ───────────────────────────────────────────── // ─── Fetch data ─────────────────────────────────────────────
@@ -139,7 +155,9 @@ export default function RecoveryIncidentsList() {
setIncidents(data); setIncidents(data);
getRecoveryMetrics() getRecoveryMetrics()
.then((m) => setMetrics(m)) .then((m) => setMetrics(m))
.catch((err) => console.error("Failed to fetch recovery metrics:", err)); .catch((err) =>
console.error("Failed to fetch recovery metrics:", err),
);
} catch (error) { } catch (error) {
console.error("Failed to fetch recovery incidents", error); console.error("Failed to fetch recovery incidents", error);
setIncidents([]); setIncidents([]);
@@ -157,7 +175,7 @@ export default function RecoveryIncidentsList() {
setCollapsedGroups((prev) => { setCollapsedGroups((prev) => {
const hasSaved = sessionStorage.getItem("recovery_hasSavedGroups"); const hasSaved = sessionStorage.getItem("recovery_hasSavedGroups");
if (hasSaved) return prev; if (hasSaved) return prev;
sessionStorage.setItem("recovery_hasSavedGroups", "true"); sessionStorage.setItem("recovery_hasSavedGroups", "true");
return new Set(incidents.map((i) => i.flightNumber || "Other")); return new Set(incidents.map((i) => i.flightNumber || "Other"));
}); });
@@ -206,11 +224,14 @@ export default function RecoveryIncidentsList() {
}); });
}; };
const handleStatusChange = async (incident: RecoveryIncident, text: string) => { const handleStatusChange = async (
incident: RecoveryIncident,
text: string,
) => {
try { try {
setError(null); setError(null);
setSuccessMsg(null); setSuccessMsg(null);
const { updateIncidentStatus } = await import('../RecoveryIncidentsApi'); const { updateIncidentStatus } = await import("../RecoveryIncidentsApi");
await updateIncidentStatus(incident.id, text); await updateIncidentStatus(incident.id, text);
fetchIncidents(); fetchIncidents();
setSuccessMsg(`Incident status updated to ${text} successfully.`); setSuccessMsg(`Incident status updated to ${text} successfully.`);
@@ -253,11 +274,13 @@ export default function RecoveryIncidentsList() {
const firstItem = groupItems[0]; const firstItem = groupItems[0];
// Calculate status summary for group header // Calculate status summary for group header
const pendingCount = groupItems.filter(i => { const pendingCount = groupItems.filter((i) => {
const s = (i.status || "").toLowerCase(); const s = (i.status || "").toLowerCase();
return s.includes("pending") || s.includes("review") || s.includes("new"); return (
s.includes("pending") || s.includes("review") || s.includes("new")
);
}).length; }).length;
const approvedCount = groupItems.filter(i => { const approvedCount = groupItems.filter((i) => {
const s = (i.status || "").toLowerCase(); const s = (i.status || "").toLowerCase();
return s.includes("approved") || s.includes("active"); return s.includes("approved") || s.includes("active");
}).length; }).length;
@@ -326,13 +349,14 @@ export default function RecoveryIncidentsList() {
/> />
), ),
className: "w-[40px] pr-0", className: "w-[40px] pr-0",
accessor: (row) => row.isGroupHeader ? null : ( accessor: (row) =>
<CustomCheckBox row.isGroupHeader ? null : (
checked={selectedIds.has(row.id)} <CustomCheckBox
onChange={() => toggleSelectOne(row.id)} checked={selectedIds.has(row.id)}
onClick={(e) => e.stopPropagation()} onChange={() => toggleSelectOne(row.id)}
/> onClick={(e) => e.stopPropagation()}
), />
),
}, },
{ {
header: <HeaderLabel text="Recovery ID" />, header: <HeaderLabel text="Recovery ID" />,
@@ -375,7 +399,9 @@ export default function RecoveryIncidentsList() {
{ {
header: <HeaderLabel text="Category" />, header: <HeaderLabel text="Category" />,
accessor: (row) => accessor: (row) =>
row.isGroupHeader ? null : row.category ? <BadgeLabel text={row.category} /> : null, row.isGroupHeader ? null : row.category ? (
<BadgeLabel text={row.category} />
) : null,
}, },
{ {
header: <HeaderLabel text="Status" />, header: <HeaderLabel text="Status" />,
@@ -384,7 +410,10 @@ export default function RecoveryIncidentsList() {
return ( return (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{(row.statuses || []).map((status, idx) => ( {(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]'}`}> <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} {status.text}
</div> </div>
))} ))}
@@ -419,7 +448,8 @@ export default function RecoveryIncidentsList() {
{ {
header: <HeaderLabel text="Action" />, header: <HeaderLabel text="Action" />,
accessor: (row) => { accessor: (row) => {
const key = (row as any).groupKey || row.flightNumber || row.recoveryCode; const key =
(row as any).groupKey || row.flightNumber || row.recoveryCode;
return ( return (
<div className="flex items-center pl-2"> <div className="flex items-center pl-2">
{row.isGroupHeader ? ( {row.isGroupHeader ? (
@@ -446,12 +476,16 @@ export default function RecoveryIncidentsList() {
setEditingIncident(row); setEditingIncident(row);
setIsModalOpen(true); setIsModalOpen(true);
}} }}
icon={<PencilSimpleIcon size={16} className="text-yellow-500" />} icon={
<PencilSimpleIcon size={16} className="text-yellow-500" />
}
> >
Edit Incident Edit Incident
</CustomActionItem> </CustomActionItem>
<CustomActionItem <CustomActionItem
icon={<CheckCircleIcon size={16} className="text-green-500" />} icon={
<CheckCircleIcon size={16} className="text-green-500" />
}
onClick={() => handleStatusChange(row, "Approved")} onClick={() => handleStatusChange(row, "Approved")}
> >
Approve Approve
@@ -480,7 +514,7 @@ export default function RecoveryIncidentsList() {
const isAnyGroupExpanded = useMemo(() => { const isAnyGroupExpanded = useMemo(() => {
if (!isGrouped) return true; if (!isGrouped) return true;
const allGroupKeys = Array.from( const allGroupKeys = Array.from(
new Set(incidents.map((i) => i.flightNumber || "Other")) new Set(incidents.map((i) => i.flightNumber || "Other")),
); );
return allGroupKeys.some((key) => !collapsedGroups.has(key)); return allGroupKeys.some((key) => !collapsedGroups.has(key));
}, [isGrouped, incidents, collapsedGroups]); }, [isGrouped, incidents, collapsedGroups]);
@@ -491,7 +525,9 @@ export default function RecoveryIncidentsList() {
} }
return columns.filter((col) => { return columns.filter((col) => {
const headerText = const headerText =
typeof col.header === "object" && col.header !== null && "props" in col.header typeof col.header === "object" &&
col.header !== null &&
"props" in col.header
? (col.header as any).props.text ? (col.header as any).props.text
: ""; : "";
return ( return (
@@ -597,7 +633,8 @@ export default function RecoveryIncidentsList() {
itemName="Recovery Incidents" itemName="Recovery Incidents"
onRowClick={(row) => { onRowClick={(row) => {
if (row.isGroupHeader) { if (row.isGroupHeader) {
const key = (row as any).groupKey || row.flightNumber || row.recoveryCode; const key =
(row as any).groupKey || row.flightNumber || row.recoveryCode;
toggleGroup(key); toggleGroup(key);
} else { } else {
navigate(`/recovery/${row.id}`); navigate(`/recovery/${row.id}`);
@@ -620,12 +657,13 @@ export default function RecoveryIncidentsList() {
setEditingIncident(null); setEditingIncident(null);
fetchIncidents(); // Refresh list fetchIncidents(); // Refresh list
}} }}
onSuccess={(createdCount, flightNumber, passengerName) => { onSuccess={(createdCount, flightNumber, passengerName, isEdit) => {
setSuccessModalData({ setSuccessModalData({
isOpen: true, isOpen: true,
count: createdCount, count: createdCount,
flightNumber, flightNumber,
passengerName, passengerName,
isEdit,
}); });
}} }}
/> />
@@ -633,18 +671,24 @@ export default function RecoveryIncidentsList() {
{/* Success Modal */} {/* Success Modal */}
<CustomSuccessModal <CustomSuccessModal
isOpen={successModalData.isOpen} isOpen={successModalData.isOpen}
onClose={() => setSuccessModalData((prev) => ({ ...prev, isOpen: false }))} onClose={() =>
setSuccessModalData((prev) => ({ ...prev, isOpen: false }))
}
title={ title={
successModalData.count > 1 successModalData.isEdit
? "Recovery Incidents Created." ? (successModalData.count > 1 ? "Recovery Incidents Updated." : "Recovery Incident Updated.")
: "Recovery Incident Created." : (successModalData.count > 1 ? "Recovery Incidents Created." : "Recovery Incident Created.")
} }
label="FLIGHT & PASSENGER DETAILS" label="FLIGHT & PASSENGER DETAILS"
cohortName={`Flight ${successModalData.flightNumber}`} cohortName={`Flight ${successModalData.flightNumber}`}
cohortDescription={ cohortDescription={
successModalData.count > 1 successModalData.isEdit
? `Successfully logged incidents for ${successModalData.count} passengers, including ${successModalData.passengerName}.` ? (successModalData.count > 1
: `Successfully logged incident for ${successModalData.passengerName}.` ? `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> </div>