feat: implement recovery incidents list, audit logs table, and policy engine creation components
This commit is contained in:
@@ -10,7 +10,8 @@ import {
|
||||
CustomInput,
|
||||
CustomStatus,
|
||||
Skeleton,
|
||||
CustomButton
|
||||
CustomButton,
|
||||
CustomDropdown
|
||||
} from "../../../components/custom";
|
||||
import type { Column } from "../../../components/custom/CustomTable";
|
||||
import type { AuditLog, AuditLogFilters } from "../AuditLogsTypes";
|
||||
@@ -71,6 +72,7 @@ export default function AuditLogsList() {
|
||||
|
||||
// Filters
|
||||
const [search, setSearch] = useState("");
|
||||
const [daysFilter, setDaysFilter] = useState("7");
|
||||
|
||||
// ─── Fetch ─────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -83,6 +85,20 @@ export default function AuditLogsList() {
|
||||
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);
|
||||
|
||||
// Client-side search on entityLabel / entityId
|
||||
@@ -106,7 +122,7 @@ export default function AuditLogsList() {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[search]
|
||||
[search, daysFilter]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -116,7 +132,7 @@ export default function AuditLogsList() {
|
||||
// Reset to page 1 when filters change
|
||||
useEffect(() => {
|
||||
setCurrentPage(1);
|
||||
}, [search]);
|
||||
}, [search, daysFilter]);
|
||||
|
||||
const handleExportCSV = () => {
|
||||
if (logs.length === 0) return;
|
||||
@@ -272,13 +288,22 @@ export default function AuditLogsList() {
|
||||
}
|
||||
rightHeaderActions={
|
||||
<div className="flex items-center gap-3">
|
||||
<CustomButton
|
||||
variant="outlined"
|
||||
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"
|
||||
>
|
||||
<CalendarBlank size={16} weight="bold" />
|
||||
Range: Last 7 Days
|
||||
</CustomButton>
|
||||
<div className="w-[180px]">
|
||||
<CustomDropdown
|
||||
value={daysFilter}
|
||||
onChange={setDaysFilter}
|
||||
leftIcon={<CalendarBlank size={16} weight="bold" className="text-[#1E8E3E]" />}
|
||||
searchable={false}
|
||||
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
|
||||
variant="outlined"
|
||||
onClick={handleExportCSV}
|
||||
|
||||
@@ -769,7 +769,7 @@ export default function AddPolicyEngine() {
|
||||
<div className="flex flex-col h-full bg-white min-h-screen">
|
||||
|
||||
{/* ─── 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">
|
||||
<button
|
||||
onClick={() => navigate('/policy-engine')}
|
||||
@@ -787,7 +787,7 @@ export default function AddPolicyEngine() {
|
||||
</div>
|
||||
|
||||
{/* ─── 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">
|
||||
|
||||
{/* Policy Information Card */}
|
||||
@@ -1241,7 +1241,7 @@ export default function AddPolicyEngine() {
|
||||
</div>
|
||||
|
||||
{/* ─── 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">
|
||||
<span className="text-[14px] font-semibold text-gray-600">Status:</span>
|
||||
<CustomStatus status={status} />
|
||||
|
||||
@@ -233,7 +233,7 @@ export default function RecoveryIncidentsList() {
|
||||
|
||||
const headerStatuses = [
|
||||
{ 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
|
||||
@@ -264,12 +264,12 @@ export default function RecoveryIncidentsList() {
|
||||
result.push({
|
||||
...child,
|
||||
groupKey: groupKey,
|
||||
});
|
||||
} as any);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
return result.map((r, i) => ({ ...r, globalIndex: i }));
|
||||
}, [filteredIncidents, isGrouped, collapsedGroups]);
|
||||
|
||||
const totalItems = displayData.length;
|
||||
@@ -306,7 +306,7 @@ export default function RecoveryIncidentsList() {
|
||||
{
|
||||
header: <HeaderLabel text="Recovery ID" />,
|
||||
accessor: (row) => (
|
||||
<div>
|
||||
<div className={row.isGroupHeader ? "-ml-[40px]" : ""}>
|
||||
<PrimaryText text={row.recoveryCode} />
|
||||
<SecondaryText text={formatDate(row.date)} />
|
||||
</div>
|
||||
@@ -344,7 +344,7 @@ export default function RecoveryIncidentsList() {
|
||||
{
|
||||
header: <HeaderLabel text="Category" />,
|
||||
accessor: (row) =>
|
||||
row.category ? <BadgeLabel text={row.category} /> : null,
|
||||
row.isGroupHeader ? null : row.category ? <BadgeLabel text={row.category} /> : null,
|
||||
},
|
||||
{
|
||||
header: <HeaderLabel text="Status" />,
|
||||
@@ -353,11 +353,9 @@ export default function RecoveryIncidentsList() {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
{(row.statuses || []).map((status, idx) => (
|
||||
<CustomStatus
|
||||
key={idx}
|
||||
status={status.text}
|
||||
variant={status.variant}
|
||||
/>
|
||||
<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}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
@@ -389,11 +387,10 @@ export default function RecoveryIncidentsList() {
|
||||
},
|
||||
{
|
||||
header: <HeaderLabel text="Action" />,
|
||||
className: "text-right",
|
||||
accessor: (row) => {
|
||||
const key = (row as any).groupKey || row.flightNumber || row.recoveryCode;
|
||||
return (
|
||||
<div className="flex justify-end pr-2">
|
||||
<div className="flex items-center pl-2">
|
||||
{row.isGroupHeader ? (
|
||||
<div
|
||||
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}`);
|
||||
}
|
||||
}}
|
||||
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 */}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 { CustomButton, CustomTabs, CustomBackButton, CustomStatus } from '../../../components/custom';
|
||||
import SummaryTab from './SummaryTab';
|
||||
@@ -22,6 +22,7 @@ function getStatusVariant(status?: string): "success" | "error" | "warning" | "i
|
||||
|
||||
export default function RecoveryIncidentTabs() {
|
||||
const { id } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const [activeTab, setActiveTab] = useState('Summary');
|
||||
const [incident, setIncident] = useState<RecoveryIncident | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -50,6 +51,7 @@ export default function RecoveryIncidentTabs() {
|
||||
try {
|
||||
const updated = await updateIncidentStatus(id, newStatus);
|
||||
setIncident(updated);
|
||||
navigate('/recovery');
|
||||
} catch (err) {
|
||||
console.error("Failed to update incident status:", err);
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user