mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-25 20:51:11 -05:00
Add audit trail and filters
Implements audit trail view for item approvals, adds approval date range filtering to moderation queue, and wires up UI and backend components (Approval History page, ItemApprovalHistory component, materialized view-based history, and query/filters integration) to support compliant reporting and time-based moderation filtering.
This commit is contained in:
136
src/pages/admin/ApprovalHistory.tsx
Normal file
136
src/pages/admin/ApprovalHistory.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* Approval History Page
|
||||
*
|
||||
* Full-page view for compliance reporting with advanced filters,
|
||||
* date range selection, and export functionality.
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
import { ItemApprovalHistory } from '@/components/moderation/ItemApprovalHistory';
|
||||
import { FilterDateRangePicker } from '@/components/filters/FilterDateRangePicker';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { X, FileCheck } from 'lucide-react';
|
||||
import { useUserRole } from '@/hooks/useUserRole';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
import type { EntityType } from '@/types/submissions';
|
||||
|
||||
export default function ApprovalHistory() {
|
||||
const { isModerator, loading: rolesLoading } = useUserRole();
|
||||
const [fromDate, setFromDate] = useState<Date | null>(null);
|
||||
const [toDate, setToDate] = useState<Date | null>(null);
|
||||
const [itemType, setItemType] = useState<EntityType | 'all'>('all');
|
||||
const [limit, setLimit] = useState<number>(100);
|
||||
|
||||
// Access control: moderators only
|
||||
if (rolesLoading) {
|
||||
return (
|
||||
<div className="container mx-auto p-6">
|
||||
<div className="text-center">Loading...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!isModerator()) {
|
||||
return <Navigate to="/" replace />;
|
||||
}
|
||||
|
||||
const hasFilters = fromDate || toDate || itemType !== 'all' || limit !== 100;
|
||||
|
||||
const clearFilters = () => {
|
||||
setFromDate(null);
|
||||
setToDate(null);
|
||||
setItemType('all');
|
||||
setLimit(100);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-6 max-w-7xl">
|
||||
{/* Header */}
|
||||
<div className="mb-6">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<FileCheck className="w-8 h-8 text-primary" />
|
||||
<h1 className="text-3xl font-bold">Approval History</h1>
|
||||
</div>
|
||||
<p className="text-muted-foreground">
|
||||
Complete audit trail of all approved items with exact timestamps for compliance reporting
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Filters */}
|
||||
<Card className="mb-6">
|
||||
<CardContent className="pt-6">
|
||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-4">
|
||||
{/* Date Range Filter */}
|
||||
<div className="lg:col-span-2">
|
||||
<FilterDateRangePicker
|
||||
label="Approval Date Range"
|
||||
fromDate={fromDate}
|
||||
toDate={toDate}
|
||||
onFromChange={(date) => setFromDate(date || null)}
|
||||
onToChange={(date) => setToDate(date || null)}
|
||||
fromPlaceholder="Start Date"
|
||||
toPlaceholder="End Date"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Item Type Filter */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="item-type">Item Type</Label>
|
||||
<Select value={itemType} onValueChange={(val) => setItemType(val as EntityType | 'all')}>
|
||||
<SelectTrigger id="item-type">
|
||||
<SelectValue placeholder="All Types" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All Types</SelectItem>
|
||||
<SelectItem value="park">Parks</SelectItem>
|
||||
<SelectItem value="ride">Rides</SelectItem>
|
||||
<SelectItem value="manufacturer">Manufacturers</SelectItem>
|
||||
<SelectItem value="designer">Designers</SelectItem>
|
||||
<SelectItem value="operator">Operators</SelectItem>
|
||||
<SelectItem value="ride_model">Ride Models</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Results Limit */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="limit">Results Limit</Label>
|
||||
<Select value={limit.toString()} onValueChange={(val) => setLimit(parseInt(val))}>
|
||||
<SelectTrigger id="limit">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="50">50</SelectItem>
|
||||
<SelectItem value="100">100</SelectItem>
|
||||
<SelectItem value="250">250</SelectItem>
|
||||
<SelectItem value="500">500</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Clear Filters */}
|
||||
{hasFilters && (
|
||||
<div className="mt-4 flex justify-end">
|
||||
<Button variant="outline" size="sm" onClick={clearFilters}>
|
||||
<X className="w-4 h-4 mr-2" />
|
||||
Clear Filters
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* History Table */}
|
||||
<ItemApprovalHistory
|
||||
dateRange={fromDate && toDate ? { from: fromDate, to: toDate } : undefined}
|
||||
itemType={itemType === 'all' ? undefined : itemType}
|
||||
limit={limit}
|
||||
embedded={false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user