From 248c3e94abb8a12c6eabbf08610a450c450404fb Mon Sep 17 00:00:00 2001 From: Lukas Davidovic Date: Tue, 2 Apr 2024 16:43:18 +0200 Subject: [PATCH] reports admin panel --- pages/admin/reports.tsx | 190 +++++++++++++++++++++++++++++++ pages/api/admin/reports/[id].ts | 49 ++++++++ pages/api/admin/reports/index.ts | 55 +++++++++ 3 files changed, 294 insertions(+) create mode 100644 pages/admin/reports.tsx create mode 100644 pages/api/admin/reports/[id].ts create mode 100644 pages/api/admin/reports/index.ts diff --git a/pages/admin/reports.tsx b/pages/admin/reports.tsx new file mode 100644 index 0000000..d191eeb --- /dev/null +++ b/pages/admin/reports.tsx @@ -0,0 +1,190 @@ +import { useState, useEffect, useCallback } from 'react' +import type { NextPage } from 'next' +import Head from 'next/head' +import Link from 'next/link' + +interface Report { + id: number + source_url: string + source_domain: string + reporter_email?: string + reporter_name?: string + category_suggestions: string[] + description: string + priority: string + status: string + created_at: string +} + +const ReportsManagement: NextPage = () => { + const [reports, setReports] = useState([]) + const [loading, setLoading] = useState(true) + const [filter, setFilter] = useState('pending') + + const fetchReports = useCallback(async () => { + try { + const response = await fetch(`/api/admin/reports?status=${filter}`) + if (response.ok) { + const data = await response.json() + setReports(data) + } + } catch (error) { + console.error('Failed to fetch reports:', error) + } finally { + setLoading(false) + } + }, [filter]) + + useEffect(() => { + fetchReports() + }, [fetchReports]) + + const updateReport = async (id: number, status: string, notes?: string) => { + try { + const response = await fetch(`/api/admin/reports/${id}`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + status, + admin_notes: notes, + }), + }) + + if (response.ok) { + fetchReports() + } + } catch (error) { + console.error('Failed to update report:', error) + } + } + + const getPriorityColor = (priority: string) => { + switch (priority) { + case 'urgent': return '#dc2626' + case 'high': return '#ea580c' + case 'medium': return '#d97706' + default: return '#6b7280' + } + } + + return ( +
+ + Správa hlásení - Infohliadka + + +
+

Správa hlásení

+ +
+ + +
+ + {loading ? ( +
Loading...
+ ) : ( +
+ + + + + + + + + + + + + {reports.map((report) => ( + + + + + + + + + ))} + +
DoménaKategóriePrioritaStatusDátumAkcie
+ + {report.source_domain} + +
+ {report.reporter_name || 'Anonymous'} +
+
+ {report.category_suggestions.join(', ')} + + + {report.priority} + + {report.status} + {new Date(report.created_at).toLocaleDateString('sk-SK')} + + {report.status === 'pending' && ( +
+ + +
+ )} +
+
+ )} + +
+ + ← Späť na dashboard + +
+
+
+ ) +} + +export default ReportsManagement \ No newline at end of file diff --git a/pages/api/admin/reports/[id].ts b/pages/api/admin/reports/[id].ts new file mode 100644 index 0000000..3d86900 --- /dev/null +++ b/pages/api/admin/reports/[id].ts @@ -0,0 +1,49 @@ +import type { NextApiRequest, NextApiResponse } from 'next' +import sqlite3 from 'sqlite3' +import path from 'path' + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method !== 'PATCH') { + return res.status(405).json({ error: 'Method not allowed' }) + } + + const { id } = req.query + const { status, admin_notes } = req.body + + if (!id || !status) { + return res.status(400).json({ error: 'ID and status are required' }) + } + + const dbPath = path.join(process.cwd(), 'database', 'antihoax.db') + const db = new sqlite3.Database(dbPath) + + try { + await new Promise((resolve, reject) => { + const query = ` + UPDATE reports + SET status = ?, admin_notes = ?, processed_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP + WHERE id = ? + ` + + db.run( + query, + [status, admin_notes || null, id], + function(err) { + if (err) reject(err) + else resolve() + } + ) + }) + + return res.status(200).json({ success: true }) + + } catch (error) { + console.error('Database error:', error) + return res.status(500).json({ error: 'Internal server error' }) + } finally { + db.close() + } +} \ No newline at end of file diff --git a/pages/api/admin/reports/index.ts b/pages/api/admin/reports/index.ts new file mode 100644 index 0000000..e5ced22 --- /dev/null +++ b/pages/api/admin/reports/index.ts @@ -0,0 +1,55 @@ +import type { NextApiRequest, NextApiResponse } from 'next' +import sqlite3 from 'sqlite3' +import path from 'path' + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method !== 'GET') { + return res.status(405).json({ error: 'Method not allowed' }) + } + + const { status = 'pending', page = '1', limit = '20' } = req.query + + const dbPath = path.join(process.cwd(), 'database', 'antihoax.db') + const db = new sqlite3.Database(dbPath) + + try { + const reports = await new Promise((resolve, reject) => { + const offset = (parseInt(page as string) - 1) * parseInt(limit as string) + + db.all( + `SELECT *, + CASE + WHEN category_suggestions IS NOT NULL + THEN json_extract(category_suggestions, '$') + ELSE '[]' + END as category_suggestions + FROM reports + WHERE status = ? + ORDER BY created_at DESC + LIMIT ? OFFSET ?`, + [status, parseInt(limit as string), offset], + (err, rows: any[]) => { + if (err) reject(err) + else { + const processedRows = rows.map(row => ({ + ...row, + category_suggestions: row.category_suggestions ? JSON.parse(row.category_suggestions) : [] + })) + resolve(processedRows) + } + } + ) + }) + + return res.status(200).json(reports) + + } catch (error) { + console.error('Database error:', error) + return res.status(500).json({ error: 'Internal server error' }) + } finally { + db.close() + } +} \ No newline at end of file