123 lines
3.8 KiB
TypeScript
123 lines
3.8 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import { Card } from "@/components/ui/card";
|
|
import { Input } from "@/components/ui/input";
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/components/ui/select";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { formatDistanceToNow } from "date-fns";
|
|
import { AuditLog } from "@/types/admin";
|
|
import { adminApi } from "@/lib/api/admin";
|
|
import { Loader2 } from "lucide-react";
|
|
|
|
export default function AuditLogsPage() {
|
|
const [logs, setLogs] = useState<AuditLog[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
const [filters, setFilters] = useState({
|
|
user: "",
|
|
action: "",
|
|
entity: "",
|
|
});
|
|
|
|
useEffect(() => {
|
|
const fetchLogs = async () => {
|
|
setLoading(true);
|
|
try {
|
|
const data = await adminApi.getAuditLogs();
|
|
setLogs(data);
|
|
} catch (error) {
|
|
console.error("Failed to fetch audit logs", error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
fetchLogs();
|
|
}, []);
|
|
|
|
return (
|
|
<div className="p-6 space-y-6">
|
|
<div>
|
|
<h1 className="text-3xl font-bold">Audit Logs</h1>
|
|
<p className="text-muted-foreground mt-1">View system activity and changes</p>
|
|
</div>
|
|
|
|
{/* Filters */}
|
|
<Card className="p-4">
|
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
<div>
|
|
<Input placeholder="Search user..." />
|
|
</div>
|
|
<div>
|
|
<Select>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Action" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="CREATE">Create</SelectItem>
|
|
<SelectItem value="UPDATE">Update</SelectItem>
|
|
<SelectItem value="DELETE">Delete</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div>
|
|
<Select>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Entity Type" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="correspondence">Correspondence</SelectItem>
|
|
<SelectItem value="rfa">RFA</SelectItem>
|
|
<SelectItem value="user">User</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
|
|
{/* Logs List */}
|
|
{loading ? (
|
|
<div className="flex justify-center py-12">
|
|
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
|
</div>
|
|
) : (
|
|
<div className="space-y-2">
|
|
{logs.map((log) => (
|
|
<Card key={log.audit_log_id} className="p-4">
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-3 mb-2">
|
|
<span className="font-medium">{log.user_name}</span>
|
|
<Badge variant={log.action === "CREATE" ? "default" : "secondary"}>
|
|
{log.action}
|
|
</Badge>
|
|
<Badge variant="outline">{log.entity_type}</Badge>
|
|
</div>
|
|
<p className="text-sm text-muted-foreground">{log.description}</p>
|
|
<p className="text-xs text-muted-foreground mt-2">
|
|
{formatDistanceToNow(new Date(log.created_at), {
|
|
addSuffix: true,
|
|
})}
|
|
</p>
|
|
</div>
|
|
{log.ip_address && (
|
|
<span className="text-xs text-muted-foreground bg-muted px-2 py-1 rounded">
|
|
IP: {log.ip_address}
|
|
</span>
|
|
)}
|
|
</div>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|