Merge pull request 'feat: implement recovery incidents list, audit logs table, and policy engine creation components' (#40) from azeem into development

Reviewed-on: https://gitea.maskantech.in/gitea_admin/aeroresolve_frontend/pulls/40
Reviewed-by: Syed Waseem khadri Rafai <waseem.khadri@maskatech.com>
This commit is contained in:
Syed Waseem khadri Rafai
2026-08-18 08:59:00 +00:00
4 changed files with 56 additions and 27 deletions
+35 -10
View File
@@ -10,7 +10,8 @@ import {
CustomInput, CustomInput,
CustomStatus, CustomStatus,
Skeleton, Skeleton,
CustomButton CustomButton,
CustomDropdown
} from "../../../components/custom"; } from "../../../components/custom";
import type { Column } from "../../../components/custom/CustomTable"; import type { Column } from "../../../components/custom/CustomTable";
import type { AuditLog, AuditLogFilters } from "../AuditLogsTypes"; import type { AuditLog, AuditLogFilters } from "../AuditLogsTypes";
@@ -71,6 +72,7 @@ export default function AuditLogsList() {
// Filters // Filters
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const [daysFilter, setDaysFilter] = useState("7");
// ─── Fetch ───────────────────────────────────────────────────────────────── // ─── Fetch ─────────────────────────────────────────────────────────────────
@@ -83,6 +85,20 @@ export default function AuditLogsList() {
limit: PAGE_SIZE, limit: PAGE_SIZE,
}; };
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); const result = await getAuditLogs(filters);
// Client-side search on entityLabel / entityId // Client-side search on entityLabel / entityId
@@ -106,7 +122,7 @@ export default function AuditLogsList() {
setLoading(false); setLoading(false);
} }
}, },
[search] [search, daysFilter]
); );
useEffect(() => { useEffect(() => {
@@ -116,7 +132,7 @@ export default function AuditLogsList() {
// Reset to page 1 when filters change // Reset to page 1 when filters change
useEffect(() => { useEffect(() => {
setCurrentPage(1); setCurrentPage(1);
}, [search]); }, [search, daysFilter]);
const handleExportCSV = () => { const handleExportCSV = () => {
if (logs.length === 0) return; if (logs.length === 0) return;
@@ -272,13 +288,22 @@ export default function AuditLogsList() {
} }
rightHeaderActions={ rightHeaderActions={
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<CustomButton <div className="w-[180px]">
variant="outlined" <CustomDropdown
className="flex items-center gap-2 h-[40px] px-4 rounded-[8px] border border-[#1E8E3E] text-[#1E8E3E] text-[13px] font-bold hover:bg-[#E6F4EA] transition-colors cursor-pointer" value={daysFilter}
> onChange={setDaysFilter}
<CalendarBlank size={16} weight="bold" /> leftIcon={<CalendarBlank size={16} weight="bold" className="text-[#1E8E3E]" />}
Range: Last 7 Days searchable={false}
</CustomButton> options={[
{ label: "Today", value: "today" },
{ label: "Last 7 Days", value: "7" },
{ label: "Last 30 Days", value: "30" },
{ label: "Last 90 Days", value: "90" },
{ label: "All Time", value: "all" },
]}
className="!border-[#1E8E3E] !bg-[#E6F4EA] !h-[40px] hover:!bg-[#E6F4EA]"
/>
</div>
<CustomButton <CustomButton
variant="outlined" variant="outlined"
onClick={handleExportCSV} onClick={handleExportCSV}
@@ -769,7 +769,7 @@ export default function AddPolicyEngine() {
<div className="flex flex-col h-full bg-white min-h-screen"> <div className="flex flex-col h-full bg-white min-h-screen">
{/* ─── Header ────────────────────────────────────────────────────── */} {/* ─── Header ────────────────────────────────────────────────────── */}
<div className="flex items-center justify-between px-8 py-4 bg-white border-b border-gray-200"> <div className="flex items-center justify-between py-4 bg-white border-b border-gray-200">
<div className="flex items-start gap-4"> <div className="flex items-start gap-4">
<button <button
onClick={() => navigate('/policy-engine')} onClick={() => navigate('/policy-engine')}
@@ -787,7 +787,7 @@ export default function AddPolicyEngine() {
</div> </div>
{/* ─── Body Content ──────────────────────────────────────────────── */} {/* ─── Body Content ──────────────────────────────────────────────── */}
<div className="flex-1 overflow-y-auto px-8 py-6 pb-32"> <div className="flex-1 overflow-y-auto [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden py-6 pb-32">
<div className="w-full flex flex-col gap-6"> <div className="w-full flex flex-col gap-6">
{/* Policy Information Card */} {/* Policy Information Card */}
@@ -1241,7 +1241,7 @@ export default function AddPolicyEngine() {
</div> </div>
{/* ─── Sticky Footer ─────────────────────────────────────────────── */} {/* ─── Sticky Footer ─────────────────────────────────────────────── */}
<div className="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 px-8 py-4 flex items-center justify-between z-10 shadow-[0_-4px_10px_rgba(0,0,0,0.02)]"> <div className="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 px-4 py-4 flex items-center justify-between z-10 shadow-[0_-4px_10px_rgba(0,0,0,0.02)]">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<span className="text-[14px] font-semibold text-gray-600">Status:</span> <span className="text-[14px] font-semibold text-gray-600">Status:</span>
<CustomStatus status={status} /> <CustomStatus status={status} />
@@ -233,7 +233,7 @@ export default function RecoveryIncidentsList() {
const headerStatuses = [ const headerStatuses = [
{ text: `${pendingCount} Pending`, variant: "warning" as const }, { text: `${pendingCount} Pending`, variant: "warning" as const },
{ text: `${approvedCount} Approved`, variant: "success" as const }, { text: `${approvedCount} App`, variant: "success" as const },
]; ];
// Calculate sum of values // Calculate sum of values
@@ -264,12 +264,12 @@ export default function RecoveryIncidentsList() {
result.push({ result.push({
...child, ...child,
groupKey: groupKey, groupKey: groupKey,
}); } as any);
}); });
} }
}); });
return result; return result.map((r, i) => ({ ...r, globalIndex: i }));
}, [filteredIncidents, isGrouped, collapsedGroups]); }, [filteredIncidents, isGrouped, collapsedGroups]);
const totalItems = displayData.length; const totalItems = displayData.length;
@@ -306,7 +306,7 @@ export default function RecoveryIncidentsList() {
{ {
header: <HeaderLabel text="Recovery ID" />, header: <HeaderLabel text="Recovery ID" />,
accessor: (row) => ( accessor: (row) => (
<div> <div className={row.isGroupHeader ? "-ml-[40px]" : ""}>
<PrimaryText text={row.recoveryCode} /> <PrimaryText text={row.recoveryCode} />
<SecondaryText text={formatDate(row.date)} /> <SecondaryText text={formatDate(row.date)} />
</div> </div>
@@ -344,7 +344,7 @@ export default function RecoveryIncidentsList() {
{ {
header: <HeaderLabel text="Category" />, header: <HeaderLabel text="Category" />,
accessor: (row) => accessor: (row) =>
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" />,
@@ -353,11 +353,9 @@ 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) => (
<CustomStatus <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]'}`}>
key={idx} {status.text}
status={status.text} </div>
variant={status.variant}
/>
))} ))}
</div> </div>
); );
@@ -389,11 +387,10 @@ export default function RecoveryIncidentsList() {
}, },
{ {
header: <HeaderLabel text="Action" />, header: <HeaderLabel text="Action" />,
className: "text-right",
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 justify-end pr-2"> <div className="flex items-center pl-2">
{row.isGroupHeader ? ( {row.isGroupHeader ? (
<div <div
className="cursor-pointer text-gray-500 hover:text-gray-900 transition-colors p-1" className="cursor-pointer text-gray-500 hover:text-gray-900 transition-colors p-1"
@@ -561,7 +558,12 @@ export default function RecoveryIncidentsList() {
navigate(`/recovery/${row.id}`); navigate(`/recovery/${row.id}`);
} }
}} }}
rowClassName={(row) => row.isGroupHeader ? "bg-white border-b border-gray-100 hover:bg-gray-50/60" : "bg-[#F9FAFB] border-transparent cursor-pointer hover:bg-[#F3F4F6]"} rowClassName={(row: any) => {
if (row.isGroupHeader) {
return "bg-white border-b border-gray-100 cursor-pointer hover:bg-gray-50/60";
}
return "bg-[#F9FAFB] border-transparent cursor-pointer hover:bg-[#F3F4F6]";
}}
/> />
{/* Modal */} {/* Modal */}
+3 -1
View File
@@ -1,5 +1,5 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom'; import { useParams, useNavigate } from 'react-router-dom';
import { User, Checks, X, ClockCounterClockwiseIcon, ArrowLeftIcon, ArrowsClockwiseIcon, AirplaneTiltIcon } from '@phosphor-icons/react'; import { User, Checks, X, ClockCounterClockwiseIcon, ArrowLeftIcon, ArrowsClockwiseIcon, AirplaneTiltIcon } from '@phosphor-icons/react';
import { CustomButton, CustomTabs, CustomBackButton, CustomStatus } from '../../../components/custom'; import { CustomButton, CustomTabs, CustomBackButton, CustomStatus } from '../../../components/custom';
import SummaryTab from './SummaryTab'; import SummaryTab from './SummaryTab';
@@ -22,6 +22,7 @@ function getStatusVariant(status?: string): "success" | "error" | "warning" | "i
export default function RecoveryIncidentTabs() { export default function RecoveryIncidentTabs() {
const { id } = useParams(); const { id } = useParams();
const navigate = useNavigate();
const [activeTab, setActiveTab] = useState('Summary'); const [activeTab, setActiveTab] = useState('Summary');
const [incident, setIncident] = useState<RecoveryIncident | null>(null); const [incident, setIncident] = useState<RecoveryIncident | null>(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@@ -50,6 +51,7 @@ export default function RecoveryIncidentTabs() {
try { try {
const updated = await updateIncidentStatus(id, newStatus); const updated = await updateIncidentStatus(id, newStatus);
setIncident(updated); setIncident(updated);
navigate('/recovery');
} catch (err) { } catch (err) {
console.error("Failed to update incident status:", err); console.error("Failed to update incident status:", err);
} finally { } finally {