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 || "",
|
title: changelog?.title || "",
|
||||||
text: changelog?.text || "",
|
text: changelog?.text || "",
|
||||||
previewImage: changelog?.previewImage || "", // Changed to accept a URL as a string
|
previewImage: changelog?.previewImage || "", // Changed to accept a URL as a string
|
||||||
|
showOnChangelogPage: changelog?.showOnChangelogPage || true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const [skipUserUpdate, setSkipUserUpdate] = useState(false);
|
const [skipUserUpdate, setSkipUserUpdate] = useState(false);
|
||||||
@@ -84,6 +85,7 @@ export const ChangelogForm = ({ changelog }: { changelog?: Changelog }) => {
|
|||||||
placeholder="Titel (vX.X.X)"
|
placeholder="Titel (vX.X.X)"
|
||||||
className="input-sm"
|
className="input-sm"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
form={form}
|
form={form}
|
||||||
label="Bild-URL"
|
label="Bild-URL"
|
||||||
@@ -146,6 +148,16 @@ export const ChangelogForm = ({ changelog }: { changelog?: Changelog }) => {
|
|||||||
</span>
|
</span>
|
||||||
</label>
|
</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="card-body">
|
||||||
<div className="flex w-full gap-4">
|
<div className="flex w-full gap-4">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { DatabaseBackupIcon } from "lucide-react";
|
import { Check, Cross, DatabaseBackupIcon } from "lucide-react";
|
||||||
import { PaginatedTable } from "../../../_components/PaginatedTable";
|
import { PaginatedTable } from "../../../_components/PaginatedTable";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { ColumnDef } from "@tanstack/react-table";
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
import { Keyword, Prisma } from "@repo/db";
|
import { Changelog, Keyword, Prisma } from "@repo/db";
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PaginatedTable
|
<PaginatedTable
|
||||||
stickyHeaders
|
stickyHeaders
|
||||||
initialOrderBy={[{ id: "title", desc: true }]}
|
initialOrderBy={[{ id: "createdAt", desc: true }]}
|
||||||
prismaModel="changelog"
|
prismaModel="changelog"
|
||||||
showSearch
|
showSearch
|
||||||
getFilter={(search) =>
|
getFilter={(search) =>
|
||||||
({
|
({
|
||||||
OR: [
|
OR: [
|
||||||
{ title: { contains: search, mode: "insensitive" } },
|
{ title: { contains: search, mode: "insensitive" } },
|
||||||
{ description: { contains: search, mode: "insensitive" } },
|
{ text: { contains: search, mode: "insensitive" } },
|
||||||
],
|
],
|
||||||
}) as Prisma.ChangelogWhereInput
|
}) as Prisma.ChangelogWhereInput
|
||||||
}
|
}
|
||||||
@@ -27,6 +27,16 @@ export default () => {
|
|||||||
header: "Title",
|
header: "Title",
|
||||||
accessorKey: "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",
|
header: "Aktionen",
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
@@ -37,7 +47,7 @@ export default () => {
|
|||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
] as ColumnDef<Keyword>[]
|
] as ColumnDef<Changelog>[]
|
||||||
}
|
}
|
||||||
leftOfSearch={
|
leftOfSearch={
|
||||||
<span className="flex items-center gap-2">
|
<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,
|
RocketIcon,
|
||||||
ReaderIcon,
|
ReaderIcon,
|
||||||
DownloadIcon,
|
DownloadIcon,
|
||||||
|
UpdateIcon,
|
||||||
|
ActivityLogIcon,
|
||||||
} from "@radix-ui/react-icons";
|
} from "@radix-ui/react-icons";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { WarningAlert } from "./ui/PageAlert";
|
import { WarningAlert } from "./ui/PageAlert";
|
||||||
import { getServerSession } from "api/auth/[...nextauth]/auth";
|
import { getServerSession } from "api/auth/[...nextauth]/auth";
|
||||||
import { Error } from "./Error";
|
import { Error } from "./Error";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { Plane, Radar, Workflow } from "lucide-react";
|
import { Loader, Plane, Radar, Workflow } from "lucide-react";
|
||||||
import { BookingButton } from "./BookingButton";
|
import { BookingButton } from "./BookingButton";
|
||||||
|
|
||||||
export const VerticalNav = async () => {
|
export const VerticalNav = async () => {
|
||||||
@@ -22,7 +24,8 @@ export const VerticalNav = async () => {
|
|||||||
return p.startsWith("ADMIN");
|
return p.startsWith("ADMIN");
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<ul className="menu bg-base-300 w-64 flex-nowrap rounded-lg p-3 font-semibold shadow-md">
|
<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>
|
<li>
|
||||||
<Link href="/">
|
<Link href="/">
|
||||||
<HomeIcon /> Dashboard
|
<HomeIcon /> Dashboard
|
||||||
@@ -109,6 +112,13 @@ export const VerticalNav = async () => {
|
|||||||
</details>
|
</details>
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
<li>
|
||||||
|
<Link href="/changelog">
|
||||||
|
<ActivityLogIcon />
|
||||||
|
Changelog
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
/* const nextConfig = removeImports({}); */
|
/* const nextConfig = removeImports({}); */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
images: {
|
images: {
|
||||||
domains: ["cdn.discordapp.com"],
|
domains: ["cdn.discordapp.com", "nextcloud.virtualairrescue.com"],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,4 +4,5 @@ model Changelog {
|
|||||||
previewImage String?
|
previewImage String?
|
||||||
text String
|
text String
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
showOnChangelogPage Boolean @default(true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Changelog" ADD COLUMN "showOnChangelogPage" BOOLEAN NOT NULL DEFAULT true;
|
||||||
Reference in New Issue
Block a user