This commit is contained in:
@@ -1,15 +1,34 @@
|
||||
"use client";
|
||||
|
||||
import type { MediaKind, PortfolioAssetKind, PortfolioSectionType } from "@prisma/client";
|
||||
import { ArrowDown, ArrowUp, Plus, Trash2 } from "lucide-react";
|
||||
import {
|
||||
ArrowDown,
|
||||
ArrowUp,
|
||||
BriefcaseBusiness,
|
||||
CalendarRange,
|
||||
FolderTree,
|
||||
Hash,
|
||||
Link2,
|
||||
Plus,
|
||||
Sparkles,
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { MediaFieldPicker, type MediaFieldState } from "@/components/root/media-field-picker";
|
||||
import { AppCard } from "@/components/ui/app-card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { moveArrayItem } from "@/lib/array";
|
||||
@@ -176,6 +195,8 @@ export function PortfolioProjectForm({
|
||||
}))
|
||||
: [],
|
||||
);
|
||||
const [isFeatured, setIsFeatured] = useState(project?.isFeatured ?? false);
|
||||
const [isPublished, setIsPublished] = useState(project?.isPublished ?? false);
|
||||
|
||||
const sectionsPayload = JSON.stringify(
|
||||
sections.map((section, index) => ({
|
||||
@@ -205,71 +226,106 @@ export function PortfolioProjectForm({
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-4 md:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="categoryId">Kategorie</Label>
|
||||
<select
|
||||
id="categoryId"
|
||||
name="categoryId"
|
||||
defaultValue={project?.category.id ?? categories[0]?.id ?? ""}
|
||||
className="flex h-11 w-full rounded-pill border border-border bg-background px-4 text-sm text-foreground shadow-sm"
|
||||
required
|
||||
>
|
||||
{categories.map((category) => (
|
||||
<option key={category.id} value={category.id}>
|
||||
{category.name.de} / {category.name.en}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<Label htmlFor="categoryId" className="sr-only">Kategorie</Label>
|
||||
<div className="relative">
|
||||
<FolderTree className="pointer-events-none absolute left-3 top-1/2 z-10 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<Select
|
||||
name="categoryId"
|
||||
defaultValue={project?.category.id ?? categories[0]?.id ?? ""}
|
||||
required
|
||||
>
|
||||
<SelectTrigger id="categoryId" className="pl-9">
|
||||
<SelectValue placeholder="Kategorie" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{categories.map((category) => (
|
||||
<SelectItem key={category.id} value={category.id}>
|
||||
{category.name.de} / {category.name.en}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="slug">Slug</Label>
|
||||
<Input id="slug" name="slug" defaultValue={project?.slug ?? ""} required />
|
||||
<Label htmlFor="slug" className="sr-only">Slug</Label>
|
||||
<div className="relative">
|
||||
<Hash className="pointer-events-none absolute left-3 top-1/2 z-10 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<Input
|
||||
id="slug"
|
||||
name="slug"
|
||||
defaultValue={project?.slug ?? ""}
|
||||
placeholder="Slug"
|
||||
className="pl-9"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="clientName">Kundenname</Label>
|
||||
<Input
|
||||
id="clientName"
|
||||
name="clientName"
|
||||
defaultValue={project?.clientName ?? ""}
|
||||
required
|
||||
/>
|
||||
<Label htmlFor="clientName" className="sr-only">Kundenname</Label>
|
||||
<div className="relative">
|
||||
<BriefcaseBusiness className="pointer-events-none absolute left-3 top-1/2 z-10 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<Input
|
||||
id="clientName"
|
||||
name="clientName"
|
||||
defaultValue={project?.clientName ?? ""}
|
||||
placeholder="Kundenname"
|
||||
className="pl-9"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="projectYear">Projektjahr</Label>
|
||||
<Input
|
||||
id="projectYear"
|
||||
name="projectYear"
|
||||
type="number"
|
||||
min="2000"
|
||||
max="2100"
|
||||
defaultValue={project?.projectYear ?? new Date().getFullYear()}
|
||||
required
|
||||
/>
|
||||
<Label htmlFor="projectYear" className="sr-only">Projektjahr</Label>
|
||||
<div className="relative">
|
||||
<CalendarRange className="pointer-events-none absolute left-3 top-1/2 z-10 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<Input
|
||||
id="projectYear"
|
||||
name="projectYear"
|
||||
type="number"
|
||||
min="2000"
|
||||
max="2100"
|
||||
defaultValue={project?.projectYear ?? new Date().getFullYear()}
|
||||
placeholder="Projektjahr"
|
||||
className="pl-9"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="previewUrl">Preview URL</Label>
|
||||
<Input
|
||||
id="previewUrl"
|
||||
name="previewUrl"
|
||||
type="url"
|
||||
defaultValue={project?.previewUrl ?? ""}
|
||||
placeholder="https://example.com"
|
||||
/>
|
||||
<Label htmlFor="previewUrl" className="sr-only">Preview URL</Label>
|
||||
<div className="relative">
|
||||
<Link2 className="pointer-events-none absolute left-3 top-1/2 z-10 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<Input
|
||||
id="previewUrl"
|
||||
name="previewUrl"
|
||||
type="url"
|
||||
defaultValue={project?.previewUrl ?? ""}
|
||||
placeholder="https://example.com"
|
||||
className="pl-9"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="sortOrder">Sortierung</Label>
|
||||
<Input
|
||||
id="sortOrder"
|
||||
name="sortOrder"
|
||||
type="number"
|
||||
min="0"
|
||||
defaultValue={project?.sortOrder ?? 0}
|
||||
required
|
||||
/>
|
||||
<Label htmlFor="sortOrder" className="sr-only">Sortierung</Label>
|
||||
<div className="relative">
|
||||
<Hash className="pointer-events-none absolute left-3 top-1/2 z-10 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<Input
|
||||
id="sortOrder"
|
||||
name="sortOrder"
|
||||
type="number"
|
||||
min="0"
|
||||
defaultValue={project?.sortOrder ?? 0}
|
||||
placeholder="Sortierung"
|
||||
className="pl-9"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 md:col-span-2">
|
||||
@@ -284,13 +340,23 @@ export function PortfolioProjectForm({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<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} />
|
||||
<label className="flex items-center gap-3 rounded-md border border-border px-4 py-3 text-sm">
|
||||
<Checkbox
|
||||
name="isFeatured"
|
||||
checked={isFeatured}
|
||||
onCheckedChange={(checked) => setIsFeatured(checked === true)}
|
||||
/>
|
||||
<Sparkles className="h-4 w-4 text-muted-foreground" />
|
||||
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} />
|
||||
<label className="flex items-center gap-3 rounded-md border border-border px-4 py-3 text-sm">
|
||||
<Checkbox
|
||||
name="isPublished"
|
||||
checked={isPublished}
|
||||
onCheckedChange={(checked) => setIsPublished(checked === true)}
|
||||
/>
|
||||
<Sparkles className="h-4 w-4 text-muted-foreground" />
|
||||
Veroeffentlicht
|
||||
</label>
|
||||
</CardContent>
|
||||
@@ -411,25 +477,29 @@ export function PortfolioProjectForm({
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label>Typ</Label>
|
||||
<select
|
||||
<Select
|
||||
value={section.type}
|
||||
onChange={(event) =>
|
||||
onValueChange={(value) =>
|
||||
setSections((current) =>
|
||||
current.map((item, currentIndex) =>
|
||||
currentIndex === index
|
||||
? { ...item, type: event.target.value as PortfolioSectionType }
|
||||
? { ...item, type: value as PortfolioSectionType }
|
||||
: item,
|
||||
),
|
||||
)
|
||||
}
|
||||
className="flex h-11 w-full rounded-pill border border-border bg-background px-4 text-sm text-foreground shadow-sm"
|
||||
>
|
||||
{sectionTypeOptions.map((type) => (
|
||||
<option key={type} value={type}>
|
||||
{type}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Typ" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{sectionTypeOptions.map((type) => (
|
||||
<SelectItem key={type} value={type}>
|
||||
{type}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 md:col-span-2">
|
||||
@@ -457,17 +527,22 @@ export function PortfolioProjectForm({
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 md:col-span-2">
|
||||
<Label>Link URL</Label>
|
||||
<Input
|
||||
value={section.linkUrl}
|
||||
onChange={(event) =>
|
||||
setSections((current) =>
|
||||
current.map((item, currentIndex) =>
|
||||
currentIndex === index ? { ...item, linkUrl: event.target.value } : item,
|
||||
),
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Label className="sr-only">Link URL</Label>
|
||||
<div className="relative">
|
||||
<Link2 className="pointer-events-none absolute left-3 top-1/2 z-10 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<Input
|
||||
value={section.linkUrl}
|
||||
onChange={(event) =>
|
||||
setSections((current) =>
|
||||
current.map((item, currentIndex) =>
|
||||
currentIndex === index ? { ...item, linkUrl: event.target.value } : item,
|
||||
),
|
||||
)
|
||||
}
|
||||
placeholder="Link URL"
|
||||
className="pl-9"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{localeFieldConfig.map((locale) => (
|
||||
@@ -575,32 +650,36 @@ export function PortfolioProjectForm({
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label>Typ</Label>
|
||||
<select
|
||||
<Select
|
||||
value={asset.kind}
|
||||
onChange={(event) =>
|
||||
onValueChange={(value) =>
|
||||
setAssets((current) =>
|
||||
current.map((item, currentIndex) =>
|
||||
currentIndex === index
|
||||
? {
|
||||
...item,
|
||||
kind: event.target.value as PortfolioAssetKind,
|
||||
kind: value as PortfolioAssetKind,
|
||||
media: {
|
||||
...item.media,
|
||||
kind: event.target.value as PortfolioAssetKind,
|
||||
kind: value as PortfolioAssetKind,
|
||||
},
|
||||
}
|
||||
: item,
|
||||
),
|
||||
)
|
||||
}
|
||||
className="flex h-11 w-full rounded-pill border border-border bg-background px-4 text-sm text-foreground shadow-sm"
|
||||
>
|
||||
{assetKindOptions.map((kind) => (
|
||||
<option key={kind} value={kind}>
|
||||
{kind}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Typ" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{assetKindOptions.map((kind) => (
|
||||
<SelectItem key={kind} value={kind}>
|
||||
{kind}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 md:col-span-2">
|
||||
|
||||
Reference in New Issue
Block a user