diff --git a/src/features/prozess/components/phase-card.tsx b/src/features/prozess/components/phase-card.tsx
new file mode 100644
index 0000000..df63bf9
--- /dev/null
+++ b/src/features/prozess/components/phase-card.tsx
@@ -0,0 +1,53 @@
+import { Badge } from "@/shared/components/ui/badge";
+import { Card, CardContent } from "@/shared/components/ui/card";
+import { cn } from "@/shared/lib/utils";
+
+type PhaseStatus = "erledigt" | "aktuell" | "offen";
+
+interface PhaseCardProps {
+ label: string;
+ beschreibung: string;
+ status: PhaseStatus;
+ index: number;
+}
+
+export function PhaseCard({
+ label,
+ beschreibung,
+ status,
+ index,
+}: PhaseCardProps) {
+ return (
+
+
+
+ {status === "erledigt" ? "✓" : index + 1}
+
+
+
+ {label}
+ {status === "aktuell" && Aktuell}
+
+ {status === "aktuell" && (
+
{beschreibung}
+ )}
+
+
+
+ );
+}
diff --git a/src/features/prozess/components/process-stepper.tsx b/src/features/prozess/components/process-stepper.tsx
new file mode 100644
index 0000000..90bf489
--- /dev/null
+++ b/src/features/prozess/components/process-stepper.tsx
@@ -0,0 +1,71 @@
+import {
+ Card,
+ CardContent,
+ CardHeader,
+ CardTitle,
+} from "@/shared/components/ui/card";
+import type { ProzessSchritt } from "@/shared/db/schema";
+import { PROZESS_SCHRITTE } from "@/shared/lib/constants";
+import { PhaseCard } from "./phase-card";
+
+interface ProcessStepperProps {
+ aktuellerSchritt: ProzessSchritt;
+ kontaktGesamt: number;
+ absagen: number;
+}
+
+export function ProcessStepper({
+ aktuellerSchritt,
+ kontaktGesamt,
+ absagen,
+}: ProcessStepperProps) {
+ const currentIndex = PROZESS_SCHRITTE.findIndex(
+ (s) => s.key === aktuellerSchritt,
+ );
+
+ return (
+
+
+
Dein Fortschritt
+
+ Schritt {currentIndex + 1} von {PROZESS_SCHRITTE.length}
+
+
+
+
+ {PROZESS_SCHRITTE.map((schritt, i) => {
+ const status =
+ i < currentIndex
+ ? "erledigt"
+ : i === currentIndex
+ ? "aktuell"
+ : "offen";
+
+ return (
+
+ );
+ })}
+
+
+ {currentIndex >= 3 && (
+
+
+ Kontaktübersicht
+
+
+
+ {kontaktGesamt} Kontaktversuche insgesamt, davon {absagen}{" "}
+ Absagen.
+
+
+
+ )}
+
+ );
+}
diff --git a/src/features/prozess/hooks.ts b/src/features/prozess/hooks.ts
new file mode 100644
index 0000000..f286f50
--- /dev/null
+++ b/src/features/prozess/hooks.ts
@@ -0,0 +1,40 @@
+import type { ProzessSchritt } from "@/shared/db/schema";
+import { dbExec, useDbQuery } from "@/shared/hooks/use-db";
+
+interface NutzerRow {
+ id: number;
+ name: string;
+ aktueller_schritt: ProzessSchritt;
+ dringlichkeitscode: boolean;
+ tss_beantragt: boolean;
+ krankenkasse: string;
+}
+
+interface KontaktStats {
+ gesamt: number;
+ absagen: number;
+ warteliste: number;
+ keine_antwort: number;
+}
+
+export function useNutzer() {
+ return useDbQuery("SELECT * FROM nutzer LIMIT 1");
+}
+
+export function useKontaktStats() {
+ return useDbQuery(`
+ SELECT
+ COUNT(*) as gesamt,
+ COUNT(*) FILTER (WHERE ergebnis = 'absage') as absagen,
+ COUNT(*) FILTER (WHERE ergebnis = 'warteliste') as warteliste,
+ COUNT(*) FILTER (WHERE ergebnis = 'keine_antwort') as keine_antwort
+ FROM kontakt
+ `);
+}
+
+export async function updateSchritt(schritt: ProzessSchritt) {
+ await dbExec(
+ "UPDATE nutzer SET aktueller_schritt = $1, aktualisiert_am = NOW() WHERE id = 1",
+ [schritt],
+ );
+}
diff --git a/src/features/prozess/index.ts b/src/features/prozess/index.ts
new file mode 100644
index 0000000..13362ee
--- /dev/null
+++ b/src/features/prozess/index.ts
@@ -0,0 +1,2 @@
+export { ProcessStepper } from "./components/process-stepper";
+export { updateSchritt, useKontaktStats, useNutzer } from "./hooks";
diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts
index 74becb9..eff7f13 100644
--- a/src/routeTree.gen.ts
+++ b/src/routeTree.gen.ts
@@ -8,70 +8,88 @@
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
-import { Route as rootRouteImport } from "./routes/__root";
-import { Route as IndexRouteImport } from "./routes/index";
-import { Route as OnboardingIndexRouteImport } from "./routes/onboarding/index";
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as OnboardingIndexRouteImport } from './routes/onboarding/index'
+import { Route as ProzessIndexRouteImport } from './routes/prozess/index'
const IndexRoute = IndexRouteImport.update({
- id: "/",
- path: "/",
- getParentRoute: () => rootRouteImport,
-} as any);
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const ProzessIndexRoute = ProzessIndexRouteImport.update({
+ id: '/prozess/',
+ path: '/prozess/',
+ getParentRoute: () => rootRouteImport,
+} as any)
const OnboardingIndexRoute = OnboardingIndexRouteImport.update({
- id: "/onboarding/",
- path: "/onboarding/",
- getParentRoute: () => rootRouteImport,
-} as any);
+ id: '/onboarding/',
+ path: '/onboarding/',
+ getParentRoute: () => rootRouteImport,
+} as any)
export interface FileRoutesByFullPath {
- "/": typeof IndexRoute;
- "/onboarding/": typeof OnboardingIndexRoute;
+ '/': typeof IndexRoute
+ '/onboarding/': typeof OnboardingIndexRoute
+ '/prozess/': typeof ProzessIndexRoute
}
export interface FileRoutesByTo {
- "/": typeof IndexRoute;
- "/onboarding": typeof OnboardingIndexRoute;
+ '/': typeof IndexRoute
+ '/onboarding': typeof OnboardingIndexRoute
+ '/prozess': typeof ProzessIndexRoute
}
export interface FileRoutesById {
- __root__: typeof rootRouteImport;
- "/": typeof IndexRoute;
- "/onboarding/": typeof OnboardingIndexRoute;
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/onboarding/': typeof OnboardingIndexRoute
+ '/prozess/': typeof ProzessIndexRoute
}
export interface FileRouteTypes {
- fileRoutesByFullPath: FileRoutesByFullPath;
- fullPaths: "/" | "/onboarding/";
- fileRoutesByTo: FileRoutesByTo;
- to: "/" | "/onboarding";
- id: "__root__" | "/" | "/onboarding/";
- fileRoutesById: FileRoutesById;
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/onboarding/' | '/prozess/'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/onboarding' | '/prozess'
+ id: '__root__' | '/' | '/onboarding/' | '/prozess/'
+ fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
- IndexRoute: typeof IndexRoute;
- OnboardingIndexRoute: typeof OnboardingIndexRoute;
+ IndexRoute: typeof IndexRoute
+ OnboardingIndexRoute: typeof OnboardingIndexRoute
+ ProzessIndexRoute: typeof ProzessIndexRoute
}
-declare module "@tanstack/react-router" {
- interface FileRoutesByPath {
- "/": {
- id: "/";
- path: "/";
- fullPath: "/";
- preLoaderRoute: typeof IndexRouteImport;
- parentRoute: typeof rootRouteImport;
- };
- "/onboarding/": {
- id: "/onboarding/";
- path: "/onboarding";
- fullPath: "/onboarding/";
- preLoaderRoute: typeof OnboardingIndexRouteImport;
- parentRoute: typeof rootRouteImport;
- };
- }
+declare module '@tanstack/react-router' {
+ interface FileRoutesByPath {
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/prozess/': {
+ id: '/prozess/'
+ path: '/prozess'
+ fullPath: '/prozess/'
+ preLoaderRoute: typeof ProzessIndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/onboarding/': {
+ id: '/onboarding/'
+ path: '/onboarding'
+ fullPath: '/onboarding/'
+ preLoaderRoute: typeof OnboardingIndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
}
const rootRouteChildren: RootRouteChildren = {
- IndexRoute: IndexRoute,
- OnboardingIndexRoute: OnboardingIndexRoute,
-};
+ IndexRoute: IndexRoute,
+ OnboardingIndexRoute: OnboardingIndexRoute,
+ ProzessIndexRoute: ProzessIndexRoute,
+}
export const routeTree = rootRouteImport
- ._addFileChildren(rootRouteChildren)
- ._addFileTypes();
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/src/routes/index.tsx b/src/routes/index.tsx
index 5bd0f60..aa0a363 100644
--- a/src/routes/index.tsx
+++ b/src/routes/index.tsx
@@ -8,7 +8,6 @@ export const Route = createFileRoute("/")({
if (result.rows.length === 0) {
throw redirect({ to: "/onboarding" });
}
- // /prozess route will be added in a future task
- throw redirect({ to: "/onboarding" as string });
+ throw redirect({ to: "/prozess" });
},
});
diff --git a/src/routes/prozess/index.tsx b/src/routes/prozess/index.tsx
new file mode 100644
index 0000000..bb1e369
--- /dev/null
+++ b/src/routes/prozess/index.tsx
@@ -0,0 +1,25 @@
+import { createFileRoute } from "@tanstack/react-router";
+import { ProcessStepper } from "@/features/prozess/components/process-stepper";
+import { useKontaktStats, useNutzer } from "@/features/prozess/hooks";
+
+export const Route = createFileRoute("/prozess/")({
+ component: ProzessPage,
+});
+
+function ProzessPage() {
+ const { data: nutzer, loading: nutzerLoading } = useNutzer();
+ const { data: stats, loading: statsLoading } = useKontaktStats();
+
+ if (nutzerLoading || statsLoading) return Laden…
;
+ if (!nutzer[0]) return Bitte zuerst das Onboarding abschließen.
;
+
+ const s = stats[0] ?? { gesamt: 0, absagen: 0 };
+
+ return (
+
+ );
+}