Files
aeroresolve_frontend/src/app/recoveryIncidents/tabs/AuditTrailTab.tsx
T

144 lines
5.5 KiB
TypeScript

import { useState, useEffect } from 'react';
import { ClipboardTextIcon, CheckCircleIcon } from '@phosphor-icons/react';
import { formatDate } from '../../../utils/formatDate';
import { getIncidentAuditTrail, type AuditTrailStepDto } from '../RecoveryIncidentsApi';
import type { RecoveryIncident } from '../RecoveryIncidentsTypes';
interface AuditTrailTabProps {
incident?: RecoveryIncident | null;
}
export default function AuditTrailTab({ incident }: AuditTrailTabProps) {
const [apiSteps, setApiSteps] = useState<AuditTrailStepDto[]>([]);
const [loading, setLoading] = useState(false);
const code = incident?.recoveryCode || 'REC-INCIDENT';
const passenger = incident?.passengerName || 'Passenger';
const flight = incident?.flightNumber || 'Flight';
const route = incident?.flightRoute || 'Route';
const status = incident?.status || 'Pending';
const evaluation = incident?.evaluation;
const policyName = evaluation?.policyName || 'Standard Policy';
const cohortName = evaluation?.matchedCohortName || 'General Audience';
const recoveryScore = evaluation?.recoveryScore || 0;
useEffect(() => {
if (!incident?.id) return;
setLoading(true);
getIncidentAuditTrail(incident.id)
.then((data) => {
if (Array.isArray(data)) {
setApiSteps(data);
}
})
.catch((err) => {
console.error('Failed to load backend audit trail:', err);
})
.finally(() => {
setLoading(false);
});
}, [incident?.id, incident?.status, incident?.evaluation?.updatedAt]);
const checkIcon = <CheckCircleIcon size={24} weight="fill" className="text-[#1B9869]" />;
const isApproved = status.toLowerCase().includes('appr');
const isRejected = status.toLowerCase().includes('reject');
// Initial default steps if API response is loading
const defaultInitialSteps: AuditTrailStepDto[] = [
{
id: 'step-1',
title: 'Flight Disruption Recorded',
description: `Disruption identified for ${passenger} on flight ${flight} (${route}).`,
timestamp: incident?.createdAt || incident?.date || new Date().toISOString(),
status: 'completed',
},
{
id: 'step-2',
title: 'Target Audience Cohort Evaluated',
description: `Automated audience eligibility assessment performed against active frameworks. Matched cohort: "${cohortName}".`,
timestamp: incident?.createdAt || new Date().toISOString(),
status: 'completed',
},
{
id: 'step-3',
title: 'Policy Evaluated',
description: `Evaluated against active rules under "${policyName}". Recovery Score: ${recoveryScore}/100.`,
timestamp: evaluation?.createdAt || incident?.createdAt || new Date().toISOString(),
status: 'completed',
},
{
id: 'step-4',
title: `Status: ${status}`,
description: `Case officer assigned. Case status updated to "${status}".`,
timestamp: incident?.updatedAt || new Date().toISOString(),
status: 'completed',
},
{
id: 'step-5',
title: 'Recovery Resolution',
description: isApproved
? 'Recovery incident approved. Automated settlement and customer notification dispatched.'
: isRejected
? 'Recovery incident rejected by case officer.'
: 'Refund and compensation settlement will initiate upon final approval.',
timestamp: incident?.updatedAt || new Date().toISOString(),
status: isApproved ? 'completed' : isRejected ? 'rejected' : 'pending',
},
];
const displaySteps = apiSteps.length > 0 ? apiSteps : defaultInitialSteps;
return (
<div className="flex flex-col gap-4">
<div className="bg-white rounded-2xl p-6 border border-gray-100 shadow-sm flex flex-col gap-6">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<ClipboardTextIcon size={20} weight="bold" className="text-[#143d30]" />
<h3 className="text-[13px] font-bold text-[#143d30] tracking-wider uppercase">
Lifecycle Timeline & Audit Trail ({code})
</h3>
</div>
</div>
<div className="flex flex-col mt-4 pl-2">
{displaySteps.map((step, idx) => {
const isLast = idx === displaySteps.length - 1;
const rawTime = (step as any).createdAt || step.timestamp;
return (
<div key={step.id || idx} className={`relative pl-10 ${isLast ? '' : 'pb-8'}`}>
{/* Connecting Line */}
{!isLast && (
<div
className="absolute left-[7px] top-7 bottom-0 w-[2px] bg-[#1B9869]"
></div>
)}
{/* Node Icon Indicator */}
<div className="absolute left-[-4px] top-0.5 bg-white flex items-center justify-center">
<div className="text-[#1B9869]">{checkIcon}</div>
</div>
{/* Step Details */}
<div className="flex justify-between items-start gap-4">
<div className="flex flex-col gap-1 max-w-xl">
<h4 className="text-sm font-semibold text-gray-900">{step.title}</h4>
<p className="text-[13px] text-gray-500 leading-relaxed">
{step.description}
</p>
</div>
<span className="text-[11px] font-medium text-gray-400 tracking-wider shrink-0">
{formatDate(rawTime)}
</span>
</div>
</div>
);
})}
</div>
</div>
</div>
);
}