diff --git a/src/app/auditLogs/components/AuditLogsList.tsx b/src/app/auditLogs/components/AuditLogsList.tsx
index 2022615..e83e7fe 100644
--- a/src/app/auditLogs/components/AuditLogsList.tsx
+++ b/src/app/auditLogs/components/AuditLogsList.tsx
@@ -3,7 +3,7 @@ import {
MagnifyingGlassIcon,
CalendarBlank,
Export,
- CaretRight
+ CaretRight,
} from "@phosphor-icons/react";
import {
CustomTable,
@@ -11,14 +11,11 @@ import {
CustomStatus,
Skeleton,
CustomButton,
- CustomDropdown
+ CustomDropdown,
} from "../../../components/custom";
import type { Column } from "../../../components/custom/CustomTable";
import type { AuditLog, AuditLogFilters } from "../AuditLogsTypes";
-import {
- AUDIT_MODULE_LABELS,
- AUDIT_ACTION_VARIANTS,
-} from "../AuditLogsTypes";
+import { AUDIT_MODULE_LABELS, AUDIT_ACTION_VARIANTS } from "../AuditLogsTypes";
import { getAuditLogs } from "../AuditLogsApi";
import AuditLogDetail from "./AuditLogDetail";
@@ -32,9 +29,17 @@ function HeaderLabel({ text }: { text: string }) {
);
}
-function PrimaryText({ text, className = "" }: { text: string, className?: string }) {
+function PrimaryText({
+ text,
+ className = "",
+}: {
+ text: string;
+ className?: string;
+}) {
return (
-
+
{text}
);
@@ -53,9 +58,11 @@ function ModuleBadge({ module }: { module: string }) {
function getLogDescription(row: AuditLog) {
const entity = row.entityLabel || row.entityId || "record";
const mod = AUDIT_MODULE_LABELS[row.module] || row.module;
- if (row.action === "CREATE") return `New record created in ${mod}: ${entity}.`;
+ if (row.action === "CREATE")
+ return `New record created in ${mod}: ${entity}.`;
if (row.action === "UPDATE") return `${mod} record updated: ${entity}.`;
- if (row.action === "STATUS_CHANGE") return `Status changed for ${entity} in ${mod}.`;
+ if (row.action === "STATUS_CHANGE")
+ return `Status changed for ${entity} in ${mod}.`;
if (row.action === "DELETE") return `${mod} record deleted: ${entity}.`;
return `Action ${row.action} performed on ${entity}.`;
}
@@ -88,33 +95,22 @@ export default function AuditLogsList() {
if (daysFilter !== "all") {
const now = new Date();
const past = new Date();
-
+
if (daysFilter === "today") {
past.setHours(0, 0, 0, 0);
} else {
past.setDate(now.getDate() - parseInt(daysFilter, 10));
}
-
+
filters.dateFrom = past.toISOString();
filters.dateTo = now.toISOString();
}
const result = await getAuditLogs(filters);
- // Client-side search on entityLabel / entityId
- const filtered = search
- ? result.data.filter(
- (l) =>
- l.entityLabel?.toLowerCase().includes(search.toLowerCase()) ||
- l.entityId?.toLowerCase().includes(search.toLowerCase()) ||
- l.module?.toLowerCase().includes(search.toLowerCase()) ||
- l.performedBy?.toLowerCase().includes(search.toLowerCase())
- )
- : result.data;
-
- setLogs(filtered);
- setTotalItems(result.total);
- setTotalPages(result.totalPages);
+ setLogs(result.data || []);
+ setTotalItems(result.total || 0);
+ setTotalPages(result.totalPages || 1);
} catch (err) {
console.error("Failed to fetch audit logs", err);
setLogs([]);
@@ -122,7 +118,7 @@ export default function AuditLogsList() {
setLoading(false);
}
},
- [search, daysFilter]
+ [daysFilter],
);
useEffect(() => {
@@ -137,20 +133,29 @@ export default function AuditLogsList() {
const handleExportCSV = () => {
if (logs.length === 0) return;
- const headers = ["Timestamp", "User", "Action", "Module", "Description", "Audit ID"];
- const rows = logs.map(row => {
+ const headers = [
+ "Timestamp",
+ "User",
+ "Action",
+ "Module",
+ "Description",
+ "Audit ID",
+ ];
+ const rows = logs.map((row) => {
const date = new Date(row.createdAt).toLocaleString();
const user = row.performedBy ?? "System";
const action = row.action;
const module = AUDIT_MODULE_LABELS[row.module] ?? row.module;
-
+
const entity = row.entityLabel || row.entityId || "record";
let desc = `Action ${action} performed on ${entity}.`;
- if (action === "CREATE") desc = `New record created in ${module}: ${entity}.`;
+ if (action === "CREATE")
+ desc = `New record created in ${module}: ${entity}.`;
if (action === "UPDATE") desc = `${module} record updated: ${entity}.`;
- if (action === "STATUS_CHANGE") desc = `Status changed for ${entity} in ${module}.`;
+ if (action === "STATUS_CHANGE")
+ desc = `Status changed for ${entity} in ${module}.`;
if (action === "DELETE") desc = `${module} record deleted: ${entity}.`;
-
+
const auditId = row.id;
// Escape quotes and wrap in quotes to handle commas in values
@@ -160,14 +165,14 @@ export default function AuditLogsList() {
`"${action.replace(/"/g, '""')}"`,
`"${module.replace(/"/g, '""')}"`,
`"${desc.replace(/"/g, '""')}"`,
- `"${auditId.replace(/"/g, '""')}"`
+ `"${auditId.replace(/"/g, '""')}"`,
].join(",");
});
const csvContent = [headers.join(","), ...rows].join("\n");
const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
const url = URL.createObjectURL(blob);
-
+
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", `audit_logs_${new Date().getTime()}.csv`);
@@ -183,11 +188,14 @@ export default function AuditLogsList() {
header:
,
accessor: (row) => {
const d = new Date(row.createdAt);
- const time = d.toLocaleTimeString(undefined, {
- hour: "numeric",
- minute: "2-digit",
- hour12: true,
- }).toLowerCase().replace(' ', '');
+ const time = d
+ .toLocaleTimeString(undefined, {
+ hour: "numeric",
+ minute: "2-digit",
+ hour12: true,
+ })
+ .toLowerCase()
+ .replace(" ", "");
const date = d.toLocaleDateString(undefined, {
day: "numeric",
month: "short",
@@ -195,8 +203,12 @@ export default function AuditLogsList() {
});
return (
- {time}
- {date}
+
+ {time}
+
+
+ {date}
+
);
},
@@ -210,7 +222,8 @@ export default function AuditLogsList() {
{
header:
,
accessor: (row) => {
- const displayStatus = row.action === 'STATUS_CHANGE' ? 'Status Change' : row.action;
+ const displayStatus =
+ row.action === "STATUS_CHANGE" ? "Status Change" : row.action;
return (
,
accessor: (row) => {
- const hash = row.id.split('-')[0].substring(0, 4).toUpperCase();
+ const hash = row.id.split("-")[0].substring(0, 4).toUpperCase();
return (
LOG-{hash}
@@ -266,6 +279,15 @@ export default function AuditLogsList() {
// ─── Render ─────────────────────────────────────────────────────────────────
+ const displayedLogs = search
+ ? logs.filter((l) =>
+ l.entityLabel?.toLowerCase().includes(search.toLowerCase()) ||
+ l.entityId?.toLowerCase().includes(search.toLowerCase()) ||
+ l.module?.toLowerCase().includes(search.toLowerCase()) ||
+ l.performedBy?.toLowerCase().includes(search.toLowerCase())
+ )
+ : logs;
+
const startIndex = totalItems > 0 ? (currentPage - 1) * PAGE_SIZE + 1 : 0;
const endIndex = Math.min(currentPage * PAGE_SIZE, totalItems);
@@ -273,14 +295,16 @@ export default function AuditLogsList() {
columns={columns}
- data={logs}
+ data={displayedLogs}
leftHeaderActions={
setSearch(e.target.value)}
- leftIcon={}
+ leftIcon={
+
+ }
className="!bg-[#F8F9FA] !rounded-[8px] !h-[40px] !border-none !text-[13px]"
containerClassName="!gap-0 border-none"
/>
@@ -292,7 +316,13 @@ export default function AuditLogsList() {
}
+ leftIcon={
+
+ }
searchable={false}
options={[
{ label: "Today", value: "today" },
@@ -304,7 +334,7 @@ export default function AuditLogsList() {
className="!border-[#1E8E3E] !bg-[#E6F4EA] !h-[40px] hover:!bg-[#E6F4EA]"
/>
-
- setSelectedLog(null)}
- />
+ setSelectedLog(null)} />
);
}
diff --git a/src/app/cohartManage/components/cohartList.tsx b/src/app/cohartManage/components/cohartList.tsx
index 4ac232a..6acb2a5 100644
--- a/src/app/cohartManage/components/cohartList.tsx
+++ b/src/app/cohartManage/components/cohartList.tsx
@@ -54,6 +54,7 @@ export default function CohortList() {
const [cohorts, setCohorts] = useState
([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
+ const [successMsg, setSuccessMsg] = useState(null);
// Server-side pagination
const [currentPage, setCurrentPage] = useState(1);
@@ -113,8 +114,11 @@ export default function CohortList() {
const handleDelete = async () => {
if (!deleteTarget) return;
setDeleteLoading(true);
+ setError(null);
+ setSuccessMsg(null);
try {
await deleteCohart(deleteTarget.id);
+ setSuccessMsg(`Cohort "${deleteTarget.name}" deleted successfully.`);
setDeleteTarget(null);
fetchCohorts(currentPage);
} catch {
@@ -127,9 +131,12 @@ export default function CohortList() {
const handleToggleStatus = async () => {
if (!deactivateTarget) return;
setStatusLoading(true);
+ setError(null);
+ setSuccessMsg(null);
try {
const newStatus = deactivateTarget.status === 'Active' ? 'Inactive' : 'Active';
await updateCohartStatus(deactivateTarget.id, { status: newStatus });
+ setSuccessMsg(`Cohort "${deactivateTarget.name}" is now ${newStatus}.`);
setDeactivateTarget(null);
fetchCohorts(currentPage);
} catch {
@@ -239,6 +246,13 @@ export default function CohortList() {
onClose={() => setError(null)}
/>
)}
+ {successMsg && (
+ setSuccessMsg(null)}
+ />
+ )}
columns={columns}
diff --git a/src/app/policyEngine/components/AddPolicyEngine.tsx b/src/app/policyEngine/components/AddPolicyEngine.tsx
index f7a4dc9..aa3fee8 100644
--- a/src/app/policyEngine/components/AddPolicyEngine.tsx
+++ b/src/app/policyEngine/components/AddPolicyEngine.tsx
@@ -15,6 +15,7 @@ import {
CustomSwitch,
CustomCheckBox,
CustomLoader,
+ Skeleton,
} from '../../../components/custom';
import CustomSuccessModal from '../../../components/custom/CustomSuccessModal';
import {
@@ -649,7 +650,7 @@ export default function AddPolicyEngine() {
jurisdictionId: Array.isArray(jurisdiction) && jurisdiction.length > 0 ? String(jurisdiction[0]) : undefined,
jurisdictionIds: Array.isArray(jurisdiction) ? jurisdiction.map(String) : [],
description: description || undefined,
- status: isDeploy ? 'active' : 'draft',
+ status: isDeploy ? status.toLowerCase() : 'draft',
audienceType: audienceType === 'Selected Cohorts' ? 'COHORT' : 'ALL',
targetAudiences:
audienceType === 'Selected Cohorts'
@@ -713,13 +714,13 @@ export default function AddPolicyEngine() {
valObj.booleanValue = !!val;
} else if (fieldType === 'number' || fieldType === 'decimal' || fieldType === 'percentage') {
valObj.numberValue = parseFloat(val) || 0;
+ } else if (Array.isArray(val)) {
+ valObj.textValue = JSON.stringify(val);
} else if (fieldType === 'dropdown' || fieldType === 'select' || fieldDef?.lookupSource) {
valObj.selectedValueId = String(val ?? '');
const lookupOpts = fieldLookupOptionsMap[fieldDef?.lookupSource || ''] || [];
const matchedOpt = lookupOpts.find((o) => o.value === val || o.id === val || o.code === val);
valObj.textValue = matchedOpt ? matchedOpt.label : String(val ?? '');
- } else if (Array.isArray(val)) {
- valObj.textValue = JSON.stringify(val);
} else {
valObj.textValue = String(val ?? '');
}
@@ -875,8 +876,82 @@ export default function AddPolicyEngine() {
if (loadingPolicy) {
return (
-
-
+
+ {/* ─── Header Skeleton ────────────────────────────────────────────── */}
+
+
+ {/* ─── Body Content Skeleton ──────────────────────────────────────── */}
+
+
+
+ {/* Policy Information Card */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Target Audience Card */}
+
+
+ {/* Rule Engine Card */}
+
+
+
+
);
}
@@ -1346,6 +1421,13 @@ export default function AddPolicyEngine() {
value={fieldVal ?? ''}
onChange={(e) => handleFieldValueChange(rule.id, action.id, field.fieldCode, e.target.value)}
placeholder={field.placeholder || 'Enter value...'}
+ min={
+ field.fieldType === 'date'
+ ? new Date(Date.now() - new Date().getTimezoneOffset() * 60000).toISOString().split('T')[0]
+ : field.fieldType === 'datetime'
+ ? new Date(Date.now() - new Date().getTimezoneOffset() * 60000).toISOString().slice(0, 16)
+ : undefined
+ }
/>
)}
diff --git a/src/app/policyEngine/components/PolicyEngineList.tsx b/src/app/policyEngine/components/PolicyEngineList.tsx
index 76f6b0e..6415489 100644
--- a/src/app/policyEngine/components/PolicyEngineList.tsx
+++ b/src/app/policyEngine/components/PolicyEngineList.tsx
@@ -1,6 +1,13 @@
-import { useState, useEffect, useCallback } from 'react';
-import { PlusIcon, TrashIcon, MagnifyingGlassIcon, XIcon, PencilSimpleIcon, ChecksIcon } from '@phosphor-icons/react';
-import { useNavigate } from 'react-router-dom';
+import { useState, useEffect, useCallback } from "react";
+import {
+ PlusIcon,
+ TrashIcon,
+ MagnifyingGlassIcon,
+ XIcon,
+ PencilSimpleIcon,
+ ChecksIcon,
+} from "@phosphor-icons/react";
+import { useNavigate } from "react-router-dom";
import {
CustomTable,
CustomInput,
@@ -9,11 +16,16 @@ import {
CustomActionMenu,
CustomActionItem,
CustomConfirmationModal,
+ CustomAlertBanner,
Skeleton,
-} from '../../../components/custom';
-import type { Column } from '../../../components/custom/CustomTable';
-import type { PolicyEngineResponse } from '../PolicyEngineTypes';
-import { getPolicies, deletePolicy, updatePolicyStatus } from '../PolicyEngineApi';
+} from "../../../components/custom";
+import type { Column } from "../../../components/custom/CustomTable";
+import type { PolicyEngineResponse } from "../PolicyEngineTypes";
+import {
+ getPolicies,
+ deletePolicy,
+ updatePolicyStatus,
+} from "../PolicyEngineApi";
// ─── Constants ───────────────────────────────────────────────────────────────
@@ -23,14 +35,16 @@ const PAGE_SIZE = 10;
function HeaderLabel({ text }: { text: string }) {
return (
-
{text}
+
+ {text}
+
);
}
function CellText({ text }: { text: string | null | undefined }) {
return (
-
- {text || '—'}
+
+ {text || "—"}
);
}
@@ -49,6 +63,8 @@ export default function PolicyEngineList() {
const navigate = useNavigate();
const [policies, setPolicies] = useState([]);
const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [successMsg, setSuccessMsg] = useState(null);
// Pagination
const [currentPage, setCurrentPage] = useState(1);
@@ -56,36 +72,42 @@ export default function PolicyEngineList() {
const [totalPages, setTotalPages] = useState(1);
// Filters
- const [search, setSearch] = useState('');
+ const [search, setSearch] = useState("");
// Modal state
- const [deleteTarget, setDeleteTarget] = useState(null);
- const [deactivateTarget, setDeactivateTarget] = useState(null);
+ const [deleteTarget, setDeleteTarget] = useState(
+ null,
+ );
+ const [deactivateTarget, setDeactivateTarget] =
+ useState(null);
// ─── Fetch data ─────────────────────────────────────────────
- const fetchPolicies = useCallback((page: number) => {
- setLoading(true);
- getPolicies(page, PAGE_SIZE, search)
- .then((res) => {
- setPolicies(res.data || []);
- setTotalItems(res.total || 0);
- setTotalPages(res.totalPages || 1);
- })
- .catch((err) => {
- console.error('Failed to fetch policies:', err);
- setPolicies([]);
- setTotalItems(0);
- setTotalPages(1);
- })
- .finally(() => {
- setLoading(false);
- });
- }, [search]);
+ const fetchPolicies = useCallback(
+ (page: number) => {
+ setLoading(true);
+ getPolicies(page, PAGE_SIZE)
+ .then((res) => {
+ setPolicies(res.data || []);
+ setTotalItems(res.total || 0);
+ setTotalPages(res.totalPages || 1);
+ })
+ .catch((err) => {
+ console.error("Failed to fetch policies:", err);
+ setPolicies([]);
+ setTotalItems(0);
+ setTotalPages(1);
+ })
+ .finally(() => {
+ setLoading(false);
+ });
+ },
+ [],
+ );
useEffect(() => {
fetchPolicies(currentPage);
- }, [currentPage, search, fetchPolicies]);
+ }, [currentPage, fetchPolicies]);
// ─── Handlers ──────────────────────────────────────────────────────────────
@@ -100,27 +122,43 @@ export default function PolicyEngineList() {
const handleDelete = async () => {
if (!deleteTarget) return;
+ setError(null);
+ setSuccessMsg(null);
try {
await deletePolicy(deleteTarget.id);
+ setSuccessMsg(`Policy "${deleteTarget.policyName}" deleted successfully.`);
setDeleteTarget(null);
fetchPolicies(currentPage);
} catch (err) {
- console.error('Failed to delete policy:', err);
+ console.error("Failed to delete policy:", err);
+ setError("Failed to delete policy. Please try again.");
}
};
const handleToggleStatus = async () => {
if (!deactivateTarget) return;
+ setError(null);
+ setSuccessMsg(null);
try {
- const nextStatus = deactivateTarget.status === 'Active' ? 'inactive' : 'active';
+ const nextStatus =
+ deactivateTarget.status === "Active" ? "inactive" : "active";
await updatePolicyStatus(deactivateTarget.id, nextStatus);
+ setSuccessMsg(`Policy "${deactivateTarget.policyName}" is now ${nextStatus === 'active' ? 'Active' : 'Inactive'}.`);
setDeactivateTarget(null);
fetchPolicies(currentPage);
} catch (err) {
- console.error('Failed to toggle policy status:', err);
+ console.error("Failed to toggle policy status:", err);
+ setError("Failed to update policy status. Please try again.");
}
};
+ const displayedPolicies = search
+ ? policies.filter(p =>
+ p.policyName?.toLowerCase().includes(search.toLowerCase()) ||
+ p.jurisdiction?.toLowerCase().includes(search.toLowerCase())
+ )
+ : policies;
+
const startIndex = totalItems > 0 ? (currentPage - 1) * PAGE_SIZE + 1 : 0;
const endIndex = Math.min(currentPage * PAGE_SIZE, totalItems);
@@ -129,7 +167,7 @@ export default function PolicyEngineList() {
const columns: Column[] = [
{
header: ,
- accessor: row => (
+ accessor: (row) => (
{row.policyName}
@@ -137,22 +175,22 @@ export default function PolicyEngineList() {
},
{
header: ,
- accessor: row => ,
+ accessor: (row) => ,
},
{
header: ,
- accessor: row => ,
+ accessor: (row) => ,
},
{
header: ,
- accessor: row => ,
+ accessor: (row) => ,
},
{
header: ,
- className: 'text-right',
- accessor: row => (
+ className: "text-right",
+ accessor: (row) => (
- {row.status !== 'Active' && (
+ {row.status !== "Active" && (
}
variant="success"
@@ -161,7 +199,7 @@ export default function PolicyEngineList() {
Activate
)}
- {row.status === 'Active' && (
+ {row.status === "Active" && (
}
onClick={() => setDeactivateTarget(row)}
@@ -211,15 +249,29 @@ export default function PolicyEngineList() {
return (
<>
+ {error && (
+ setError(null)}
+ />
+ )}
+ {successMsg && (
+ setSuccessMsg(null)}
+ />
+ )}
columns={columns}
- data={policies}
+ data={displayedPolicies}
leftHeaderActions={
handleSearchChange(e.target.value)}
+ onChange={(e) => handleSearchChange(e.target.value)}
leftIcon={}
className="!bg-[#F3F6F5] !rounded-[10px] !h-[38px] !border !border-[#E5E7EB]"
containerClassName="!gap-0"
@@ -233,7 +285,7 @@ export default function PolicyEngineList() {
size="md"
leftIcon={}
className="!rounded-[10px] !gap-[10px] !h-[40px] !bg-[#1E7D5C] hover:!bg-[#17664B]"
- onClick={() => navigate('/policy-engine/add')}
+ onClick={() => navigate("/policy-engine/add")}
>
Deploy New Policy
@@ -265,13 +317,19 @@ export default function PolicyEngineList() {
isOpen={!!deactivateTarget}
onClose={() => setDeactivateTarget(null)}
onConfirm={handleToggleStatus}
- title={deactivateTarget?.status === 'Active' ? 'Deactivate Policy' : 'Activate Policy'}
+ title={
+ deactivateTarget?.status === "Active"
+ ? "Deactivate Policy"
+ : "Activate Policy"
+ }
description={
- deactivateTarget?.status === 'Active'
+ deactivateTarget?.status === "Active"
? `"${deactivateTarget?.policyName}" will be deactivated.`
: `"${deactivateTarget?.policyName}" will be reactivated.`
}
- confirmText={deactivateTarget?.status === 'Active' ? 'Deactivate' : 'Activate'}
+ confirmText={
+ deactivateTarget?.status === "Active" ? "Deactivate" : "Activate"
+ }
cancelText="Cancel"
variant="warning"
/>
diff --git a/src/app/recoveryIncidents/components/AddRecoveryIncidents.tsx b/src/app/recoveryIncidents/components/AddRecoveryIncidents.tsx
index 55f1a47..a54c218 100644
--- a/src/app/recoveryIncidents/components/AddRecoveryIncidents.tsx
+++ b/src/app/recoveryIncidents/components/AddRecoveryIncidents.tsx
@@ -18,6 +18,7 @@ import type { RecoveryIncident } from '../RecoveryIncidentsTypes';
interface AddRecoveryIncidentsProps {
isOpen: boolean;
onClose: () => void;
+ onSuccess?: (createdCount: number, flightNumber: string, passengerName: string) => void;
incident?: RecoveryIncident | null;
}
@@ -44,7 +45,7 @@ const DEFAULT_JURISDICTION_OPTIONS = [
{ label: "CAA SG (Singapore)", value: "CAA_SG" },
];
-export default function AddRecoveryIncidents({ isOpen, onClose, incident }: AddRecoveryIncidentsProps) {
+export default function AddRecoveryIncidents({ isOpen, onClose, onSuccess, 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);
@@ -347,6 +348,14 @@ export default function AddRecoveryIncidents({ isOpen, onClose, incident }: AddR
});
await Promise.all(promises);
+
+ if (onSuccess) {
+ onSuccess(
+ selectedPassengers.length,
+ formData.flightNumber || "TBD",
+ selectedPassengers[0]?.passengerName || "Unknown"
+ );
+ }
}
onClose();
} catch (error: any) {
diff --git a/src/app/recoveryIncidents/components/RecoveryIncidentsList.tsx b/src/app/recoveryIncidents/components/RecoveryIncidentsList.tsx
index 4607cf6..a6d6f17 100644
--- a/src/app/recoveryIncidents/components/RecoveryIncidentsList.tsx
+++ b/src/app/recoveryIncidents/components/RecoveryIncidentsList.tsx
@@ -22,6 +22,8 @@ import {
Skeleton,
CustomActionMenu,
CustomActionItem,
+ CustomAlertBanner,
+ CustomSuccessModal,
} from "../../../components/custom";
import type { Column } from "../../../components/custom/CustomTable";
import type { RecoveryIncident, MetricCardData } from "../RecoveryIncidentsTypes";
@@ -90,6 +92,8 @@ export default function RecoveryIncidentsList() {
const [incidents, setIncidents] = useState([]);
const [metrics, setMetrics] = useState([]);
const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [successMsg, setSuccessMsg] = useState(null);
useEffect(() => {
getRecoveryMetrics()
@@ -110,6 +114,14 @@ export default function RecoveryIncidentsList() {
// Selection
const [selectedIds, setSelectedIds] = useState>(new Set());
+ // Success Modal
+ const [successModalData, setSuccessModalData] = useState<{
+ isOpen: boolean;
+ count: number;
+ flightNumber: string;
+ passengerName: string;
+ }>({ isOpen: false, count: 0, flightNumber: '', passengerName: '' });
+
// ─── Fetch data ─────────────────────────────────────────────
const fetchIncidents = useCallback(async () => {
@@ -180,12 +192,16 @@ export default function RecoveryIncidentsList() {
const handleStatusChange = async (incident: RecoveryIncident, text: string) => {
try {
+ setError(null);
+ setSuccessMsg(null);
const { updateIncidentStatus, getRecoveryMetrics } = await import('../RecoveryIncidentsApi');
await updateIncidentStatus(incident.id, text);
fetchIncidents();
getRecoveryMetrics().then((data) => setMetrics(data)).catch(() => { });
+ setSuccessMsg(`Incident status updated to ${text} successfully.`);
} catch (error) {
console.error("Failed to update status", error);
+ setError("Failed to update status. Please try again.");
}
};
@@ -495,6 +511,20 @@ export default function RecoveryIncidentsList() {
return (
+ {error && (
+
setError(null)}
+ />
+ )}
+ {successMsg && (
+ setSuccessMsg(null)}
+ />
+ )}
{/* Metrics Row */}
{metrics.map((metric) => (
@@ -575,6 +605,32 @@ export default function RecoveryIncidentsList() {
setEditingIncident(null);
fetchIncidents(); // Refresh list
}}
+ onSuccess={(createdCount, flightNumber, passengerName) => {
+ setSuccessModalData({
+ isOpen: true,
+ count: createdCount,
+ flightNumber,
+ passengerName,
+ });
+ }}
+ />
+
+ {/* Success Modal */}
+ setSuccessModalData((prev) => ({ ...prev, isOpen: false }))}
+ title={
+ successModalData.count > 1
+ ? "Recovery Incidents Created."
+ : "Recovery Incident Created."
+ }
+ label="FLIGHT & PASSENGER DETAILS"
+ cohortName={`Flight ${successModalData.flightNumber}`}
+ cohortDescription={
+ successModalData.count > 1
+ ? `Successfully logged incidents for ${successModalData.count} passengers, including ${successModalData.passengerName}.`
+ : `Successfully logged incident for ${successModalData.passengerName}.`
+ }
/>
);
diff --git a/src/components/custom/CustomTable.tsx b/src/components/custom/CustomTable.tsx
index f759ee9..12b0002 100644
--- a/src/components/custom/CustomTable.tsx
+++ b/src/components/custom/CustomTable.tsx
@@ -1,5 +1,11 @@
import React from "react";
-import { MagnifyingGlassIcon, CaretLeftIcon, CaretRightIcon, FunnelSimpleIcon, ArrowsDownUpIcon } from "@phosphor-icons/react";
+import {
+ MagnifyingGlassIcon,
+ CaretLeftIcon,
+ CaretRightIcon,
+ FunnelSimpleIcon,
+ ArrowsDownUpIcon,
+} from "@phosphor-icons/react";
import CustomInput from "./CustomInput";
export interface Column {
@@ -53,7 +59,6 @@ export function CustomTable({
onRowClick,
rowClassName,
}: CustomTableProps) {
-
const handlePageChange = (newPage: number) => {
if (newPage >= 1 && newPage <= totalPages && onPageChange) {
onPageChange(newPage);
@@ -61,16 +66,43 @@ export function CustomTable({
};
const getPageNumbers = () => {
- const pages = [];
- for (let i = 1; i <= totalPages; i++) {
- pages.push(i);
+ const pages: (number | string)[] = [];
+
+ if (totalPages <= 7) {
+ for (let i = 1; i <= totalPages; i++) {
+ pages.push(i);
+ }
+ } else {
+ if (currentPage <= 4) {
+ pages.push(1, 2, 3, 4, 5, "...", totalPages);
+ } else if (currentPage >= totalPages - 3) {
+ pages.push(
+ 1,
+ "...",
+ totalPages - 4,
+ totalPages - 3,
+ totalPages - 2,
+ totalPages - 1,
+ totalPages,
+ );
+ } else {
+ pages.push(
+ 1,
+ "...",
+ currentPage - 1,
+ currentPage,
+ currentPage + 1,
+ "...",
+ totalPages,
+ );
+ }
}
+
return pages;
};
return (
-
{/* Top Header Section */}
@@ -89,9 +121,7 @@ export function CustomTable({
{leftHeaderActions}
-
- {rightHeaderActions}
-
+
{rightHeaderActions}
{/* Table Section */}
@@ -106,8 +136,18 @@ export function CustomTable
({
>
{col.header}
- {col.sortable &&
}
- {col.filterable &&
}
+ {col.sortable && (
+
+ )}
+ {col.filterable && (
+
+ )}
))}
@@ -122,7 +162,10 @@ export function CustomTable({
className={`border-b border-gray-50 hover:bg-gray-50/50 transition-colors ${onRowClick ? "cursor-pointer" : ""} ${rowClassName ? rowClassName(row) : ""}`}
>
{columns.map((col, colIndex) => (
-
+ |
{typeof col.accessor === "function"
? col.accessor(row)
: (row[col.accessor] as React.ReactNode)}
@@ -132,7 +175,10 @@ export function CustomTable({
))
) : (
- |
+ |
No {itemName} found.
|
@@ -144,7 +190,8 @@ export function CustomTable({
{/* Pagination Footer */}
- Showing {totalItems > 0 ? startIndex : 0} to {endIndex} of {totalItems} {itemName}
+ Showing {totalItems > 0 ? startIndex : 0} to {endIndex} of{" "}
+ {totalItems} {itemName}
@@ -157,20 +204,27 @@ export function CustomTable ({
- {getPageNumbers().map(page => (
-
- ))}
+ {getPageNumbers().map((page, index) =>
+ page === "..." ? (
+
+ ...
+
+ ) : (
+
+ ),
+ )}
|