);
diff --git a/src/features/prozess/components/process-stepper.tsx b/src/features/prozess/components/process-stepper.tsx
index 90bf489..562a4ad 100644
--- a/src/features/prozess/components/process-stepper.tsx
+++ b/src/features/prozess/components/process-stepper.tsx
@@ -1,10 +1,12 @@
-import {
- Card,
- CardContent,
- CardHeader,
- CardTitle,
-} from "@/shared/components/ui/card";
+import { useForm } from "@tanstack/react-form";
+import { Link } from "@tanstack/react-router";
+import { useState } from "react";
+import { useTherapeutenListe } from "@/features/kontakte/hooks";
+import { Button } from "@/shared/components/ui/button";
+import { Label } from "@/shared/components/ui/label";
+import { Separator } from "@/shared/components/ui/separator";
import type { ProzessSchritt } from "@/shared/db/schema";
+import { dbExec } from "@/shared/hooks/use-db";
import { PROZESS_SCHRITTE } from "@/shared/lib/constants";
import { PhaseCard } from "./phase-card";
@@ -12,19 +14,29 @@ interface ProcessStepperProps {
aktuellerSchritt: ProzessSchritt;
kontaktGesamt: number;
absagen: number;
+ onUpdate: () => void;
+}
+
+const inputClasses =
+ "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring";
+
+function todayISO() {
+ const d = new Date();
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
}
export function ProcessStepper({
aktuellerSchritt,
kontaktGesamt,
absagen,
+ onUpdate,
}: ProcessStepperProps) {
const currentIndex = PROZESS_SCHRITTE.findIndex(
(s) => s.key === aktuellerSchritt,
);
return (
-
+
Dein Fortschritt
@@ -48,24 +60,235 @@ export function ProcessStepper({
beschreibung={schritt.beschreibung}
status={status}
index={i}
- />
+ >
+ {status === "aktuell" && (
+
+ )}
+
);
})}
-
- {currentIndex >= 3 && (
-
-
- Kontaktübersicht
-
-
-
- {kontaktGesamt} Kontaktversuche insgesamt, davon {absagen}{" "}
- Absagen.
-
-
-
- )}
);
}
+
+function StepAction({
+ schritt,
+ kontaktGesamt,
+ absagen,
+ onUpdate,
+}: {
+ schritt: ProzessSchritt;
+ kontaktGesamt: number;
+ absagen: number;
+ onUpdate: () => void;
+}) {
+ switch (schritt) {
+ case "neu":
+ return
;
+ case "sprechstunde_absolviert":
+ return (
+
+ );
+ case "diagnose_erhalten":
+ return (
+
+ );
+ case "tss_beantragt":
+ return (
+
+ );
+ case "eigensuche":
+ return (
+ <>
+
+
+ {kontaktGesamt} Kontaktversuche, davon {absagen} Absagen.{" "}
+
+ Kontakte verwalten
+
+
+
+ >
+ );
+ case "antrag_gestellt":
+ return (
+ <>
+
+
+ Dein Kostenerstattungsantrag wurde eingereicht.{" "}
+
+ Zum Antrag
+
+
+ >
+ );
+ default:
+ return null;
+ }
+}
+
+function AdvanceButton({
+ nextStep,
+ label,
+ onDone,
+}: {
+ nextStep: ProzessSchritt;
+ label: string;
+ onDone: () => void;
+}) {
+ return (
+ <>
+
+
+ >
+ );
+}
+
+function SprechstundeForm({ onDone }: { onDone: () => void }) {
+ const { data: therapeuten, loading } = useTherapeutenListe();
+ const [saved, setSaved] = useState(false);
+
+ const form = useForm({
+ defaultValues: {
+ therapeut_id: "",
+ datum: todayISO(),
+ diagnose: "",
+ },
+ onSubmit: async ({ value }) => {
+ const therapeutId = Number(value.therapeut_id);
+ if (!therapeutId) return;
+
+ await dbExec(
+ `INSERT INTO sprechstunde (therapeut_id, datum, ergebnis, diagnose) VALUES ($1, $2, 'erstgespraech', $3)`,
+ [therapeutId, value.datum, value.diagnose || null],
+ );
+ await dbExec(
+ "UPDATE nutzer SET aktueller_schritt = 'sprechstunde_absolviert', aktualisiert_am = NOW() WHERE id = 1",
+ );
+ setSaved(true);
+ onDone();
+ },
+ });
+
+ if (saved) {
+ return (
+
+ Sprechstunde erfasst.
+
+ );
+ }
+
+ return (
+ <>
+
+
+ {(field) => (
+
+
+ {loading ? (
+
Laden…
+ ) : therapeuten.length === 0 ? (
+
+ Lege zuerst unter{" "}
+
+ Kontakte
+ {" "}
+ einen Eintrag an.
+
+ ) : (
+
+ )}
+
+ )}
+
+
+
+ {(field) => (
+
+
+ field.handleChange(e.target.value)}
+ />
+
+ )}
+
+
+
+ {(field) => (
+
+
+ field.handleChange(e.target.value)}
+ />
+
+ )}
+
+
+
+
+ >
+ );
+}
diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts
index fbde9de..4c5e810 100644
--- a/src/routeTree.gen.ts
+++ b/src/routeTree.gen.ts
@@ -10,7 +10,9 @@
import { Route as rootRouteImport } from './routes/__root'
import { Route as AntragIndexRouteImport } from './routes/antrag/index'
+import { Route as EinstellungenIndexRouteImport } from './routes/einstellungen/index'
import { Route as IndexRouteImport } from './routes/index'
+import { Route as KontakteIdRouteImport } from './routes/kontakte/$id'
import { Route as KontakteIndexRouteImport } from './routes/kontakte/index'
import { Route as KontakteNeuRouteImport } from './routes/kontakte/neu'
import { Route as OnboardingIndexRouteImport } from './routes/onboarding/index'
@@ -36,6 +38,11 @@ const KontakteIndexRoute = KontakteIndexRouteImport.update({
path: '/kontakte/',
getParentRoute: () => rootRouteImport,
} as any)
+const EinstellungenIndexRoute = EinstellungenIndexRouteImport.update({
+ id: '/einstellungen/',
+ path: '/einstellungen/',
+ getParentRoute: () => rootRouteImport,
+} as any)
const AntragIndexRoute = AntragIndexRouteImport.update({
id: '/antrag/',
path: '/antrag/',
@@ -46,19 +53,28 @@ const KontakteNeuRoute = KontakteNeuRouteImport.update({
path: '/kontakte/neu',
getParentRoute: () => rootRouteImport,
} as any)
+const KontakteIdRoute = KontakteIdRouteImport.update({
+ id: '/kontakte/$id',
+ path: '/kontakte/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
+ '/kontakte/$id': typeof KontakteIdRoute
'/kontakte/neu': typeof KontakteNeuRoute
'/antrag/': typeof AntragIndexRoute
+ '/einstellungen/': typeof EinstellungenIndexRoute
'/kontakte/': typeof KontakteIndexRoute
'/onboarding/': typeof OnboardingIndexRoute
'/prozess/': typeof ProzessIndexRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
+ '/kontakte/$id': typeof KontakteIdRoute
'/kontakte/neu': typeof KontakteNeuRoute
'/antrag': typeof AntragIndexRoute
+ '/einstellungen': typeof EinstellungenIndexRoute
'/kontakte': typeof KontakteIndexRoute
'/onboarding': typeof OnboardingIndexRoute
'/prozess': typeof ProzessIndexRoute
@@ -66,8 +82,10 @@ export interface FileRoutesByTo {
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
+ '/kontakte/$id': typeof KontakteIdRoute
'/kontakte/neu': typeof KontakteNeuRoute
'/antrag/': typeof AntragIndexRoute
+ '/einstellungen/': typeof EinstellungenIndexRoute
'/kontakte/': typeof KontakteIndexRoute
'/onboarding/': typeof OnboardingIndexRoute
'/prozess/': typeof ProzessIndexRoute
@@ -76,24 +94,30 @@ export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths:
| '/'
+ | '/kontakte/$id'
| '/kontakte/neu'
| '/antrag/'
+ | '/einstellungen/'
| '/kontakte/'
| '/onboarding/'
| '/prozess/'
fileRoutesByTo: FileRoutesByTo
to:
| '/'
+ | '/kontakte/$id'
| '/kontakte/neu'
| '/antrag'
+ | '/einstellungen'
| '/kontakte'
| '/onboarding'
| '/prozess'
id:
| '__root__'
| '/'
+ | '/kontakte/$id'
| '/kontakte/neu'
| '/antrag/'
+ | '/einstellungen/'
| '/kontakte/'
| '/onboarding/'
| '/prozess/'
@@ -101,8 +125,10 @@ export interface FileRouteTypes {
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
+ KontakteIdRoute: typeof KontakteIdRoute
KontakteNeuRoute: typeof KontakteNeuRoute
AntragIndexRoute: typeof AntragIndexRoute
+ EinstellungenIndexRoute: typeof EinstellungenIndexRoute
KontakteIndexRoute: typeof KontakteIndexRoute
OnboardingIndexRoute: typeof OnboardingIndexRoute
ProzessIndexRoute: typeof ProzessIndexRoute
@@ -138,6 +164,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof KontakteIndexRouteImport
parentRoute: typeof rootRouteImport
}
+ '/einstellungen/': {
+ id: '/einstellungen/'
+ path: '/einstellungen'
+ fullPath: '/einstellungen/'
+ preLoaderRoute: typeof EinstellungenIndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
'/antrag/': {
id: '/antrag/'
path: '/antrag'
@@ -152,13 +185,22 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof KontakteNeuRouteImport
parentRoute: typeof rootRouteImport
}
+ '/kontakte/$id': {
+ id: '/kontakte/$id'
+ path: '/kontakte/$id'
+ fullPath: '/kontakte/$id'
+ preLoaderRoute: typeof KontakteIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
}
}
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
+ KontakteIdRoute: KontakteIdRoute,
KontakteNeuRoute: KontakteNeuRoute,
AntragIndexRoute: AntragIndexRoute,
+ EinstellungenIndexRoute: EinstellungenIndexRoute,
KontakteIndexRoute: KontakteIndexRoute,
OnboardingIndexRoute: OnboardingIndexRoute,
ProzessIndexRoute: ProzessIndexRoute,
diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx
index 11b387b..90274d8 100644
--- a/src/routes/__root.tsx
+++ b/src/routes/__root.tsx
@@ -1,58 +1,55 @@
-import { createRootRoute, Link, Outlet } from "@tanstack/react-router";
-import { useThemeStore } from "../shared/hooks/use-theme";
-
-const themeIcon = {
- light: "\u2600",
- dark: "\u263D",
- system: "\u2699",
-} as const;
-const themeNext = {
- light: "dark",
- dark: "system",
- system: "light",
-} as const;
+import {
+ createRootRoute,
+ Link,
+ Outlet,
+ useMatchRoute,
+} from "@tanstack/react-router";
+import { FileText, ListChecks, Settings, Users } from "lucide-react";
export const Route = createRootRoute({
component: () => {
- const { theme, setTheme } = useThemeStore();
+ const matchRoute = useMatchRoute();
+ const isOnboarding = matchRoute({ to: "/onboarding" });
return (
-
-
-
-
+
-
+ {!isOnboarding && (
+
+ )}
);
},
diff --git a/src/routes/einstellungen/index.tsx b/src/routes/einstellungen/index.tsx
new file mode 100644
index 0000000..688872d
--- /dev/null
+++ b/src/routes/einstellungen/index.tsx
@@ -0,0 +1,6 @@
+import { createFileRoute } from "@tanstack/react-router";
+import { SettingsPage } from "@/features/einstellungen/components/settings-page";
+
+export const Route = createFileRoute("/einstellungen/")({
+ component: () =>
,
+});
diff --git a/src/routes/index.tsx b/src/routes/index.tsx
index aa0a363..1fc4c4b 100644
--- a/src/routes/index.tsx
+++ b/src/routes/index.tsx
@@ -1,13 +1,39 @@
-import { createFileRoute, redirect } from "@tanstack/react-router";
+import { createFileRoute, useNavigate } from "@tanstack/react-router";
+import { useEffect, useState } from "react";
import { getDb } from "@/shared/db/client";
export const Route = createFileRoute("/")({
- beforeLoad: async () => {
- const db = await getDb();
- const result = await db.query("SELECT id FROM nutzer LIMIT 1");
- if (result.rows.length === 0) {
- throw redirect({ to: "/onboarding" });
- }
- throw redirect({ to: "/prozess" });
- },
+ component: RootRedirect,
});
+
+function RootRedirect() {
+ const navigate = useNavigate();
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ (async () => {
+ try {
+ const db = await getDb();
+ const result = await db.query("SELECT id FROM nutzer LIMIT 1");
+ if (result.rows.length === 0) {
+ navigate({ to: "/onboarding", replace: true });
+ } else {
+ navigate({ to: "/prozess", replace: true });
+ }
+ } catch (e) {
+ console.error("DB init failed:", e);
+ navigate({ to: "/onboarding", replace: true });
+ } finally {
+ setLoading(false);
+ }
+ })();
+ }, [navigate]);
+
+ if (!loading) return null;
+
+ return (
+
+ );
+}
diff --git a/src/routes/kontakte/$id.tsx b/src/routes/kontakte/$id.tsx
new file mode 100644
index 0000000..a291fe9
--- /dev/null
+++ b/src/routes/kontakte/$id.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from "@tanstack/react-router";
+import { ContactDetail } from "@/features/kontakte/components/contact-detail";
+
+export const Route = createFileRoute("/kontakte/$id")({
+ component: () => {
+ const { id } = Route.useParams();
+ return
;
+ },
+});
diff --git a/src/routes/prozess/index.tsx b/src/routes/prozess/index.tsx
index bb1e369..8c355ca 100644
--- a/src/routes/prozess/index.tsx
+++ b/src/routes/prozess/index.tsx
@@ -7,8 +7,16 @@ export const Route = createFileRoute("/prozess/")({
});
function ProzessPage() {
- const { data: nutzer, loading: nutzerLoading } = useNutzer();
- const { data: stats, loading: statsLoading } = useKontaktStats();
+ const {
+ data: nutzer,
+ loading: nutzerLoading,
+ refetch: refetchNutzer,
+ } = useNutzer();
+ const {
+ data: stats,
+ loading: statsLoading,
+ refetch: refetchStats,
+ } = useKontaktStats();
if (nutzerLoading || statsLoading) return
Laden…
;
if (!nutzer[0]) return
Bitte zuerst das Onboarding abschließen.
;
@@ -20,6 +28,10 @@ function ProzessPage() {
aktuellerSchritt={nutzer[0].aktueller_schritt}
kontaktGesamt={Number(s.gesamt)}
absagen={Number(s.absagen)}
+ onUpdate={() => {
+ refetchNutzer();
+ refetchStats();
+ }}
/>
);
}
diff --git a/src/shared/components/ui/switch.tsx b/src/shared/components/ui/switch.tsx
new file mode 100644
index 0000000..c539629
--- /dev/null
+++ b/src/shared/components/ui/switch.tsx
@@ -0,0 +1,27 @@
+import * as SwitchPrimitive from "@radix-ui/react-switch";
+import type * as React from "react";
+
+import { cn } from "@/shared/lib/utils";
+
+function Switch({
+ className,
+ ...props
+}: React.ComponentProps
) {
+ return (
+
+
+
+ );
+}
+
+export { Switch };
diff --git a/src/shared/lib/constants.ts b/src/shared/lib/constants.ts
index 42a2c72..656b23d 100644
--- a/src/shared/lib/constants.ts
+++ b/src/shared/lib/constants.ts
@@ -12,8 +12,9 @@ export const PROZESS_SCHRITTE: {
}[] = [
{
key: "neu",
- label: "Noch nicht begonnen",
- beschreibung: "Du hast noch keine Schritte unternommen.",
+ label: "Erstgespräch durchführen",
+ beschreibung:
+ "Das Erstgespräch heißt in der Fachsprache psychotherapeutische Sprechstunde. Dort wird eine erste Einschätzung vorgenommen.",
},
{
key: "sprechstunde_absolviert",
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
new file mode 100644
index 0000000..6dea5bb
--- /dev/null
+++ b/src/vite-env.d.ts
@@ -0,0 +1,4 @@
+///
+///
+
+declare const __APP_VERSION__: string;
diff --git a/vite.config.ts b/vite.config.ts
index a1f2cd0..3ca8330 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,3 +1,5 @@
+import { execSync } from "node:child_process";
+import { readFileSync } from "node:fs";
import path from "node:path";
import tailwindcss from "@tailwindcss/vite";
import tanstackRouter from "@tanstack/router-plugin/vite";
@@ -5,14 +7,22 @@ import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
import { VitePWA } from "vite-plugin-pwa";
+const pkg = JSON.parse(readFileSync("./package.json", "utf-8"));
+const gitCount = execSync("git rev-list --count HEAD", {
+ encoding: "utf-8",
+}).trim();
+
export default defineConfig({
base: "/tpf/",
+ define: {
+ __APP_VERSION__: JSON.stringify(`${pkg.version}+${gitCount}`),
+ },
plugins: [
tanstackRouter(),
react(),
tailwindcss(),
VitePWA({
- registerType: "autoUpdate",
+ registerType: "prompt",
includeAssets: ["icons/icon-192.png", "icons/icon-512.png"],
workbox: {
globPatterns: ["**/*.{js,css,html,wasm,data}"],