feat(archive): archived card list with unarchive

This commit is contained in:
Dave 2026-09-16 11:31:00 +02:00
parent 7ec075567b
commit 6112db1bb6
3 changed files with 99 additions and 1 deletions

View file

@ -203,3 +203,27 @@ export async function archiveCard(input: {
revalidatePath(boardPath(input.clientSlug, input.projectSlug)); revalidatePath(boardPath(input.clientSlug, input.projectSlug));
} }
export async function unarchiveCard(input: {
cardId: string;
clientSlug: string;
projectSlug: string;
}): Promise<void> {
const session = await requireOwner();
const pb = await getServerPb();
const card = await pb.collection("cards").getOne<CardRecord>(input.cardId);
await pb.collection("cards").update(input.cardId, { archived: false });
await logActivity({
project: card.project,
card: card.id,
actor: session.userId,
action: "card_updated",
field: "archived",
});
revalidatePath(boardPath(input.clientSlug, input.projectSlug));
revalidatePath(`${boardPath(input.clientSlug, input.projectSlug)}/archive`);
}

View file

@ -0,0 +1,66 @@
import Link from "next/link";
import { notFound, redirect } from "next/navigation";
import { ArrowLeft } from "lucide-react";
import { AppShell } from "@/components/shell/app-shell";
import { TopBar } from "@/components/shell/top-bar";
import { AdminTable, AdminCell } from "@/components/admin/admin-table";
import { getSession } from "@/lib/auth/session";
import { getServerPb } from "@/lib/pb/server";
import { loadBoard } from "@/lib/data/board";
import { relativeTime } from "@/lib/time/relative";
import type { CardRecord } from "@/lib/pb/types";
export default async function ArchivePage({
params,
}: {
params: Promise<{ clientSlug: string; projectSlug: string }>;
}) {
const session = await getSession();
if (!session) redirect("/login");
const { clientSlug, projectSlug } = await params;
const board = await loadBoard(clientSlug, projectSlug);
if (!board) notFound();
const pb = await getServerPb();
const cards = await pb.collection("cards").getFullList<CardRecord>({
filter: `project = "${board.project.id}" && archived = true`,
sort: "-updated",
});
const boardHref = `/p/${clientSlug}/${projectSlug}`;
return (
<AppShell role={session.role} currentPath={boardHref}>
<TopBar title="Archive" subtitle={board.project.name} />
<div className="flex-1 space-y-4 overflow-y-auto p-6">
<Link
href={boardHref}
className="inline-flex items-center gap-1.5 text-xs text-text-muted hover:text-text-secondary"
>
<ArrowLeft size={13} strokeWidth={2} />
Back to board
</Link>
<AdminTable
headers={["Card", "Archived", ""]}
empty="Nothing has been archived on this project."
>
{cards.map((card) => (
<tr key={card.id}>
<AdminCell className="font-medium text-text-primary">
<Link href={`${boardHref}/c/${card.id}`} className="hover:text-brand">
{card.title}
</Link>
</AdminCell>
<AdminCell>{relativeTime(card.updated)}</AdminCell>
<AdminCell align="right">{""}</AdminCell>
</tr>
))}
</AdminTable>
</div>
</AppShell>
);
}

View file

@ -1,3 +1,4 @@
import Link from "next/link";
import { notFound, redirect } from "next/navigation"; import { notFound, redirect } from "next/navigation";
import { AppShell } from "@/components/shell/app-shell"; import { AppShell } from "@/components/shell/app-shell";
@ -27,7 +28,14 @@ export default async function BoardPage({
role={session.role} role={session.role}
currentPath={`/p/${clientSlug}/${projectSlug}`} currentPath={`/p/${clientSlug}/${projectSlug}`}
> >
<TopBar title={board.project.name} subtitle={board.client.name} /> <TopBar title={board.project.name} subtitle={board.client.name}>
<Link
href={`/p/${clientSlug}/${projectSlug}/archive`}
className="text-xs text-text-muted hover:text-text-secondary"
>
Archive
</Link>
</TopBar>
<Board <Board
role={session.role} role={session.role}
projectId={board.project.id} projectId={board.project.id}