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:
@@ -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 */}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user