88 lines
2.6 KiB
TypeScript
88 lines
2.6 KiB
TypeScript
'use client';
|
|
|
|
import { useSortable } from '@dnd-kit/sortable';
|
|
import { CSS } from '@dnd-kit/utilities';
|
|
import clsx from 'clsx';
|
|
import { CheckCircle, Circle, GripVertical } from 'lucide-react';
|
|
import NextImage from 'next/image';
|
|
import Link from 'next/link';
|
|
|
|
type SortableCardItemProps = {
|
|
id: string;
|
|
item: {
|
|
id: string;
|
|
name: string;
|
|
fileKey: string;
|
|
altText: string;
|
|
published?: boolean;
|
|
creationDate?: Date | string;
|
|
};
|
|
};
|
|
|
|
export function SortableImage({ id, item }: SortableCardItemProps) {
|
|
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id });
|
|
|
|
const style = {
|
|
transform: CSS.Transform.toString(transform),
|
|
transition,
|
|
};
|
|
|
|
const href = `/portfolio/edit/${item.id}`;
|
|
|
|
let dateDisplay = null;
|
|
if (item.creationDate instanceof Date) {
|
|
dateDisplay = item.creationDate.toLocaleDateString('de-DE');
|
|
} else if (typeof item.creationDate === 'string') {
|
|
const parsed = new Date(item.creationDate);
|
|
if (!isNaN(parsed.getTime())) {
|
|
dateDisplay = parsed.toLocaleDateString('de-DE');
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div
|
|
ref={setNodeRef}
|
|
style={style}
|
|
{...attributes}
|
|
className="relative cursor-grab active:cursor-grabbing"
|
|
>
|
|
<div
|
|
{...listeners}
|
|
className="absolute top-2 left-2 z-20 text-muted-foreground bg-white/70 rounded-full p-1"
|
|
title="Drag to reorder"
|
|
>
|
|
<GripVertical className="w-4 h-4" />
|
|
</div>
|
|
|
|
<Link href={href}>
|
|
<div className="group rounded-lg border overflow-hidden hover:shadow-md transition-shadow bg-background relative">
|
|
<div className="relative aspect-[4/3] w-full bg-muted items-center justify-center">
|
|
<NextImage
|
|
src={`/api/image/thumbnail/${item.fileKey}.webp`}
|
|
alt={item.altText ? item.altText : "Image"}
|
|
fill
|
|
className={clsx("object-cover transition duration-300")}
|
|
/>
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<div className="p-4 text-center">
|
|
<h2 className="text-lg font-semibold truncate">{item.name}</h2>
|
|
{dateDisplay && (
|
|
<p className="text-xs text-muted-foreground mt-1">{dateDisplay}</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* Status icon in bottom-left corner */}
|
|
<div className="absolute bottom-2 left-2 z-10 bg-white/80 rounded-full p-1">
|
|
{item.published ? (
|
|
<CheckCircle className="w-4 h-4 text-green-600" />
|
|
) : (
|
|
<Circle className="w-4 h-4 text-muted-foreground" />
|
|
)}
|
|
</div>
|
|
</div>
|
|
</Link>
|
|
</div>
|
|
);
|
|
} |