mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-26 09:31:08 -05:00
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.
136 lines
5.1 KiB
TypeScript
136 lines
5.1 KiB
TypeScript
/**
|
|
* 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>
|
|
);
|
|
} |