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:
@@ -280,11 +280,12 @@ export default function AuditLogsList() {
|
|||||||
// ─── Render ─────────────────────────────────────────────────────────────────
|
// ─── Render ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const displayedLogs = search
|
const displayedLogs = search
|
||||||
? logs.filter((l) =>
|
? logs.filter(
|
||||||
|
(l) =>
|
||||||
l.entityLabel?.toLowerCase().includes(search.toLowerCase()) ||
|
l.entityLabel?.toLowerCase().includes(search.toLowerCase()) ||
|
||||||
l.entityId?.toLowerCase().includes(search.toLowerCase()) ||
|
l.entityId?.toLowerCase().includes(search.toLowerCase()) ||
|
||||||
l.module?.toLowerCase().includes(search.toLowerCase()) ||
|
l.module?.toLowerCase().includes(search.toLowerCase()) ||
|
||||||
l.performedBy?.toLowerCase().includes(search.toLowerCase())
|
l.performedBy?.toLowerCase().includes(search.toLowerCase()),
|
||||||
)
|
)
|
||||||
: logs;
|
: logs;
|
||||||
|
|
||||||
|
|||||||
@@ -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,8 +91,7 @@ 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) => {
|
||||||
@@ -109,9 +108,7 @@ export default function PolicyEngineList() {
|
|||||||
.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) {
|
||||||
@@ -161,9 +162,10 @@ export default function PolicyEngineList() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const displayedPolicies = search
|
const displayedPolicies = search
|
||||||
? policies.filter(p =>
|
? policies.filter(
|
||||||
|
(p) =>
|
||||||
p.policyName?.toLowerCase().includes(search.toLowerCase()) ||
|
p.policyName?.toLowerCase().includes(search.toLowerCase()) ||
|
||||||
p.jurisdiction?.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([]);
|
||||||
@@ -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,7 +349,8 @@ export default function RecoveryIncidentsList() {
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
className: "w-[40px] pr-0",
|
className: "w-[40px] pr-0",
|
||||||
accessor: (row) => row.isGroupHeader ? null : (
|
accessor: (row) =>
|
||||||
|
row.isGroupHeader ? null : (
|
||||||
<CustomCheckBox
|
<CustomCheckBox
|
||||||
checked={selectedIds.has(row.id)}
|
checked={selectedIds.has(row.id)}
|
||||||
onChange={() => toggleSelectOne(row.id)}
|
onChange={() => toggleSelectOne(row.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
|
||||||
|
? (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 incidents for ${successModalData.count} passengers, including ${successModalData.passengerName}.`
|
||||||
: `Successfully logged incident for ${successModalData.passengerName}.`
|
: `Successfully logged incident for ${successModalData.passengerName}.`)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user