ts issue
This commit is contained in:
parent
da8982e0e1
commit
1babe131b5
|
|
@ -7,37 +7,54 @@ import {
|
|||
Search,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Filter,
|
||||
Terminal,
|
||||
} from "lucide-react";
|
||||
import { auditService, type AuditLog } from "@/services";
|
||||
import { format } from "date-fns";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export default function AuditPage() {
|
||||
export default function ActivityLogPage() {
|
||||
const [page, setPage] = useState(1);
|
||||
const [limit] = useState(15);
|
||||
const [search, setSearch] = useState("");
|
||||
const [filter, setFilter] = useState<"action" | "userId">("action");
|
||||
|
||||
const { data: auditData, isLoading } = useQuery({
|
||||
queryKey: ["admin", "audit", "logs", page, limit, search],
|
||||
queryKey: ["activity-log", page, limit, search, filter],
|
||||
queryFn: async () => {
|
||||
const params: Record<string, string | number> = { page, limit };
|
||||
if (search) params.search = search;
|
||||
return await auditService.getAuditLogs(params);
|
||||
const params: Record<string, string | number> = {
|
||||
page,
|
||||
limit,
|
||||
};
|
||||
|
||||
if (search.trim()) {
|
||||
params.search = search.trim();
|
||||
params.filter = filter;
|
||||
}
|
||||
|
||||
return auditService.getAuditLogs(params);
|
||||
},
|
||||
});
|
||||
|
||||
const getActionColor = (action: string) => {
|
||||
const act = action.toUpperCase();
|
||||
if (act.includes("CREATE"))
|
||||
|
||||
if (act.includes("CREATE")) {
|
||||
return "text-blue-600 bg-blue-50 border-blue-100";
|
||||
if (act.includes("UPDATE"))
|
||||
}
|
||||
|
||||
if (act.includes("UPDATE")) {
|
||||
return "text-emerald-600 bg-emerald-50 border-emerald-100";
|
||||
if (act.includes("DELETE"))
|
||||
}
|
||||
|
||||
if (act.includes("DELETE")) {
|
||||
return "text-rose-600 bg-rose-50 border-rose-100";
|
||||
if (act.includes("LOGIN"))
|
||||
}
|
||||
|
||||
if (act.includes("LOGIN")) {
|
||||
return "text-purple-600 bg-purple-50 border-purple-100";
|
||||
}
|
||||
|
||||
return "text-slate-600 bg-slate-50 border-slate-100";
|
||||
};
|
||||
|
||||
|
|
@ -46,78 +63,88 @@ export default function AuditPage() {
|
|||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-gray-900 tracking-tight">
|
||||
Audit Logs
|
||||
Activity Log
|
||||
</h1>
|
||||
|
||||
<p className="text-gray-500 mt-1">
|
||||
Comprehensive system transaction registry.
|
||||
Audit trail of all administrative actions.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{/* View only access: No administrative actions */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card className="border shadow-none rounded-none">
|
||||
<CardHeader className="border-b pb-4 flex flex-row items-center justify-between space-y-0">
|
||||
<CardTitle className="text-xs font-bold uppercase tracking-widest text-gray-400">
|
||||
Security Ledger
|
||||
System Audit
|
||||
</CardTitle>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative w-64">
|
||||
<select
|
||||
value={filter}
|
||||
onChange={(e) => {
|
||||
setPage(1);
|
||||
setFilter(e.target.value as "action" | "userId");
|
||||
}}
|
||||
className="h-9 px-3 border border-gray-200 text-xs bg-white"
|
||||
>
|
||||
<option value="action">Action</option>
|
||||
<option value="userId">User ID</option>
|
||||
</select>
|
||||
|
||||
<div className="relative w-72">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
|
||||
|
||||
<Input
|
||||
className="pl-10 h-9 rounded-none border-gray-200 text-xs"
|
||||
placeholder="Search resources..."
|
||||
placeholder={`Search by ${filter}...`}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
onChange={(e) => {
|
||||
setPage(1);
|
||||
setSearch(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-9 rounded-none border-gray-200"
|
||||
>
|
||||
<Filter className="w-4 h-4 mr-2" /> Filter
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="p-0">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-left">
|
||||
<thead className="bg-gray-50 border-b">
|
||||
<tr>
|
||||
<th className="px-6 py-4 text-[10px] font-bold text-gray-400 uppercase tracking-widest">
|
||||
Act
|
||||
Action
|
||||
</th>
|
||||
|
||||
<th className="px-6 py-4 text-[10px] font-bold text-gray-400 uppercase tracking-widest">
|
||||
User ID
|
||||
User
|
||||
</th>
|
||||
|
||||
<th className="px-6 py-4 text-[10px] font-bold text-gray-400 uppercase tracking-widest">
|
||||
Resource
|
||||
</th>
|
||||
<th className="px-6 py-4 text-[10px] font-bold text-gray-400 uppercase tracking-widest">
|
||||
IP
|
||||
Detail
|
||||
</th>
|
||||
|
||||
<th className="px-6 py-4 text-[10px] font-bold text-gray-400 uppercase tracking-widest text-right">
|
||||
Date
|
||||
Timestamp
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody className="divide-y">
|
||||
{isLoading ? (
|
||||
<tr>
|
||||
<td
|
||||
colSpan={5}
|
||||
colSpan={4}
|
||||
className="px-6 py-20 text-center text-gray-400 animate-pulse font-medium uppercase tracking-widest text-[10px]"
|
||||
>
|
||||
Retrieving audit trail...
|
||||
Synchronizing audit records...
|
||||
</td>
|
||||
</tr>
|
||||
) : auditData?.data && auditData.data.length > 0 ? (
|
||||
) : auditData?.data?.length ? (
|
||||
auditData.data.map((log: AuditLog) => (
|
||||
<tr
|
||||
key={log.id}
|
||||
className="hover:bg-gray-50 transition-colors group"
|
||||
className="hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
<td className="px-6 py-4">
|
||||
<span
|
||||
|
|
@ -129,28 +156,45 @@ export default function AuditPage() {
|
|||
{log.action}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-6 py-4 text-sm font-bold text-gray-900 tracking-tighter">
|
||||
{log.userId || "--"}
|
||||
|
||||
<td className="px-6 py-4">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-900">
|
||||
{log.user
|
||||
? `${log.user.firstName ?? ""} ${
|
||||
log.user.lastName ?? ""
|
||||
}`.trim() || "Unknown User"
|
||||
: "SYSTEM"}
|
||||
</p>
|
||||
|
||||
<p className="text-xs text-gray-500">
|
||||
{log.user?.email ?? log.userId}
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 text-[10px] font-bold text-gray-400 uppercase flex items-center gap-1.5 mt-4">
|
||||
<Terminal className="w-3 h-3" /> {log.resourceType}:{" "}
|
||||
{log.resourceId.substring(0, 10)}
|
||||
|
||||
<td className="px-6 py-4">
|
||||
<div className="flex items-center gap-2 text-sm text-gray-700">
|
||||
<Terminal className="w-4 h-4 text-gray-400 shrink-0" />
|
||||
<span>{log.detail}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 text-xs font-mono text-gray-500">
|
||||
{log.ipAddress || "--"}
|
||||
</td>
|
||||
<td className="px-6 py-4 text-right text-xs text-gray-500 font-medium">
|
||||
{format(new Date(log.timestamp), "MMM dd, HH:mm")}
|
||||
|
||||
<td className="px-6 py-4 text-right text-xs text-gray-500 whitespace-nowrap">
|
||||
{format(
|
||||
new Date(log.createdAt),
|
||||
"MMM dd, yyyy HH:mm:ss",
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td
|
||||
colSpan={5}
|
||||
className="px-6 py-20 text-center text-gray-400 italic font-medium uppercase tracking-widest text-[10px]"
|
||||
colSpan={4}
|
||||
className="px-6 py-20 text-center text-gray-400 italic"
|
||||
>
|
||||
Security ledger is clear.
|
||||
No activity logs recorded.
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
|
|
@ -158,27 +202,31 @@ export default function AuditPage() {
|
|||
</table>
|
||||
</div>
|
||||
</CardContent>
|
||||
{auditData && (
|
||||
|
||||
{auditData?.meta && auditData.meta.totalPages > 1 && (
|
||||
<div className="p-4 border-t flex items-center justify-between bg-gray-50/30">
|
||||
<p className="text-[10px] font-bold text-gray-400 uppercase tracking-widest">
|
||||
Standard View: {auditData.total || 0} Entries
|
||||
Page {auditData.meta.page} of {auditData.meta.totalPages} (
|
||||
{auditData.meta.total} records)
|
||||
</p>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="h-8 w-8 rounded-none"
|
||||
onClick={() => setPage((p) => Math.max(1, p - 1))}
|
||||
disabled={page === 1}
|
||||
disabled={!auditData.meta.hasPreviousPage}
|
||||
>
|
||||
<ChevronLeft className="w-4 h-4" />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="h-8 w-8 rounded-none"
|
||||
onClick={() => setPage((p) => p + 1)}
|
||||
disabled={!auditData?.data || auditData.data.length < limit}
|
||||
disabled={!auditData.meta.hasNextPage}
|
||||
>
|
||||
<ChevronRight className="w-4 h-4" />
|
||||
</Button>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user