Initial commit
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { ArrowLeft, Trash2 } from "lucide-react";
|
||||
import { getCollectionBySlug, listImages } from "@/lib/db/queries";
|
||||
import { MasonryGrid } from "@/components/grid/MasonryGrid";
|
||||
import { RegisterVisible } from "@/components/select/RegisterVisible";
|
||||
import { FilterBar } from "@/components/grid/FilterBar";
|
||||
import { UploadCard } from "@/components/ingest/UploadCard";
|
||||
import { deleteCollection, renameCollection } from "@/app/actions/collections";
|
||||
import { EntityRenameInline } from "@/components/entities/EntityRenameInline";
|
||||
import { ReorderableCollectionGrid } from "@/components/collections/ReorderableCollectionGrid";
|
||||
import { resolveSort } from "@/lib/sortServer";
|
||||
import { parseFilterCriteria, statusToFlags } from "@/lib/filters";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function CollectionPage({
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>;
|
||||
searchParams: Promise<Record<string, string | string[] | undefined>>;
|
||||
}) {
|
||||
const { slug } = await params;
|
||||
const sp = await searchParams;
|
||||
const c = getCollectionBySlug(decodeURIComponent(slug));
|
||||
if (!c) notFound();
|
||||
const urlSort = typeof sp.sort === "string" ? sp.sort : undefined;
|
||||
// Resolve only when user explicitly chose; otherwise keep manual position default.
|
||||
const sort = urlSort ? await resolveSort(urlSort) : undefined;
|
||||
const search = (typeof sp.q === "string" ? sp.q.trim() : "") || undefined;
|
||||
const criteria = parseFilterCriteria(sp);
|
||||
const items = listImages({
|
||||
collectionId: c.id,
|
||||
sort,
|
||||
search,
|
||||
...statusToFlags(criteria.status),
|
||||
marks: criteria.marks,
|
||||
actressIds: criteria.ids.actresses,
|
||||
actressMode: criteria.mode.actresses,
|
||||
studioIds: criteria.ids.studios,
|
||||
seriesIds: criteria.ids.series,
|
||||
genreIds: criteria.ids.genres,
|
||||
genreMode: criteria.mode.genres,
|
||||
collectionIds: criteria.ids.collections,
|
||||
collectionMode: criteria.mode.collections,
|
||||
tagIds: criteria.ids.tags,
|
||||
tagMode: criteria.mode.tags,
|
||||
categoryIds: criteria.ids.categories,
|
||||
categoryMode: criteria.mode.categories,
|
||||
});
|
||||
|
||||
const remove = async () => {
|
||||
"use server";
|
||||
await deleteCollection(c.id);
|
||||
};
|
||||
|
||||
const rename = async (name: string) => {
|
||||
"use server";
|
||||
return await renameCollection(c.id, name);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-[1600px] mx-auto px-6 py-6 fade-in">
|
||||
<Link href="/collection" className="inline-flex items-center gap-1 text-sm text-[var(--color-fg-dim)] hover:text-[var(--color-fg)] mb-4">
|
||||
<ArrowLeft className="w-4 h-4" /> Collection
|
||||
</Link>
|
||||
<div className="flex items-start justify-between gap-6 mb-6">
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<h1 className="text-3xl font-semibold tracking-tight truncate">{c.name}</h1>
|
||||
<EntityRenameInline
|
||||
initialName={c.name}
|
||||
onRename={rename}
|
||||
redirectBase="/collection/"
|
||||
redirectKey="slug"
|
||||
/>
|
||||
<form action={remove}>
|
||||
<button className="flex items-center gap-1.5 text-xs px-2 py-1 rounded-lg glass text-[var(--color-fg-muted)] hover:text-[var(--color-coral)] hover:border-[var(--color-coral)]/30">
|
||||
<Trash2 className="w-3.5 h-3.5" /> Delete
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{c.description && <p className="text-[var(--color-fg-dim)] mt-1">{c.description}</p>}
|
||||
<p className="text-sm text-[var(--color-fg-muted)] mt-1">{items.length} Item{items.length === 1 ? "" : "s"}</p>
|
||||
</div>
|
||||
<div className="w-[300px] flex-shrink-0">
|
||||
<UploadCard autoAssign={{ collectionId: c.id }} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FilterBar current={{ kind: "collection", id: c.id, name: c.name }} criteria={criteria} sort={sort} />
|
||||
|
||||
{items.length === 0 ? (
|
||||
<div className="glass rounded-2xl p-card text-center text-[var(--color-fg-dim)]">
|
||||
Empty. Add images from any image detail page.
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<RegisterVisible ids={items.map((i) => i.id)} />
|
||||
{urlSort ? (
|
||||
// User overrode the default order via the sort menu — disable
|
||||
// drag-reorder since drag-position vs sorted-position would
|
||||
// contradict each other.
|
||||
<MasonryGrid images={items} />
|
||||
) : (
|
||||
<ReorderableCollectionGrid images={items} collectionId={c.id} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user