Refactor root admin dashboard UI
This commit is contained in:
@@ -199,11 +199,11 @@ export function PortfolioProjectForm({
|
||||
|
||||
<AppCard>
|
||||
<CardHeader>
|
||||
<CardTitle>Project Basics</CardTitle>
|
||||
<CardTitle>Projekt Basisdaten</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-4 md:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="categoryId">Category</Label>
|
||||
<Label htmlFor="categoryId">Kategorie</Label>
|
||||
<select
|
||||
id="categoryId"
|
||||
name="categoryId"
|
||||
@@ -225,7 +225,7 @@ export function PortfolioProjectForm({
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="clientName">Client Name</Label>
|
||||
<Label htmlFor="clientName">Kundenname</Label>
|
||||
<Input
|
||||
id="clientName"
|
||||
name="clientName"
|
||||
@@ -235,7 +235,7 @@ export function PortfolioProjectForm({
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="projectYear">Project Year</Label>
|
||||
<Label htmlFor="projectYear">Projektjahr</Label>
|
||||
<Input
|
||||
id="projectYear"
|
||||
name="projectYear"
|
||||
@@ -259,7 +259,7 @@ export function PortfolioProjectForm({
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="sortOrder">Sort Order</Label>
|
||||
<Label htmlFor="sortOrder">Sortierung</Label>
|
||||
<Input
|
||||
id="sortOrder"
|
||||
name="sortOrder"
|
||||
@@ -272,7 +272,7 @@ export function PortfolioProjectForm({
|
||||
|
||||
<div className="space-y-2 md:col-span-2">
|
||||
<MediaFieldPicker
|
||||
title="Cover Image"
|
||||
title="Cover Bild"
|
||||
value={coverMedia}
|
||||
onChange={setCoverMedia}
|
||||
options={mediaOptions}
|
||||
@@ -284,19 +284,19 @@ export function PortfolioProjectForm({
|
||||
|
||||
<label className="flex items-center gap-3 rounded-nested border border-border px-4 py-3 text-sm">
|
||||
<input type="checkbox" name="isFeatured" defaultChecked={project?.isFeatured ?? false} />
|
||||
Featured
|
||||
Hervorgehoben
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-3 rounded-nested border border-border px-4 py-3 text-sm">
|
||||
<input type="checkbox" name="isPublished" defaultChecked={project?.isPublished ?? false} />
|
||||
Published
|
||||
Veroeffentlicht
|
||||
</label>
|
||||
</CardContent>
|
||||
</AppCard>
|
||||
|
||||
<AppCard>
|
||||
<CardHeader>
|
||||
<CardTitle>Localized Content</CardTitle>
|
||||
<CardTitle>Lokalisierte Inhalte</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Tabs defaultValue="de">
|
||||
@@ -312,13 +312,13 @@ export function PortfolioProjectForm({
|
||||
<div className="grid gap-4 rounded-surface border border-border p-4 md:grid-cols-2">
|
||||
<div className="md:col-span-2 text-sm text-muted-foreground">
|
||||
{locale.key === "ar"
|
||||
? "Arabic content for the Arabic website."
|
||||
? "Arabische Inhalte fuer die arabische Website."
|
||||
: locale.key === "en"
|
||||
? "English content for the English website."
|
||||
: "German content for the German website."}
|
||||
? "Englische Inhalte fuer die englische Website."
|
||||
: "Deutsche Inhalte fuer die deutsche Website."}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={`title${locale.suffix}`}>{`Title ${locale.label}`}</Label>
|
||||
<Label htmlFor={`title${locale.suffix}`}>{`Titel ${locale.label}`}</Label>
|
||||
<Input
|
||||
id={`title${locale.suffix}`}
|
||||
name={`title${locale.suffix}`}
|
||||
@@ -327,7 +327,7 @@ export function PortfolioProjectForm({
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={`serviceLabel${locale.suffix}`}>{`Service Label ${locale.label}`}</Label>
|
||||
<Label htmlFor={`serviceLabel${locale.suffix}`}>{`Leistungslabel ${locale.label}`}</Label>
|
||||
<Input
|
||||
id={`serviceLabel${locale.suffix}`}
|
||||
name={`serviceLabel${locale.suffix}`}
|
||||
@@ -336,7 +336,7 @@ export function PortfolioProjectForm({
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2 md:col-span-2">
|
||||
<Label htmlFor={`summary${locale.suffix}`}>{`Summary ${locale.label}`}</Label>
|
||||
<Label htmlFor={`summary${locale.suffix}`}>{`Kurzbeschreibung ${locale.label}`}</Label>
|
||||
<Textarea
|
||||
id={`summary${locale.suffix}`}
|
||||
name={`summary${locale.suffix}`}
|
||||
@@ -354,14 +354,14 @@ export function PortfolioProjectForm({
|
||||
|
||||
<AppCard>
|
||||
<CardHeader className="flex flex-row items-center justify-between">
|
||||
<CardTitle>Sections</CardTitle>
|
||||
<CardTitle>Abschnitte</CardTitle>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => setSections((current) => [...current, createEmptySection(current.length)])}
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
Add Section
|
||||
Abschnitt hinzufuegen
|
||||
</Button>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
@@ -369,7 +369,7 @@ export function PortfolioProjectForm({
|
||||
<AppCard key={section.id ?? `${section.type}-${index}`} level={2}>
|
||||
<CardContent className="space-y-4 p-4">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<p className="text-sm font-medium text-foreground">Section #{index + 1}</p>
|
||||
<p className="text-sm font-medium text-foreground">{`Abschnitt #${index + 1}`}</p>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
@@ -401,14 +401,14 @@ export function PortfolioProjectForm({
|
||||
disabled={sections.length === 1}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
Remove
|
||||
Entfernen
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label>Type</Label>
|
||||
<Label>Typ</Label>
|
||||
<select
|
||||
value={section.type}
|
||||
onChange={(event) =>
|
||||
@@ -432,7 +432,7 @@ export function PortfolioProjectForm({
|
||||
|
||||
<div className="space-y-2 md:col-span-2">
|
||||
<MediaFieldPicker
|
||||
title="Section Image"
|
||||
title="Abschnitt Bild"
|
||||
value={section.media}
|
||||
onChange={(media) =>
|
||||
setSections((current) =>
|
||||
@@ -470,7 +470,7 @@ export function PortfolioProjectForm({
|
||||
|
||||
{localeFieldConfig.map((locale) => (
|
||||
<div key={`${locale.key}-title-${index}`} className="space-y-2">
|
||||
<Label>{`Title ${locale.label}`}</Label>
|
||||
<Label>{`Titel ${locale.label}`}</Label>
|
||||
<Input
|
||||
value={section[`title${locale.suffix}` as keyof SectionFormValue] as string}
|
||||
onChange={(event) =>
|
||||
@@ -491,7 +491,7 @@ export function PortfolioProjectForm({
|
||||
|
||||
{localeFieldConfig.map((locale) => (
|
||||
<div key={`${locale.key}-body-${index}`} className="space-y-2 md:col-span-2">
|
||||
<Label>{`Body ${locale.label}`}</Label>
|
||||
<Label>{`Text ${locale.label}`}</Label>
|
||||
<Textarea
|
||||
rows={4}
|
||||
value={section[`body${locale.suffix}` as keyof SectionFormValue] as string}
|
||||
@@ -519,26 +519,26 @@ export function PortfolioProjectForm({
|
||||
|
||||
<AppCard>
|
||||
<CardHeader className="flex flex-row items-center justify-between">
|
||||
<CardTitle>Assets</CardTitle>
|
||||
<CardTitle>Dateien</CardTitle>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => setAssets((current) => [...current, createEmptyAsset(current.length)])}
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
Add Asset
|
||||
Datei hinzufuegen
|
||||
</Button>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{assets.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">No assets added yet.</p>
|
||||
<p className="text-sm text-muted-foreground">Noch keine Dateien hinzugefuegt.</p>
|
||||
) : null}
|
||||
|
||||
{assets.map((asset, index) => (
|
||||
<AppCard key={asset.id ?? `${asset.kind}-${index}`} level={2}>
|
||||
<CardContent className="space-y-4 p-4">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<p className="text-sm font-medium text-foreground">Asset #{index + 1}</p>
|
||||
<p className="text-sm font-medium text-foreground">{`Datei #${index + 1}`}</p>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
@@ -565,14 +565,14 @@ export function PortfolioProjectForm({
|
||||
}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
Remove
|
||||
Entfernen
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label>Kind</Label>
|
||||
<Label>Typ</Label>
|
||||
<select
|
||||
value={asset.kind}
|
||||
onChange={(event) =>
|
||||
@@ -603,7 +603,7 @@ export function PortfolioProjectForm({
|
||||
|
||||
<div className="space-y-2 md:col-span-2">
|
||||
<MediaFieldPicker
|
||||
title="Asset File"
|
||||
title="Datei"
|
||||
value={asset.media}
|
||||
onChange={(media) =>
|
||||
setAssets((current) =>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import Link from "next/link";
|
||||
|
||||
import { AppCard } from "@/components/ui/app-card";
|
||||
import { CardContent } from "@/components/ui/card";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type PortfolioSubnavProps = {
|
||||
@@ -9,38 +11,40 @@ type PortfolioSubnavProps = {
|
||||
const items = [
|
||||
{
|
||||
key: "overview",
|
||||
label: "Overview",
|
||||
label: "Uebersicht",
|
||||
href: "/root/portfolio",
|
||||
},
|
||||
{
|
||||
key: "categories",
|
||||
label: "Categories",
|
||||
label: "Kategorien",
|
||||
href: "/root/portfolio/categories",
|
||||
},
|
||||
{
|
||||
key: "projects",
|
||||
label: "Projects",
|
||||
label: "Projekte",
|
||||
href: "/root/portfolio/projects",
|
||||
},
|
||||
] as const;
|
||||
|
||||
export function PortfolioSubnav({ active }: PortfolioSubnavProps) {
|
||||
return (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{items.map((item) => (
|
||||
<Link
|
||||
key={item.key}
|
||||
href={item.href}
|
||||
className={cn(
|
||||
"rounded-pill border px-4 py-2 text-sm transition-colors",
|
||||
active === item.key
|
||||
? "border-border-strong bg-foreground text-background"
|
||||
: "border-border bg-background text-foreground/75 hover:border-border-strong hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<AppCard level={2}>
|
||||
<CardContent className="flex flex-wrap gap-2 p-4">
|
||||
{items.map((item) => (
|
||||
<Link
|
||||
key={item.key}
|
||||
href={item.href}
|
||||
className={cn(
|
||||
"rounded-pill border px-4 py-2 text-sm transition-colors",
|
||||
active === item.key
|
||||
? "border-border-strong bg-foreground text-background"
|
||||
: "border-border bg-background text-foreground/75 hover:border-border-strong hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
))}
|
||||
</CardContent>
|
||||
</AppCard>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { ArrowLeft, LogOut } from "lucide-react";
|
||||
import {
|
||||
ArrowLeft,
|
||||
FolderKanban,
|
||||
ImageIcon,
|
||||
LayoutDashboard,
|
||||
LogOut,
|
||||
PlusSquare,
|
||||
ShieldAlert,
|
||||
SwatchBook,
|
||||
Tags,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
import { AppHeader } from "@/components/layout/app-header";
|
||||
import { AppShell } from "@/components/layout/app-shell";
|
||||
import { AppSidebar } from "@/components/layout/app-sidebar";
|
||||
import { DashboardLayout } from "@/components/dashboard/dashboard-layout";
|
||||
import { MotionFade } from "@/components/motion-fade";
|
||||
import { ThemeToggle } from "@/components/theme-toggle";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { getLocalizedPath } from "@/lib/locale";
|
||||
@@ -30,6 +39,7 @@ type RootDashboardShellProps = {
|
||||
headerTitle: string;
|
||||
headerDescription: string;
|
||||
headerActions?: ReactNode;
|
||||
toolbar?: ReactNode;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
@@ -41,47 +51,85 @@ export function RootDashboardShell({
|
||||
headerTitle,
|
||||
headerDescription,
|
||||
headerActions,
|
||||
toolbar,
|
||||
children,
|
||||
}: RootDashboardShellProps) {
|
||||
const sidebarItems = getRootNavigation(copy, active, portfolioChild);
|
||||
const sidebarItems = getRootNavigation(copy, active, portfolioChild).filter(
|
||||
(item) => item.href !== "/root/maintenance" && item.href !== "/root/ui-kit",
|
||||
);
|
||||
const headerIcon =
|
||||
active === "overview"
|
||||
? LayoutDashboard
|
||||
: active === "maintenance"
|
||||
? ShieldAlert
|
||||
: active === "ui-kit"
|
||||
? SwatchBook
|
||||
: active === "media"
|
||||
? ImageIcon
|
||||
: portfolioChild === "categories"
|
||||
? Tags
|
||||
: portfolioChild === "new-project"
|
||||
? PlusSquare
|
||||
: FolderKanban;
|
||||
const sharedActions = (
|
||||
<>
|
||||
<ThemeToggle ariaLabel="Theme wechseln" />
|
||||
<Button asChild variant="outline" size="sm">
|
||||
<Link href={getLocalizedPath("de")}>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
{copy.backToSite}
|
||||
</Link>
|
||||
</Button>
|
||||
<form action={logoutAction}>
|
||||
<Button type="submit" variant="ghost" size="sm" className="text-destructive hover:bg-destructive/10 hover:text-destructive">
|
||||
<LogOut className="h-4 w-4" />
|
||||
{copy.logout}
|
||||
</Button>
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<AppShell
|
||||
sidebar={
|
||||
<AppSidebar
|
||||
title={copy.title}
|
||||
description={copy.subtitle}
|
||||
items={sidebarItems}
|
||||
footer={
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<ThemeToggle ariaLabel="Theme wechseln" />
|
||||
</div>
|
||||
<Button asChild variant="outline" className="w-full justify-start">
|
||||
<Link href={getLocalizedPath("de")}>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
{copy.backToSite}
|
||||
</Link>
|
||||
</Button>
|
||||
<form action={logoutAction}>
|
||||
<Button type="submit" variant="destructive" className="w-full justify-start">
|
||||
<LogOut className="h-4 w-4" />
|
||||
{copy.logout}
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<DashboardLayout
|
||||
title={headerTitle}
|
||||
description={headerDescription}
|
||||
icon={headerIcon}
|
||||
items={sidebarItems}
|
||||
sidebarFooter={
|
||||
<>
|
||||
<Button
|
||||
asChild
|
||||
variant={active === "maintenance" ? "default" : "outline"}
|
||||
className="w-full justify-between"
|
||||
>
|
||||
<Link href="/root/maintenance">
|
||||
{copy.maintenance}
|
||||
<ShieldAlert className="h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
<Button
|
||||
asChild
|
||||
variant={active === "ui-kit" ? "default" : "outline"}
|
||||
className="w-full justify-between"
|
||||
>
|
||||
<Link href="/root/ui-kit">
|
||||
{copy.uiKit}
|
||||
<SwatchBook className="h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
header={
|
||||
<AppHeader
|
||||
title={headerTitle}
|
||||
description={headerDescription}
|
||||
actions={headerActions}
|
||||
/>
|
||||
headerActions={
|
||||
<div className="flex flex-wrap items-center justify-end gap-2">
|
||||
{headerActions}
|
||||
{sharedActions}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</AppShell>
|
||||
<div className="space-y-6">
|
||||
{toolbar ? <MotionFade delay={0.05}>{toolbar}</MotionFade> : null}
|
||||
{children}
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user