feat(ui): Dashboard mit Board, Filtern und direktem Statuswechsel

- Fuenf Kennzahlen ueber dem Board: Projektzahl mit Durchschnittsfortschritt,
  Ideen, aktive Entwicklungen, fertige Projekte, verknuepfte Repos
- Volltextsuche ueber Name, Beschreibung, Tags und Repo-Pfad
- Statusfilter als Chips mit Trefferzaehler, mehrfach kombinierbar
- Sortierung nach Aenderung, Anlage, Name, Fortschritt oder Prioritaet;
  Favoriten stehen immer oben
- Zwei Ansichten: freies Raster oder nach Status gruppiert
- Archiv standardmaessig ausgeblendet, per Schalter sichtbar

Die Projektkarte zeigt Fortschritt, Provider, Default-Branch, letzten Tag,
Alter des letzten Commits, Notizzahl und Sync-Fehler. Status und Favorit sind
direkt auf der Karte aenderbar - beides optimistisch, mit Rollback wenn der
Server ablehnt. Die Karte ist ein "stretched link", damit die Schaltflaechen
darin nicht in einem Anker verschachtelt sind.

Der Anlege-Dialog verlangt nur einen Namen; neue Projekte starten als Idee.
Ist eine Git-Verbindung gewaehlt, laesst sich das Repository aus einer
durchsuchbaren Liste uebernehmen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-25 19:34:29 +02:00
co-authored by Claude Opus 5
parent 6fcbc04110
commit 62b5c5bfa6
7 changed files with 1297 additions and 0 deletions
+94
View File
@@ -0,0 +1,94 @@
import { CircleDot, GitBranch, Lightbulb, Rocket, Trophy } from "lucide-react";
import { ProjectBoard } from "@/components/ProjectBoard";
import { StatCard } from "@/components/StatCard";
import { prisma } from "@/lib/db";
import { connectionSafeSelect, projectListInclude } from "@/lib/projects";
import { STATUS_META } from "@/lib/status";
import type { ConnectionSafe, ProjectListItem } from "@/types";
export const dynamic = "force-dynamic";
export default async function DashboardPage() {
const [projects, connections] = await Promise.all([
prisma.project.findMany({
include: projectListInclude,
orderBy: [{ favorite: "desc" }, { updatedAt: "desc" }],
}),
prisma.gitConnection.findMany({
select: connectionSafeSelect,
orderBy: { name: "asc" },
}),
]);
const active = projects.filter((project) => project.status !== "ARCHIVED");
const ideas = active.filter((project) => project.status === "IDEA").length;
const inDev = active.filter((project) => project.status === "IN_DEVELOPMENT").length;
const done = active.filter((project) => project.status === "DONE").length;
const linked = active.filter((project) => Boolean(project.repoOwner && project.repoName)).length;
const averageProgress =
active.length > 0
? Math.round(active.reduce((sum, project) => sum + project.progress, 0) / active.length)
: 0;
return (
<div className="space-y-6">
<header className="fade-up pt-4">
<p className="text-[11px] font-semibold uppercase tracking-[0.3em] text-violet-300/70">
Vibe Coding Kontrollzentrum
</p>
<h1 className="mt-2 text-3xl font-semibold tracking-tight text-white sm:text-4xl">
Deine Projekte auf{" "}
<span className="text-gradient">einen Blick</span>
</h1>
<p className="mt-2 max-w-2xl text-sm text-slate-400">
Ideen festhalten, Status pflegen, Notizen sammeln – und live sehen, was in den
verknuepften Repositories bei GitHub, GitLab und Gitea passiert.
</p>
</header>
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-5">
<StatCard
label="Projekte"
value={active.length}
hint={`Ø Fortschritt ${averageProgress}%`}
icon={CircleDot}
color="#a78bfa"
/>
<StatCard
label="Ideen"
value={ideas}
hint={STATUS_META.IDEA.description}
icon={Lightbulb}
color={STATUS_META.IDEA.color}
/>
<StatCard
label="In Entwicklung"
value={inDev}
hint="Aktiv in Arbeit"
icon={Rocket}
color={STATUS_META.IN_DEVELOPMENT.color}
/>
<StatCard
label="Fertig"
value={done}
hint="Ausgeliefert"
icon={Trophy}
color={STATUS_META.DONE.color}
/>
<StatCard
label="Repos verknuepft"
value={linked}
hint={`${connections.length} Git-Verbindung${connections.length === 1 ? "" : "en"}`}
icon={GitBranch}
color="#38bdf8"
/>
</div>
<ProjectBoard
projects={projects as unknown as ProjectListItem[]}
connections={connections as unknown as ConnectionSafe[]}
/>
</div>
);
}