diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx
new file mode 100644
index 0000000..117726b
--- /dev/null
+++ b/app/dashboard/page.tsx
@@ -0,0 +1,5 @@
+import { DashboardPlatzhalterSeite } from "@/features/dashboard/pages/DashboardPlatzhalterSeite";
+
+export default function Page() {
+ return ;
+}
diff --git a/docs/NAVIGATION.md b/docs/NAVIGATION.md
index 380bc3d..42856f5 100644
--- a/docs/NAVIGATION.md
+++ b/docs/NAVIGATION.md
@@ -9,7 +9,7 @@ was dazukommt, nicht was schon da ist.
```
HIVEGRID
-├── Dashboard eigenes Feature, nicht Teil dieses Prototyps
+├── Dashboard Platzhalterseite — eigenes Feature, hier nicht gestaltet
└── Analytics ◄── der Prototyp lebt hier /analytics
├─────────────────────
└── Komponenten Bausteingalerie des Prototyps /komponenten
@@ -60,8 +60,11 @@ Leads mit seinen acht. Drei Gründe geben den Ausschlag:
Ein weiterer Vorteil: Die Leiste wächst. Käme eine sechste Analyse-Seite dazu,
ist das eine Zeile mehr — ein Reiterband würde eng.
-„Dashboard" führt hier nirgendwohin und meldet das beim Klick — es ist ein
-eigenes Feature, siehe [SCOPE-ANALYTICS-DASHBOARD.md](SCOPE-ANALYTICS-DASHBOARD.md).
+„Dashboard" führt auf eine **Platzhalterseite** unter `/dashboard`: ein
+Leerzustand, der erklärt, warum hier nichts steht und was später hinkommt. Ein
+Menüpunkt, der nur einen Hinweis auslöst, lässt offen, ob etwas fehlt oder
+etwas kaputt ist. Siehe
+[SCOPE-ANALYTICS-DASHBOARD.md](SCOPE-ANALYTICS-DASHBOARD.md).
---
diff --git a/features/dashboard/components/layout/AppShell.tsx b/features/dashboard/components/layout/AppShell.tsx
index cc980a7..bfddbd0 100644
--- a/features/dashboard/components/layout/AppShell.tsx
+++ b/features/dashboard/components/layout/AppShell.tsx
@@ -7,7 +7,6 @@ import { FeatherBell, FeatherHelpCircle, FeatherSearch } from "@subframe/core";
import { SidebarWithSections } from "@/ui/components/SidebarWithSections";
import { NAV_GRUPPEN, NAV_SEKUNDAER, ANALYSE_SEITEN, type NavEintrag } from "./navigation";
import { FeatherIcon } from "./FeatherIcon";
-import { useToast } from "../../context/ToastContext";
import { DATENSATZ_INFO } from "../../data/seed";
import { formatDatum, formatZahl } from "../../utils/formatters";
import { HEUTE } from "../../utils/dateUtils";
@@ -20,24 +19,27 @@ import { HEUTE } from "../../utils/dateUtils";
* Software öffnet, soll dieselbe Navigation sehen; die Übergabe wird dadurch
* eine Frage des Einsetzens und nicht des Übersetzens.
*
- * **Nur „Analytics" führt hier irgendwohin.** Die übrigen Einträge zeigen den
- * Ort in der fertigen Anwendung und melden beim Klick, dass sie zur Software
- * gehören — eine tote Schaltfläche ohne Rückmeldung wäre schlechter als gar
- * keine.
+ * Jeder Eintrag führt auf eine echte Seite. „Dashboard" landet auf einer
+ * Platzhalterseite, weil es ein eigenes Feature ist und hier nicht gestaltet
+ * wird — aber ein Menüpunkt, der nur einen Hinweis auslöst, lässt offen, ob
+ * etwas fehlt oder etwas kaputt ist.
*/
export function AppShell({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
- const { zeigeToast } = useToast();
const [eingeklappt, setEingeklappt] = React.useState(false);
const istAktiv = (eintrag: NavEintrag) =>
- eintrag.ziel.art === "route" &&
- (eintrag.ziel.href === "/analytics"
+ eintrag.ziel.href === "/analytics"
? pathname.startsWith("/analytics")
- : pathname.startsWith(eintrag.ziel.href));
+ : pathname === eintrag.ziel.href;
- const renderEintrag = (eintrag: NavEintrag) => {
- const item = (
+ const renderEintrag = (eintrag: NavEintrag) => (
+
}
selected={istAktiv(eintrag)}
@@ -45,40 +47,17 @@ export function AppShell({ children }: { children: React.ReactNode }) {
>
{eintrag.label}
- );
-
- if (eintrag.ziel.art === "route") {
- return (
-
- {item}
-
- );
- }
-
- return (
-
- );
- };
+
+ );
const aktiveAnalyse = ANALYSE_SEITEN.find((s) =>
s.href === "/analytics" ? pathname === "/analytics" : pathname.startsWith(s.href)
);
- const bereich = pathname.startsWith("/komponenten") ? "Komponenten" : "Analytics";
+ const bereich = pathname.startsWith("/komponenten")
+ ? "Komponenten"
+ : pathname.startsWith("/dashboard")
+ ? "Dashboard"
+ : "Analytics";
return (
diff --git a/features/dashboard/components/layout/PageHeader.tsx b/features/dashboard/components/layout/PageHeader.tsx
index 74eba73..21c6e0d 100644
--- a/features/dashboard/components/layout/PageHeader.tsx
+++ b/features/dashboard/components/layout/PageHeader.tsx
@@ -13,10 +13,17 @@ export function PageHeader({
titel,
beschreibung,
aktionen,
+ /**
+ * Aktualisieren, Export und Teilen. Auf Seiten ohne Inhalt gibt es nichts zu
+ * aktualisieren oder zu exportieren — dort schaltet man sie ab, statt
+ * Schaltflächen anzubieten, die ins Leere greifen.
+ */
+ standardAktionen = true,
}: {
titel: string;
beschreibung: string;
aktionen?: React.ReactNode;
+ standardAktionen?: boolean;
}) {
const { zeigeToast } = useToast();
@@ -31,6 +38,8 @@ export function PageHeader({
);
diff --git a/features/dashboard/components/layout/navigation.ts b/features/dashboard/components/layout/navigation.ts
index d45ce89..fe6bb45 100644
--- a/features/dashboard/components/layout/navigation.ts
+++ b/features/dashboard/components/layout/navigation.ts
@@ -12,12 +12,12 @@ import type { SeitenId } from "../../data/filtering";
* (`ui/components/SidebarWithSections`, aus der Cloud-Fassung portiert), damit
* Aussehen und Verhalten übereinstimmen.
*
- * „Dashboard" führt hier nirgendwohin: Es ist ein eigenes Feature und nicht
- * Teil dieses Prototyps (siehe `docs/SCOPE-ANALYTICS-DASHBOARD.md`). Der
- * Eintrag meldet das beim Klick, statt still zu bleiben.
+ * „Dashboard" führt auf eine Platzhalterseite: Es ist ein eigenes Feature und
+ * in diesem Prototyp nicht gestaltet (siehe
+ * `docs/SCOPE-ANALYTICS-DASHBOARD.md`).
*/
-export type NavZiel = { art: "route"; href: string } | { art: "software" };
+export type NavZiel = { art: "route"; href: string };
export interface NavEintrag {
id: string;
@@ -43,12 +43,13 @@ export const NAV_GRUPPEN: NavGruppe[] = [
titel: "",
eintraege: [
{
- // Gehört zu uns, ist aber ein eigenes Feature und nicht Teil dieses
- // Prototyps — siehe docs/SCOPE-ANALYTICS-DASHBOARD.md.
+ // Eigenes Feature, in diesem Prototyp nicht gestaltet. Der Punkt führt
+ // trotzdem auf eine Seite: Ein Menüeintrag, der nur einen Hinweis
+ // auslöst, lässt offen, ob etwas fehlt oder etwas kaputt ist.
id: "dashboard",
label: "Dashboard",
icon: "LayoutDashboard",
- ziel: { art: "software" },
+ ziel: { art: "route", href: "/dashboard" },
},
{
// Der neue Menüpunkt. Die fünf Analyse-Seiten sind ausschließlich hier
diff --git a/features/dashboard/pages/DashboardPlatzhalterSeite.tsx b/features/dashboard/pages/DashboardPlatzhalterSeite.tsx
new file mode 100644
index 0000000..9e56f98
--- /dev/null
+++ b/features/dashboard/pages/DashboardPlatzhalterSeite.tsx
@@ -0,0 +1,69 @@
+"use client";
+
+import Link from "next/link";
+import { FeatherArrowRight } from "@subframe/core";
+import { Button } from "@/ui/components/Button";
+import { EmptyState } from "@/ui/components/EmptyState";
+import { PageHeader } from "../components/layout/PageHeader";
+import { PageContainer } from "../components/layout/PageContainer";
+import { ANALYTICS_START } from "../components/layout/navigation";
+
+/**
+ * Platzhalter für das Dashboard.
+ *
+ * Das Dashboard ist ein eigenes Feature und in diesem Prototyp nicht
+ * gestaltet — siehe `docs/SCOPE-ANALYTICS-DASHBOARD.md`. Der Menüpunkt führt
+ * trotzdem auf eine echte Seite statt nur einen Hinweis auszulösen: Ein
+ * Navigationspunkt, der nirgendwohin führt, lässt den Leser im Unklaren, ob
+ * etwas fehlt oder etwas kaputt ist. Diese Seite sagt es ihm.
+ *
+ * Der Inhalt beschreibt, was hier später steht — nicht als Gestaltung, sondern
+ * als Verabredung. So bleibt die Seite ehrlich leer und trotzdem nützlich.
+ */
+export function DashboardPlatzhalterSeite() {
+ return (
+ <>
+
+
+
+
+
+ Analytics und Dashboard sind zwei Features. Dieser Prototyp
+ gestaltet Analytics — den Analysebereich mit
+ Zeitraum- und Dimensionsfiltern. Das Dashboard beantwortet eine
+ andere Frage: {"„Wie ist meine Lage?“ statt „Warum ist das so?“."}
+
+
+ Geplant sind drei feste Rollen-Layouts (Geschäftsführung, Büro,
+ Monteur) aus Widgets. Ein Teil davon kommt aus Analytics —
+ Kennzahlen-Kacheln, Trend-Diagramme, Listen. Der andere Teil
+ nicht: offene HR-Anträge, eigene Aufgaben, Termine des Tages.
+
+
+ Zuschnitt und offene Punkte stehen in{" "}
+
+ docs/SCOPE-ANALYTICS-DASHBOARD.md
+
+
+
+ }
+ action={
+
+ }>
+ Zu Analytics
+
+
+ }
+ />
+
+ >
+ );
+}
diff --git a/scripts/verify-layout.mjs b/scripts/verify-layout.mjs
index 58ce6a3..d3aede3 100644
--- a/scripts/verify-layout.mjs
+++ b/scripts/verify-layout.mjs
@@ -19,6 +19,7 @@ import { chromium } from "playwright-core";
const BASIS = process.env.DASHBOARD_URL ?? "http://localhost:4300";
const SEITEN = [
+ "/dashboard",
"/analytics",
"/analytics/finanzen",
"/analytics/auftraege",
diff --git a/ui/components/EmptyState/EmptyState.tsx b/ui/components/EmptyState/EmptyState.tsx
index 4ba5566..2cc99a4 100644
--- a/ui/components/EmptyState/EmptyState.tsx
+++ b/ui/components/EmptyState/EmptyState.tsx
@@ -7,103 +7,68 @@
import React from "react";
import * as SubframeUtils from "../../utils";
-import { Button } from "../Button";
-export interface EmptyStateRootProps
- extends Omit, "title"> {
+export interface EmptyStateRootProps extends Omit, "title"> {
variant?: "default" | "no-results" | "error";
outlined?: boolean;
fillContainer?: boolean;
title?: React.ReactNode;
body?: React.ReactNode;
+ // Optional call-to-action rendered below the text. When omitted (the default),
+ // no button is shown. Replaces the previous hard-coded "Create project" button.
+ action?: React.ReactNode;
className?: string;
}
-const EmptyStateRoot = React.forwardRef(
- function EmptyStateRoot(
- {
- variant = "default",
- outlined = false,
- fillContainer = false,
- title,
- body,
- className,
- ...otherProps
- }: EmptyStateRootProps,
- ref
- ) {
- return (
-
+ );
+});
export const EmptyState = EmptyStateRoot;
diff --git a/ui/components/EmptyState/index.tsx b/ui/components/EmptyState/index.tsx
index 5d5aa8a..20113fe 100644
--- a/ui/components/EmptyState/index.tsx
+++ b/ui/components/EmptyState/index.tsx
@@ -1,10 +1,18 @@
"use client";
-import { EmptyState as EmptyStateComponent } from "./EmptyState";
+import React from "react";
+import { EmptyState as EmptyStateComponent, type EmptyStateRootProps } from "./EmptyState";
/**
* Add wrapper components and business logic here.
* If you modify this file, disable Subframe sync for it to prevent overwrites.
* Learn more: https://docs.subframe.com/concepts/syncing-components#wrapping-components
+ *
+ * History: this wrapper used to add `[&_button]:hidden` to suppress the synced
+ * component's hardcoded, non-wired english CTA ("Create project"). The synced
+ * component has since gained a proper `action` slot and no hardcoded button,
+ * so the class only hid the real action buttons screens pass in (create/retry
+ * CTAs). Do not re-add a blanket button-hiding class here.
*/
-
-export const EmptyState = EmptyStateComponent;
+export const EmptyState = React.forwardRef(function EmptyState(props, ref) {
+ return ;
+});