Skip to content

Commit dc1f389

Browse files
committed
Developed a static UI in the frontend
1 parent 94c2179 commit dc1f389

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+10266
-171
lines changed
Lines changed: 354 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,354 @@
1+
import { useState } from 'react';
2+
import { Button } from './ui/button';
3+
import { Textarea } from './ui/textarea';
4+
import { Badge } from './ui/badge';
5+
import { Card, CardContent, CardHeader, CardTitle } from './ui/card';
6+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
7+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from './ui/table';
8+
import { AlertTriangle, Trash2, BarChart3, Bot } from 'lucide-react';
9+
import { Ticket } from './ReporterDashboard';
10+
11+
export function AdminPanel() {
12+
const [selectedTicket, setSelectedTicket] = useState<Ticket | null>(null);
13+
const [reviewComment, setReviewComment] = useState('');
14+
15+
const [tickets, setTickets] = useState<Ticket[]>([
16+
{
17+
id: 'TCK-001',
18+
title: 'Misleading Health Claims in Advertisement',
19+
description: 'Found an ad making unverified health claims about supplements.',
20+
status: 'Resolved',
21+
lastUpdated: '2024-01-15',
22+
timeline: [
23+
{ status: 'Received', timestamp: '2024-01-10 09:30', message: 'Report submitted successfully', type: 'system' },
24+
{ status: 'In Review', timestamp: '2024-01-12 14:20', message: 'Report assigned to reviewer team', type: 'system' },
25+
{ status: 'Resolved', timestamp: '2024-01-15 16:45', message: 'Advertisement removed and advertiser warned', type: 'human' }
26+
]
27+
},
28+
{
29+
id: 'TCK-002',
30+
title: 'Inappropriate Content Targeting',
31+
description: 'Adult content being shown to minors in gaming app.',
32+
status: 'In Review',
33+
lastUpdated: '2024-01-14',
34+
timeline: [
35+
{ status: 'Received', timestamp: '2024-01-13 16:20', message: 'Report submitted successfully', type: 'system' },
36+
{ status: 'In Review', timestamp: '2024-01-14 10:00', message: 'Report under active investigation', type: 'system' }
37+
]
38+
},
39+
{
40+
id: 'TCK-003',
41+
title: 'Fraudulent Investment Scheme',
42+
description: 'Suspicious investment ad promising unrealistic returns.',
43+
status: 'Received',
44+
lastUpdated: '2024-01-14',
45+
timeline: [
46+
{ status: 'Received', timestamp: '2024-01-14 13:45', message: 'Report submitted successfully', type: 'system' }
47+
]
48+
},
49+
{
50+
id: 'TCK-004',
51+
title: 'Fake Product Reviews',
52+
description: 'Multiple suspicious 5-star reviews posted on the same day.',
53+
status: 'Received',
54+
lastUpdated: '2024-01-13',
55+
timeline: [
56+
{ status: 'Received', timestamp: '2024-01-13 11:20', message: 'Report submitted successfully', type: 'system' }
57+
]
58+
}
59+
]);
60+
61+
const getStatusColor = (status: string) => {
62+
switch (status) {
63+
case 'Resolved': return 'bg-green-100 text-green-800 border-green-200';
64+
case 'In Review': return 'bg-orange-100 text-orange-800 border-orange-200';
65+
case 'Received': return 'bg-gray-100 text-gray-800 border-gray-200';
66+
default: return 'bg-gray-100 text-gray-800 border-gray-200';
67+
}
68+
};
69+
70+
const updateTicketStatus = (status: string) => {
71+
if (!selectedTicket) return;
72+
73+
const updatedTickets = tickets.map(ticket => {
74+
if (ticket.id === selectedTicket.id) {
75+
const newTimeline = [...(ticket.timeline || [])];
76+
newTimeline.push({
77+
status,
78+
timestamp: new Date().toISOString().replace('T', ' ').slice(0, 16),
79+
message: `Status updated to ${status}`,
80+
type: 'system'
81+
});
82+
83+
return {
84+
...ticket,
85+
status: status as 'Received' | 'In Review' | 'Resolved',
86+
lastUpdated: new Date().toISOString().split('T')[0],
87+
timeline: newTimeline
88+
};
89+
}
90+
return ticket;
91+
});
92+
93+
setTickets(updatedTickets);
94+
setSelectedTicket(updatedTickets.find(t => t.id === selectedTicket.id) || null);
95+
};
96+
97+
const addReviewComment = () => {
98+
if (!selectedTicket || !reviewComment.trim()) return;
99+
100+
const updatedTickets = tickets.map(ticket => {
101+
if (ticket.id === selectedTicket.id) {
102+
const newTimeline = [...(ticket.timeline || [])];
103+
newTimeline.push({
104+
status: ticket.status,
105+
timestamp: new Date().toISOString().replace('T', ' ').slice(0, 16),
106+
message: reviewComment,
107+
type: 'human'
108+
});
109+
110+
return {
111+
...ticket,
112+
timeline: newTimeline
113+
};
114+
}
115+
return ticket;
116+
});
117+
118+
setTickets(updatedTickets);
119+
setSelectedTicket(updatedTickets.find(t => t.id === selectedTicket.id) || null);
120+
setReviewComment('');
121+
};
122+
123+
const generateAutoFeedback = () => {
124+
const autoMessages = [
125+
"Automated review completed. No policy violations detected.",
126+
"Content flagged for manual review due to sensitive keywords.",
127+
"Similar reports consolidated. Pattern analysis in progress.",
128+
"Advertiser notification sent. Awaiting response within 48 hours."
129+
];
130+
131+
const randomMessage = autoMessages[Math.floor(Math.random() * autoMessages.length)];
132+
setReviewComment(randomMessage);
133+
};
134+
135+
const triggerImpactEvent = (impactType: string) => {
136+
if (!selectedTicket) return;
137+
138+
// In a real app, this would trigger an actual impact event
139+
// For now, we'll just add it to the timeline
140+
const impactMessages = {
141+
'ad-removed': 'Advertisement removed from platform',
142+
'advertiser-warned': 'Advertiser received formal warning',
143+
'account-suspended': 'Advertiser account temporarily suspended'
144+
};
145+
146+
const message = impactMessages[impactType as keyof typeof impactMessages] || 'Impact action triggered';
147+
148+
const updatedTickets = tickets.map(ticket => {
149+
if (ticket.id === selectedTicket.id) {
150+
const newTimeline = [...(ticket.timeline || [])];
151+
newTimeline.push({
152+
status: ticket.status,
153+
timestamp: new Date().toISOString().replace('T', ' ').slice(0, 16),
154+
message,
155+
type: 'system'
156+
});
157+
158+
return {
159+
...ticket,
160+
timeline: newTimeline
161+
};
162+
}
163+
return ticket;
164+
});
165+
166+
setTickets(updatedTickets);
167+
setSelectedTicket(updatedTickets.find(t => t.id === selectedTicket.id) || null);
168+
};
169+
170+
const pendingTickets = tickets.filter(t => t.status !== 'Resolved');
171+
172+
return (
173+
<div className="flex-1">
174+
{/* Header */}
175+
<header className="border-b bg-background p-6">
176+
<h1 className="text-2xl">Admin Panel</h1>
177+
<p className="text-muted-foreground">Review and manage reported content</p>
178+
</header>
179+
180+
<div className="flex h-full">
181+
{/* Left Panel - Tickets List */}
182+
<div className="w-1/2 p-6 border-r">
183+
<Card>
184+
<CardHeader>
185+
<CardTitle>Tickets Awaiting Review ({pendingTickets.length})</CardTitle>
186+
</CardHeader>
187+
<CardContent>
188+
<Table>
189+
<TableHeader>
190+
<TableRow>
191+
<TableHead>ID</TableHead>
192+
<TableHead>Title</TableHead>
193+
<TableHead>Status</TableHead>
194+
<TableHead>Updated</TableHead>
195+
</TableRow>
196+
</TableHeader>
197+
<TableBody>
198+
{pendingTickets.map((ticket) => (
199+
<TableRow
200+
key={ticket.id}
201+
className={`cursor-pointer hover:bg-muted/50 ${
202+
selectedTicket?.id === ticket.id ? 'bg-muted' : ''
203+
}`}
204+
onClick={() => setSelectedTicket(ticket)}
205+
>
206+
<TableCell className="font-mono text-sm">{ticket.id}</TableCell>
207+
<TableCell className="max-w-xs truncate">{ticket.title}</TableCell>
208+
<TableCell>
209+
<Badge className={getStatusColor(ticket.status)}>
210+
{ticket.status}
211+
</Badge>
212+
</TableCell>
213+
<TableCell className="text-muted-foreground text-sm">{ticket.lastUpdated}</TableCell>
214+
</TableRow>
215+
))}
216+
</TableBody>
217+
</Table>
218+
</CardContent>
219+
</Card>
220+
</div>
221+
222+
{/* Right Panel - Ticket Details & Actions */}
223+
<div className="w-1/2 p-6">
224+
{selectedTicket ? (
225+
<div className="space-y-6">
226+
{/* Ticket Info */}
227+
<Card>
228+
<CardHeader>
229+
<CardTitle className="flex items-center justify-between">
230+
{selectedTicket.title}
231+
<Badge className={getStatusColor(selectedTicket.status)}>
232+
{selectedTicket.status}
233+
</Badge>
234+
</CardTitle>
235+
</CardHeader>
236+
<CardContent>
237+
<p className="text-sm text-muted-foreground mb-2">ID: {selectedTicket.id}</p>
238+
<p>{selectedTicket.description}</p>
239+
</CardContent>
240+
</Card>
241+
242+
{/* Status Update */}
243+
<Card>
244+
<CardHeader>
245+
<CardTitle>Update Status</CardTitle>
246+
</CardHeader>
247+
<CardContent className="space-y-4">
248+
<Select onValueChange={updateTicketStatus}>
249+
<SelectTrigger>
250+
<SelectValue placeholder="Select new status" />
251+
</SelectTrigger>
252+
<SelectContent>
253+
<SelectItem value="Received">Received</SelectItem>
254+
<SelectItem value="In Review">In Review</SelectItem>
255+
<SelectItem value="Resolved">Resolved</SelectItem>
256+
</SelectContent>
257+
</Select>
258+
</CardContent>
259+
</Card>
260+
261+
{/* Review Comment */}
262+
<Card>
263+
<CardHeader>
264+
<CardTitle>Add Review Comment</CardTitle>
265+
</CardHeader>
266+
<CardContent className="space-y-4">
267+
<Textarea
268+
value={reviewComment}
269+
onChange={(e) => setReviewComment(e.target.value)}
270+
placeholder="Add feedback or notes about this report"
271+
rows={3}
272+
/>
273+
<div className="flex gap-2">
274+
<Button onClick={addReviewComment} disabled={!reviewComment.trim()}>
275+
Add Comment
276+
</Button>
277+
<Button variant="outline" onClick={generateAutoFeedback}>
278+
<Bot className="mr-2 h-4 w-4" />
279+
Generate Auto Feedback
280+
</Button>
281+
</div>
282+
</CardContent>
283+
</Card>
284+
285+
{/* Impact Actions */}
286+
<Card>
287+
<CardHeader>
288+
<CardTitle>Trigger Impact Event</CardTitle>
289+
</CardHeader>
290+
<CardContent>
291+
<div className="grid grid-cols-2 gap-2">
292+
<Button
293+
variant="outline"
294+
onClick={() => triggerImpactEvent('advertiser-warned')}
295+
className="flex items-center justify-center"
296+
>
297+
<AlertTriangle className="mr-2 h-4 w-4" />
298+
Warn Advertiser
299+
</Button>
300+
<Button
301+
variant="outline"
302+
onClick={() => triggerImpactEvent('ad-removed')}
303+
className="flex items-center justify-center"
304+
>
305+
<Trash2 className="mr-2 h-4 w-4" />
306+
Remove Ad
307+
</Button>
308+
<Button
309+
variant="outline"
310+
onClick={() => triggerImpactEvent('account-suspended')}
311+
className="flex items-center justify-center col-span-2"
312+
>
313+
<BarChart3 className="mr-2 h-4 w-4" />
314+
Suspend Account
315+
</Button>
316+
</div>
317+
</CardContent>
318+
</Card>
319+
320+
{/* Timeline */}
321+
{selectedTicket.timeline && selectedTicket.timeline.length > 0 && (
322+
<Card>
323+
<CardHeader>
324+
<CardTitle>Timeline</CardTitle>
325+
</CardHeader>
326+
<CardContent>
327+
<div className="space-y-3">
328+
{selectedTicket.timeline.map((event, index) => (
329+
<div key={index} className="flex gap-3 p-3 bg-muted/30 rounded-lg">
330+
<div className="flex-1">
331+
<p className="text-sm">{event.message}</p>
332+
<p className="text-xs text-muted-foreground mt-1">
333+
{event.timestamp}{event.type === 'system' ? 'System' : 'Human'}
334+
</p>
335+
</div>
336+
</div>
337+
))}
338+
</div>
339+
</CardContent>
340+
</Card>
341+
)}
342+
</div>
343+
) : (
344+
<Card>
345+
<CardContent className="flex items-center justify-center h-64">
346+
<p className="text-muted-foreground">Select a ticket to view details and actions</p>
347+
</CardContent>
348+
</Card>
349+
)}
350+
</div>
351+
</div>
352+
</div>
353+
);
354+
}

0 commit comments

Comments
 (0)