Add Admin Reports page #2
This commit is contained in:
82
apps/hub/app/(app)/admin/report/[id]/page.tsx
Normal file
82
apps/hub/app/(app)/admin/report/[id]/page.tsx
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
"use client";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Eye } from "lucide-react";
|
||||||
|
import { fetchReportDetails, handleMarkAsResolved } from "../actions";
|
||||||
|
|
||||||
|
export default function ReportDetailsPage({
|
||||||
|
params: paramsPromise,
|
||||||
|
}: {
|
||||||
|
params: Promise<{ id: number }>;
|
||||||
|
}) {
|
||||||
|
const [params, setParams] = useState<{ id: number } | null>(null);
|
||||||
|
const [report, setReport] = useState<any>(null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function unwrapParams() {
|
||||||
|
const resolvedParams = await paramsPromise;
|
||||||
|
setParams(resolvedParams);
|
||||||
|
}
|
||||||
|
unwrapParams();
|
||||||
|
}, [paramsPromise]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!params) return;
|
||||||
|
|
||||||
|
async function loadReport() {
|
||||||
|
if (!params) return;
|
||||||
|
const fetchedReport = await fetchReportDetails(parseInt(params.id));
|
||||||
|
setReport(fetchedReport);
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
loadReport();
|
||||||
|
}, [params]);
|
||||||
|
|
||||||
|
if (!params || loading) return <div>Loading...</div>;
|
||||||
|
if (!report) return <div>Report not found</div>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-4">
|
||||||
|
<h1 className="text-2xl font-bold mb-4 flex items-center gap-2">
|
||||||
|
<Eye className="w-6 h-6" /> Report Details
|
||||||
|
</h1>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
<b>Sender:</b> {report.sender.firstname} {report.sender.lastname} (
|
||||||
|
{report.sender.publicId})
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>Reported:</b> {report.reported.firstname}{" "}
|
||||||
|
{report.reported.lastname} ({report.reported.publicId})
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>Timestamp:</b> {new Date(report.timestamp).toLocaleString()}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
<b>Message:</b>
|
||||||
|
</p>
|
||||||
|
<p>{report.text}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 flex gap-2">
|
||||||
|
<button
|
||||||
|
className="btn btn-success btn-outline"
|
||||||
|
onClick={async () => {
|
||||||
|
await handleMarkAsResolved(report.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Erledigen
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn btn-primary btn-outline"
|
||||||
|
onClick={() => window.history.back()}
|
||||||
|
>
|
||||||
|
Zurück
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
36
apps/hub/app/(app)/admin/report/actions.ts
Normal file
36
apps/hub/app/(app)/admin/report/actions.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
"use server";
|
||||||
|
import { prisma } from "@repo/db";
|
||||||
|
|
||||||
|
export const markAsResolved = async (id: number) => {
|
||||||
|
await prisma.reportMessage.update({
|
||||||
|
where: { id: id },
|
||||||
|
data: { erledigt: true },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// New function to handle marking a report as resolved
|
||||||
|
export const handleMarkAsResolved = async (id: number) => {
|
||||||
|
try {
|
||||||
|
await markAsResolved(id);
|
||||||
|
return { success: true };
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error marking report as resolved:", error);
|
||||||
|
return { success: false, error: error };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getReports = async () => {
|
||||||
|
return prisma.reportMessage.findMany({
|
||||||
|
include: {
|
||||||
|
sender: true,
|
||||||
|
reported: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchReportDetails = async (id: number) => {
|
||||||
|
return prisma.reportMessage.findUnique({
|
||||||
|
where: { id },
|
||||||
|
include: { sender: true, reported: true },
|
||||||
|
});
|
||||||
|
};
|
||||||
24
apps/hub/app/(app)/admin/report/layout.tsx
Normal file
24
apps/hub/app/(app)/admin/report/layout.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { prisma } from "@repo/db";
|
||||||
|
import { Error } from "_components/Error";
|
||||||
|
import { getServerSession } from "api/auth/[...nextauth]/auth";
|
||||||
|
|
||||||
|
export default async function ReportLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
const session = await getServerSession();
|
||||||
|
|
||||||
|
if (!session) return <Error title="Nicht eingeloggt" statusCode={401} />;
|
||||||
|
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: {
|
||||||
|
id: session.user.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user?.permissions.includes("ADMIN_EVENT"))
|
||||||
|
return <Error title="Keine Berechtigung" statusCode={403} />;
|
||||||
|
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
122
apps/hub/app/(app)/admin/report/page.tsx
Normal file
122
apps/hub/app/(app)/admin/report/page.tsx
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
"use client";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Check, Eye, X } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { getReports, handleMarkAsResolved } from "./actions";
|
||||||
|
|
||||||
|
export default function ReportPage() {
|
||||||
|
const [reports, setReports] = useState<
|
||||||
|
{
|
||||||
|
id: number;
|
||||||
|
timestamp: string;
|
||||||
|
erledigt: boolean;
|
||||||
|
sender: {
|
||||||
|
id: string;
|
||||||
|
firstname: string;
|
||||||
|
lastname: string;
|
||||||
|
publicId: string;
|
||||||
|
};
|
||||||
|
reported: {
|
||||||
|
id: string;
|
||||||
|
firstname: string;
|
||||||
|
lastname: string;
|
||||||
|
publicId: string;
|
||||||
|
};
|
||||||
|
}[]
|
||||||
|
>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchReports = async () => {
|
||||||
|
const reps = await getReports();
|
||||||
|
const transformedReports = reps.map((report) => ({
|
||||||
|
id: report.id,
|
||||||
|
timestamp: report.timestamp.toISOString(),
|
||||||
|
erledigt: report.erledigt,
|
||||||
|
sender: {
|
||||||
|
id: report.sender.id,
|
||||||
|
firstname: report.sender.firstname,
|
||||||
|
lastname: report.sender.lastname,
|
||||||
|
publicId: report.sender.publicId,
|
||||||
|
},
|
||||||
|
reported: {
|
||||||
|
id: report.reported.id,
|
||||||
|
firstname: report.reported.firstname,
|
||||||
|
lastname: report.reported.lastname,
|
||||||
|
publicId: report.reported.publicId,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
setReports(transformedReports);
|
||||||
|
};
|
||||||
|
fetchReports();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex items-center gap-2 mb-4">
|
||||||
|
<Eye className="w-5 h-5" />{" "}
|
||||||
|
<span className="text-lg font-bold">Reports</span>
|
||||||
|
</div>
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<table className="table table-zebra w-full">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Erledigt</th>
|
||||||
|
<th>Sender</th>
|
||||||
|
<th>Reported</th>
|
||||||
|
<th>Time</th>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{reports.map((report) => (
|
||||||
|
<tr key={report.id}>
|
||||||
|
<td className="text-center">
|
||||||
|
{report.erledigt ? (
|
||||||
|
<Check className="text-green-500 w-5 h-5" />
|
||||||
|
) : (
|
||||||
|
<X className="text-red-500 w-5 h-5" />
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td>{`${report.sender.firstname} ${report.sender.lastname} (${report.sender.publicId})`}</td>
|
||||||
|
<td>{`${report.reported.firstname} ${report.reported.lastname} (${report.reported.publicId})`}</td>
|
||||||
|
<td>{new Date(report.timestamp).toLocaleString()}</td>
|
||||||
|
<td>{report.id}</td>
|
||||||
|
<td>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Link href={`/admin/report/${report.id}`}>
|
||||||
|
<button className="btn btn-sm btn-outline btn-info flex items-center gap-2">
|
||||||
|
<Eye className="w-4 h-4" /> Anzeigen
|
||||||
|
</button>
|
||||||
|
</Link>
|
||||||
|
{!report.erledigt && (
|
||||||
|
<button
|
||||||
|
className="btn btn-sm btn-outline btn-success flex items-center gap-2"
|
||||||
|
onClick={async () => {
|
||||||
|
const result = await handleMarkAsResolved(report.id);
|
||||||
|
if (result.success) {
|
||||||
|
setReports((prevReports) =>
|
||||||
|
prevReports.map((r) =>
|
||||||
|
r.id === report.id
|
||||||
|
? { ...r, erledigt: true }
|
||||||
|
: r,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
alert("Error: " + result.error);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Check className="w-4 h-4" /> Erledigen
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -57,6 +57,9 @@ export const VerticalNav = () => {
|
|||||||
<li>
|
<li>
|
||||||
<Link href="/admin/message">Service Nachrichten</Link>
|
<Link href="/admin/message">Service Nachrichten</Link>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link href="/admin/report">Reports</Link>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
model ReportMessage {
|
model ReportMessage {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
text String
|
text String
|
||||||
senderId String
|
senderId String
|
||||||
reportedId String
|
reportedId String
|
||||||
timestamp DateTime @default(now())
|
timestamp DateTime @default(now())
|
||||||
reportedName String
|
erledigt Boolean @default(false)
|
||||||
senderName String
|
|
||||||
|
|
||||||
// relations:
|
// relations:
|
||||||
sender User @relation("SentReports", fields: [senderId], references: [id])
|
sender User @relation("SentReports", fields: [senderId], references: [id])
|
||||||
|
|||||||
Reference in New Issue
Block a user