Refactor root admin dashboard UI

This commit is contained in:
MOH
2026-03-07 17:36:45 +01:00
parent ead75769ef
commit 8606f6e315
25 changed files with 2345 additions and 685 deletions
+66 -43
View File
@@ -2,6 +2,7 @@ import { Plus } from "lucide-react";
import Link from "next/link";
import { redirect } from "next/navigation";
import { MotionFade } from "@/components/motion-fade";
import { PortfolioSubnav } from "@/components/root/portfolio-subnav";
import { RootDashboardShell } from "@/components/root/root-dashboard-shell";
import { AppCard } from "@/components/ui/app-card";
@@ -27,6 +28,17 @@ const copy = {
logout: "Ausloggen",
backToSite: "Zur Website",
newProject: "Neues Projekt",
category: "Kategorie",
all: "Alle",
status: "Status",
draft: "Entwurf",
published: "Veroeffentlicht",
filter: "Filtern",
sort: "Sortierung",
previewSet: "Preview Link gesetzt",
previewMissing: "Kein Preview Link",
editProject: "Projekt bearbeiten",
empty: "Keine Projekte fuer die aktuellen Filter gefunden.",
};
type RootPortfolioProjectsPageProps = {
@@ -71,36 +83,42 @@ export default async function RootPortfolioProjectsPage({
logoutAction={logoutAction}
headerTitle={copy.title}
headerDescription={copy.subtitle}
toolbar={<PortfolioSubnav active="projects" />}
headerActions={
<Button asChild>
<Link href="/root/portfolio/projects/new">
<Plus className="h-4 w-4" />
{copy.newProject}
</Link>
</Button>
<MotionFade delay={0.04}>
<Button asChild>
<Link href="/root/portfolio/projects/new">
<Plus className="h-4 w-4" />
{copy.newProject}
</Link>
</Button>
</MotionFade>
}
>
<div className="space-y-6">
<PortfolioSubnav active="projects" />
{searchParams?.success ? (
<p className="rounded-nested border border-status-success/30 bg-status-success/10 px-4 py-3 text-sm text-status-success">
{searchParams.success}
</p>
<MotionFade delay={0.1}>
<p className="rounded-nested border border-status-success/30 bg-status-success/10 px-4 py-3 text-sm text-status-success">
{searchParams.success}
</p>
</MotionFade>
) : null}
{searchParams?.error ? (
<p className="rounded-nested border border-destructive/30 bg-destructive/10 px-4 py-3 text-sm text-destructive">
{searchParams.error}
</p>
<MotionFade delay={0.12}>
<p className="rounded-nested border border-destructive/30 bg-destructive/10 px-4 py-3 text-sm text-destructive">
{searchParams.error}
</p>
</MotionFade>
) : null}
<AppCard>
<CardContent className="p-6">
<form className="grid gap-4 md:grid-cols-3">
<MotionFade delay={0.15}>
<AppCard>
<CardContent className="p-6">
<form className="grid gap-4 md:grid-cols-3">
<div className="space-y-2">
<label htmlFor="category" className="text-sm font-medium text-foreground">
Category
{copy.category}
</label>
<select
id="category"
@@ -108,7 +126,7 @@ export default async function RootPortfolioProjectsPage({
defaultValue={searchParams?.category ?? ""}
className="flex h-11 w-full rounded-pill border border-border bg-background px-4 text-sm text-foreground shadow-sm"
>
<option value="">All</option>
<option value="">{copy.all}</option>
{categories.map((category) => (
<option key={category.id} value={category.id}>
{category.name.de}
@@ -119,7 +137,7 @@ export default async function RootPortfolioProjectsPage({
<div className="space-y-2">
<label htmlFor="status" className="text-sm font-medium text-foreground">
Status
{copy.status}
</label>
<select
id="status"
@@ -127,25 +145,27 @@ export default async function RootPortfolioProjectsPage({
defaultValue={selectedStatus}
className="flex h-11 w-full rounded-pill border border-border bg-background px-4 text-sm text-foreground shadow-sm"
>
<option value="all">All</option>
<option value="draft">Draft</option>
<option value="published">Published</option>
<option value="all">{copy.all}</option>
<option value="draft">{copy.draft}</option>
<option value="published">{copy.published}</option>
</select>
</div>
<div className="flex items-end">
<Button type="submit" variant="outline">
Filter
{copy.filter}
</Button>
</div>
</form>
</CardContent>
</AppCard>
</form>
</CardContent>
</AppCard>
</MotionFade>
<div className="grid gap-4">
{projects.map((project) => (
<AppCard key={project.id} interactive>
<CardHeader className="flex flex-row items-center justify-between gap-4">
{projects.map((project, index) => (
<MotionFade key={project.id} delay={0.18 + index * 0.03}>
<AppCard interactive>
<CardHeader className="flex flex-row items-center justify-between gap-4">
<div>
<CardTitle>{getLocalizedValue(project.title, "de")}</CardTitle>
<p className="mt-1 text-sm text-muted-foreground">
@@ -154,34 +174,37 @@ export default async function RootPortfolioProjectsPage({
</div>
<div className="flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
<span className="rounded-pill border border-border px-3 py-1">
{project.isPublished ? "Published" : "Draft"}
{project.isPublished ? copy.published : copy.draft}
</span>
<span className="rounded-pill border border-border px-3 py-1">
{project.projectYear}
</span>
<span className="rounded-pill border border-border px-3 py-1">
Sort {project.sortOrder}
{copy.sort} {project.sortOrder}
</span>
</div>
</CardHeader>
<CardContent className="flex flex-wrap items-center justify-between gap-4">
</CardHeader>
<CardContent className="flex flex-wrap items-center justify-between gap-4">
<div className="space-y-1 text-sm text-muted-foreground">
<p>{project.slug}</p>
<p>{project.previewUrl ? "Preview link set" : "No preview link"}</p>
<p>{project.previewUrl ? copy.previewSet : copy.previewMissing}</p>
</div>
<Button asChild>
<Link href={`/root/portfolio/projects/${project.id}`}>Edit Project</Link>
<Link href={`/root/portfolio/projects/${project.id}`}>{copy.editProject}</Link>
</Button>
</CardContent>
</AppCard>
</CardContent>
</AppCard>
</MotionFade>
))}
{projects.length === 0 ? (
<AppCard>
<CardContent className="p-6 text-sm text-muted-foreground">
No projects match the selected filters.
</CardContent>
</AppCard>
<MotionFade delay={0.18}>
<AppCard>
<CardContent className="p-6 text-sm text-muted-foreground">
{copy.empty}
</CardContent>
</AppCard>
</MotionFade>
) : null}
</div>
</div>