Changelog-Seite, option zum verstecken von Einträgen auf dieser
This commit is contained in:
@@ -23,6 +23,7 @@ export const ChangelogForm = ({ changelog }: { changelog?: Changelog }) => {
|
||||
title: changelog?.title || "",
|
||||
text: changelog?.text || "",
|
||||
previewImage: changelog?.previewImage || "", // Changed to accept a URL as a string
|
||||
showOnChangelogPage: changelog?.showOnChangelogPage || true,
|
||||
},
|
||||
});
|
||||
const [skipUserUpdate, setSkipUserUpdate] = useState(false);
|
||||
@@ -84,6 +85,7 @@ export const ChangelogForm = ({ changelog }: { changelog?: Changelog }) => {
|
||||
placeholder="Titel (vX.X.X)"
|
||||
className="input-sm"
|
||||
/>
|
||||
|
||||
<Input
|
||||
form={form}
|
||||
label="Bild-URL"
|
||||
@@ -146,6 +148,16 @@ export const ChangelogForm = ({ changelog }: { changelog?: Changelog }) => {
|
||||
</span>
|
||||
</label>
|
||||
)}
|
||||
<label className="label mx-6 mt-6 w-full cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
className={cn("toggle")}
|
||||
{...form.register("showOnChangelogPage", {})}
|
||||
/>
|
||||
<span className={cn("label-text w-full text-left")}>
|
||||
Auf der Changelog-Seite anzeigen
|
||||
</span>
|
||||
</label>
|
||||
<div className="card-body">
|
||||
<div className="flex w-full gap-4">
|
||||
<Button
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
"use client";
|
||||
import { DatabaseBackupIcon } from "lucide-react";
|
||||
import { Check, Cross, DatabaseBackupIcon } from "lucide-react";
|
||||
import { PaginatedTable } from "../../../_components/PaginatedTable";
|
||||
import Link from "next/link";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
import { Keyword, Prisma } from "@repo/db";
|
||||
import { Changelog, Keyword, Prisma } from "@repo/db";
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<>
|
||||
<PaginatedTable
|
||||
stickyHeaders
|
||||
initialOrderBy={[{ id: "title", desc: true }]}
|
||||
initialOrderBy={[{ id: "createdAt", desc: true }]}
|
||||
prismaModel="changelog"
|
||||
showSearch
|
||||
getFilter={(search) =>
|
||||
({
|
||||
OR: [
|
||||
{ title: { contains: search, mode: "insensitive" } },
|
||||
{ description: { contains: search, mode: "insensitive" } },
|
||||
{ text: { contains: search, mode: "insensitive" } },
|
||||
],
|
||||
}) as Prisma.ChangelogWhereInput
|
||||
}
|
||||
@@ -27,6 +27,16 @@ export default () => {
|
||||
header: "Title",
|
||||
accessorKey: "title",
|
||||
},
|
||||
{
|
||||
header: "Auf Changelog Seite anzeigen",
|
||||
accessorKey: "showOnChangelogPage",
|
||||
cell: ({ row }) => (row.original.showOnChangelogPage ? <Check /> : <Cross />),
|
||||
},
|
||||
{
|
||||
header: "Erstellt am",
|
||||
accessorKey: "createdAt",
|
||||
cell: ({ row }) => new Date(row.original.createdAt).toLocaleDateString(),
|
||||
},
|
||||
{
|
||||
header: "Aktionen",
|
||||
cell: ({ row }) => (
|
||||
@@ -37,7 +47,7 @@ export default () => {
|
||||
</div>
|
||||
),
|
||||
},
|
||||
] as ColumnDef<Keyword>[]
|
||||
] as ColumnDef<Changelog>[]
|
||||
}
|
||||
leftOfSearch={
|
||||
<span className="flex items-center gap-2">
|
||||
|
||||
68
apps/hub/app/(app)/changelog/_components/Timeline.tsx
Normal file
68
apps/hub/app/(app)/changelog/_components/Timeline.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
"use client";
|
||||
|
||||
import MDEditor from "@uiw/react-md-editor";
|
||||
import Image from "next/image";
|
||||
|
||||
export type TimelineEntry = {
|
||||
id: number;
|
||||
title: string;
|
||||
text: string;
|
||||
previewImage?: string | null;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
const formatReleaseDate = (value: string) =>
|
||||
new Intl.DateTimeFormat("en-GB", {
|
||||
day: "2-digit",
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
}).format(new Date(value));
|
||||
|
||||
export const ChangelogTimeline = ({ entries }: { entries: TimelineEntry[] }) => {
|
||||
if (!entries.length)
|
||||
return <p className="text-base-content/70">Es sind noch keine Changelog-Einträge vorhanden.</p>;
|
||||
|
||||
return (
|
||||
<div className="relative mt-6 pl-6">
|
||||
<div className="bg-base-300 absolute bottom-0 left-2 top-0 w-px" aria-hidden />
|
||||
<div className="space-y-8">
|
||||
{entries.map((entry, idx) => (
|
||||
<article key={entry.id ?? `${entry.title}-${idx}`} className="relative pl-4">
|
||||
<div className="bg-primary ring-base-100 absolute -left-[9px] top-3 h-4 w-4 rounded-full ring-4" />
|
||||
<div className="bg-base-200/80 rounded-xl p-5 shadow">
|
||||
<div className="flex flex-col gap-1 text-left md:flex-row md:justify-between">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold leading-tight">{entry.title}</h3>
|
||||
<p className="text-base-content/60 text-sm">
|
||||
Release Date: {formatReleaseDate(entry.createdAt)}
|
||||
</p>
|
||||
</div>
|
||||
{entry.previewImage && (
|
||||
<div className="absolute right-5 top-5 md:pl-4">
|
||||
<Image
|
||||
src={entry.previewImage}
|
||||
width={300}
|
||||
height={300}
|
||||
alt={`${entry.title} preview`}
|
||||
className="mt-3 max-w-[300px] rounded-lg object-cover md:mt-0"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="text-base-content/80 text-left" data-color-mode="dark">
|
||||
<MDEditor.Markdown
|
||||
source={entry.text}
|
||||
style={{
|
||||
backgroundColor: "transparent",
|
||||
fontSize: "0.95rem",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
26
apps/hub/app/(app)/changelog/page.tsx
Normal file
26
apps/hub/app/(app)/changelog/page.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { prisma } from "@repo/db";
|
||||
import { ChangelogTimeline } from "./_components/Timeline";
|
||||
import { ActivityLogIcon } from "@radix-ui/react-icons";
|
||||
|
||||
export default async function Page() {
|
||||
const changelog = await prisma.changelog.findMany({
|
||||
where: { showOnChangelogPage: true },
|
||||
orderBy: { createdAt: "desc" },
|
||||
});
|
||||
|
||||
const entries = changelog.map((entry) => ({
|
||||
...entry,
|
||||
createdAt: entry.createdAt.toISOString(),
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-full px-4">
|
||||
<p className="flex items-center gap-2 text-left text-2xl font-semibold">
|
||||
<ActivityLogIcon className="h-5 w-5" /> Changelog
|
||||
</p>
|
||||
</div>
|
||||
<ChangelogTimeline entries={entries} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -6,13 +6,15 @@ import {
|
||||
RocketIcon,
|
||||
ReaderIcon,
|
||||
DownloadIcon,
|
||||
UpdateIcon,
|
||||
ActivityLogIcon,
|
||||
} from "@radix-ui/react-icons";
|
||||
import Link from "next/link";
|
||||
import { WarningAlert } from "./ui/PageAlert";
|
||||
import { getServerSession } from "api/auth/[...nextauth]/auth";
|
||||
import { Error } from "./Error";
|
||||
import Image from "next/image";
|
||||
import { Plane, Radar, Workflow } from "lucide-react";
|
||||
import { Loader, Plane, Radar, Workflow } from "lucide-react";
|
||||
import { BookingButton } from "./BookingButton";
|
||||
|
||||
export const VerticalNav = async () => {
|
||||
@@ -22,93 +24,101 @@ export const VerticalNav = async () => {
|
||||
return p.startsWith("ADMIN");
|
||||
});
|
||||
return (
|
||||
<ul className="menu bg-base-300 w-64 flex-nowrap rounded-lg p-3 font-semibold shadow-md">
|
||||
<li>
|
||||
<Link href="/">
|
||||
<HomeIcon /> Dashboard
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/events">
|
||||
<RocketIcon />
|
||||
Events & Kurse
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/logbook">
|
||||
<ReaderIcon />
|
||||
Einsatzhistorie
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/settings">
|
||||
<GearIcon />
|
||||
Einstellungen
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/resources">
|
||||
<DownloadIcon />
|
||||
Downloads / Links
|
||||
</Link>
|
||||
</li>
|
||||
{viewAdminMenu && (
|
||||
<ul className="menu bg-base-300 flex w-64 flex-nowrap justify-between rounded-lg p-3 font-semibold shadow-md">
|
||||
<div className="border-none">
|
||||
<li>
|
||||
<details open>
|
||||
<summary>
|
||||
<LockClosedIcon />
|
||||
Admin
|
||||
</summary>
|
||||
<ul>
|
||||
{session.user.permissions.includes("ADMIN_USER") && (
|
||||
<li>
|
||||
<Link href="/admin/user">Benutzer</Link>
|
||||
</li>
|
||||
)}
|
||||
{session.user.permissions.includes("ADMIN_STATION") && (
|
||||
<li>
|
||||
<Link href="/admin/station">Stationen</Link>
|
||||
</li>
|
||||
)}
|
||||
{session.user.permissions.includes("ADMIN_KEYWORD") && (
|
||||
<li>
|
||||
<Link href="/admin/keyword">Stichworte</Link>
|
||||
</li>
|
||||
)}
|
||||
{session.user.permissions.includes("ADMIN_HELIPORT") && (
|
||||
<li>
|
||||
<Link href="/admin/heliport">Heliports</Link>
|
||||
</li>
|
||||
)}
|
||||
{session.user.permissions.includes("ADMIN_EVENT") && (
|
||||
<li>
|
||||
<Link href="/admin/event">Events</Link>
|
||||
</li>
|
||||
)}
|
||||
{session.user.permissions.includes("ADMIN_MESSAGE") && (
|
||||
<li>
|
||||
<Link href="/admin/config">Config</Link>
|
||||
</li>
|
||||
)}
|
||||
{session.user.permissions.includes("ADMIN_USER") && (
|
||||
<li>
|
||||
<Link href="/admin/report">Reports</Link>
|
||||
</li>
|
||||
)}
|
||||
{session.user.permissions.includes("ADMIN_USER") && (
|
||||
<li>
|
||||
<Link href="/admin/penalty">Audit-Log</Link>
|
||||
</li>
|
||||
)}
|
||||
{session.user.permissions.includes("ADMIN_CHANGELOG") && (
|
||||
<li>
|
||||
<Link href="/admin/changelog">Changelog</Link>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</details>
|
||||
<Link href="/">
|
||||
<HomeIcon /> Dashboard
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
<li>
|
||||
<Link href="/events">
|
||||
<RocketIcon />
|
||||
Events & Kurse
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/logbook">
|
||||
<ReaderIcon />
|
||||
Einsatzhistorie
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/settings">
|
||||
<GearIcon />
|
||||
Einstellungen
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/resources">
|
||||
<DownloadIcon />
|
||||
Downloads / Links
|
||||
</Link>
|
||||
</li>
|
||||
{viewAdminMenu && (
|
||||
<li>
|
||||
<details open>
|
||||
<summary>
|
||||
<LockClosedIcon />
|
||||
Admin
|
||||
</summary>
|
||||
<ul>
|
||||
{session.user.permissions.includes("ADMIN_USER") && (
|
||||
<li>
|
||||
<Link href="/admin/user">Benutzer</Link>
|
||||
</li>
|
||||
)}
|
||||
{session.user.permissions.includes("ADMIN_STATION") && (
|
||||
<li>
|
||||
<Link href="/admin/station">Stationen</Link>
|
||||
</li>
|
||||
)}
|
||||
{session.user.permissions.includes("ADMIN_KEYWORD") && (
|
||||
<li>
|
||||
<Link href="/admin/keyword">Stichworte</Link>
|
||||
</li>
|
||||
)}
|
||||
{session.user.permissions.includes("ADMIN_HELIPORT") && (
|
||||
<li>
|
||||
<Link href="/admin/heliport">Heliports</Link>
|
||||
</li>
|
||||
)}
|
||||
{session.user.permissions.includes("ADMIN_EVENT") && (
|
||||
<li>
|
||||
<Link href="/admin/event">Events</Link>
|
||||
</li>
|
||||
)}
|
||||
{session.user.permissions.includes("ADMIN_MESSAGE") && (
|
||||
<li>
|
||||
<Link href="/admin/config">Config</Link>
|
||||
</li>
|
||||
)}
|
||||
{session.user.permissions.includes("ADMIN_USER") && (
|
||||
<li>
|
||||
<Link href="/admin/report">Reports</Link>
|
||||
</li>
|
||||
)}
|
||||
{session.user.permissions.includes("ADMIN_USER") && (
|
||||
<li>
|
||||
<Link href="/admin/penalty">Audit-Log</Link>
|
||||
</li>
|
||||
)}
|
||||
{session.user.permissions.includes("ADMIN_CHANGELOG") && (
|
||||
<li>
|
||||
<Link href="/admin/changelog">Changelog</Link>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</details>
|
||||
</li>
|
||||
)}
|
||||
</div>
|
||||
<li>
|
||||
<Link href="/changelog">
|
||||
<ActivityLogIcon />
|
||||
Changelog
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/* const nextConfig = removeImports({}); */
|
||||
const nextConfig = {
|
||||
images: {
|
||||
domains: ["cdn.discordapp.com"],
|
||||
domains: ["cdn.discordapp.com", "nextcloud.virtualairrescue.com"],
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user