1250 lines
34 KiB
Markdown
1250 lines
34 KiB
Markdown
# TherapyFinder V1 Implementation Plan
|
|
|
|
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
|
|
**Goal:** Build a local-first PWA that guides GKV-insured users through the psychotherapy access process — from first Sprechstunde to Kostenerstattungsantrag — with structured contact/rejection tracking and PDF export.
|
|
|
|
**Architecture:** Local-first PWA with all data stored in PGlite (IndexedDB). No backend for V1 — sensitive health data never leaves the device. The app is a multi-step process guide with a contact tracker that produces exportable rejection documentation. Therapist directory integration is deferred to V2 (data source still open).
|
|
|
|
**Tech Stack:** Bun, Vite, React 19, TanStack Router (file-based), shadcn/ui + Tailwind CSS v4, PGlite, Zustand, TanStack Form + Zod, vite-plugin-pwa, Vitest, Playwright, Biome
|
|
|
|
---
|
|
|
|
## Scope
|
|
|
|
**In scope (V1):**
|
|
- Project scaffolding and tooling
|
|
- PGlite database with schema and migrations
|
|
- Onboarding flow (capture insurance info, current process step)
|
|
- Process stepper dashboard (5 phases, interactive checklist)
|
|
- Contact tracker (log therapist contacts, outcomes, notes)
|
|
- Rejection list PDF export (for Kostenerstattungsantrag)
|
|
- Kostenerstattungs-Assistent (checklist + guidance)
|
|
- PWA (offline-capable, installable)
|
|
|
|
**Deferred (V2+):**
|
|
- Therapist directory / KV-Verzeichnis integration
|
|
- Push notifications / reminders
|
|
- Multi-language support (i18n)
|
|
- Backend / cloud sync
|
|
|
|
---
|
|
|
|
## File Structure
|
|
|
|
```
|
|
tpf/
|
|
├── .mise.toml
|
|
├── biome.json
|
|
├── package.json
|
|
├── tsconfig.json
|
|
├── vite.config.ts
|
|
├── index.html
|
|
├── public/
|
|
│ ├── manifest.webmanifest
|
|
│ └── icons/
|
|
│ ├── icon-192.png
|
|
│ └── icon-512.png
|
|
├── src/
|
|
│ ├── main.tsx
|
|
│ ├── app.tsx
|
|
│ ├── routes/
|
|
│ │ ├── __root.tsx
|
|
│ │ ├── index.tsx # redirects to /prozess or /onboarding
|
|
│ │ ├── onboarding/
|
|
│ │ │ └── index.tsx # Onboarding form
|
|
│ │ ├── prozess/
|
|
│ │ │ └── index.tsx # Process stepper dashboard
|
|
│ │ ├── kontakte/
|
|
│ │ │ ├── index.tsx # Contact list
|
|
│ │ │ └── neu.tsx # Add/edit contact
|
|
│ │ └── antrag/
|
|
│ │ └── index.tsx # Kostenerstattungs-Assistent
|
|
│ ├── features/
|
|
│ │ ├── onboarding/
|
|
│ │ │ ├── components/
|
|
│ │ │ │ └── onboarding-form.tsx
|
|
│ │ │ ├── schema.ts
|
|
│ │ │ └── index.ts
|
|
│ │ ├── prozess/
|
|
│ │ │ ├── components/
|
|
│ │ │ │ ├── process-stepper.tsx
|
|
│ │ │ │ └── phase-card.tsx
|
|
│ │ │ ├── schema.ts
|
|
│ │ │ ├── hooks.ts
|
|
│ │ │ └── index.ts
|
|
│ │ ├── kontakte/
|
|
│ │ │ ├── components/
|
|
│ │ │ │ ├── contact-form.tsx
|
|
│ │ │ │ ├── contact-list.tsx
|
|
│ │ │ │ └── contact-card.tsx
|
|
│ │ │ ├── schema.ts
|
|
│ │ │ ├── hooks.ts
|
|
│ │ │ └── index.ts
|
|
│ │ └── antrag/
|
|
│ │ ├── components/
|
|
│ │ │ ├── antrag-checklist.tsx
|
|
│ │ │ └── pdf-export-button.tsx
|
|
│ │ ├── schema.ts
|
|
│ │ ├── pdf.ts # PDF generation logic
|
|
│ │ └── index.ts
|
|
│ └── shared/
|
|
│ ├── components/
|
|
│ │ └── ui/ # shadcn/ui components (generated)
|
|
│ ├── hooks/
|
|
│ │ └── use-db.ts # PGlite query hook
|
|
│ ├── db/
|
|
│ │ ├── client.ts # PGlite singleton
|
|
│ │ ├── migrations/
|
|
│ │ │ └── 001_init.sql
|
|
│ │ └── schema.ts # Zod schemas mirroring DB tables
|
|
│ └── lib/
|
|
│ ├── utils.ts # cn() and helpers
|
|
│ └── constants.ts # Process phases, enum labels
|
|
├── e2e/
|
|
│ ├── onboarding.spec.ts
|
|
│ └── kontakte.spec.ts
|
|
└── scripts/
|
|
└── deploy.sh
|
|
```
|
|
|
|
---
|
|
|
|
## Chunk 1: Project Scaffolding
|
|
|
|
### Task 1: Initialize project with Vite + React + TypeScript
|
|
|
|
**Files:**
|
|
- Create: `.mise.toml`
|
|
- Create: `package.json` (via `bun create`)
|
|
- Create: `tsconfig.json`
|
|
- Create: `vite.config.ts`
|
|
- Create: `index.html`
|
|
- Create: `src/main.tsx`
|
|
- Create: `biome.json`
|
|
|
|
- [ ] **Step 1: Create `.mise.toml`**
|
|
|
|
```toml
|
|
[tools]
|
|
bun = "1.2.5"
|
|
```
|
|
|
|
- [ ] **Step 2: Install Bun via mise**
|
|
|
|
Run: `mise install`
|
|
|
|
- [ ] **Step 3: Scaffold Vite project**
|
|
|
|
Run: `bun create vite . --template react-ts`
|
|
|
|
Accept overwrite prompts for existing files. This generates `package.json`, `tsconfig.json`, `vite.config.ts`, `index.html`, `src/main.tsx`, `src/App.tsx`, etc.
|
|
|
|
- [ ] **Step 4: Install dependencies**
|
|
|
|
```bash
|
|
bun add react@latest react-dom@latest
|
|
bun add -d @types/react @types/react-dom typescript vite @vitejs/plugin-react
|
|
```
|
|
|
|
- [ ] **Step 5: Configure Biome**
|
|
|
|
Create `biome.json`:
|
|
```json
|
|
{
|
|
"$schema": "https://biomejs.dev/schemas/2.0.0-beta.1/schema.json",
|
|
"organizeImports": {
|
|
"enabled": true
|
|
},
|
|
"formatter": {
|
|
"indentStyle": "tab"
|
|
},
|
|
"linter": {
|
|
"enabled": true,
|
|
"rules": {
|
|
"recommended": true
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Run: `bun add -d @biomejs/biome`
|
|
|
|
- [ ] **Step 6: Add scripts to `package.json`**
|
|
|
|
Ensure these scripts exist:
|
|
```json
|
|
{
|
|
"scripts": {
|
|
"dev": "vite",
|
|
"build": "tsc && vite build",
|
|
"preview": "vite preview",
|
|
"test": "vitest run",
|
|
"e2e": "playwright test",
|
|
"lint": "biome check .",
|
|
"format": "biome check --write ."
|
|
}
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 7: Verify dev server starts**
|
|
|
|
Run: `bun dev` — should open on `http://localhost:5173` with the default Vite template.
|
|
|
|
- [ ] **Step 8: Initialize git repo and commit**
|
|
|
|
```bash
|
|
git init
|
|
```
|
|
|
|
Add `.gitignore` with: `node_modules/`, `dist/`, `.env`, `.env.local`, `.mise.local.toml`
|
|
|
|
```bash
|
|
git add -A && git commit -m "scaffold vite + react + typescript project"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 2: Add TanStack Router (file-based routing)
|
|
|
|
**Files:**
|
|
- Modify: `vite.config.ts`
|
|
- Modify: `src/main.tsx`
|
|
- Create: `src/app.tsx`
|
|
- Create: `src/routes/__root.tsx`
|
|
- Create: `src/routes/index.tsx`
|
|
|
|
- [ ] **Step 1: Install TanStack Router**
|
|
|
|
```bash
|
|
bun add @tanstack/react-router @tanstack/router-plugin
|
|
```
|
|
|
|
- [ ] **Step 2: Configure Vite plugin**
|
|
|
|
Update `vite.config.ts`:
|
|
```ts
|
|
import { TanStackRouterVite } from "@tanstack/router-plugin/vite"
|
|
import react from "@vitejs/plugin-react"
|
|
import { defineConfig } from "vite"
|
|
|
|
export default defineConfig({
|
|
plugins: [TanStackRouterVite(), react()],
|
|
})
|
|
```
|
|
|
|
- [ ] **Step 3: Create root layout**
|
|
|
|
Create `src/routes/__root.tsx`:
|
|
```tsx
|
|
import { Outlet, createRootRoute } from "@tanstack/react-router"
|
|
|
|
export const Route = createRootRoute({
|
|
component: () => (
|
|
<div className="min-h-screen bg-background text-foreground">
|
|
<main className="mx-auto max-w-2xl px-4 py-6">
|
|
<Outlet />
|
|
</main>
|
|
</div>
|
|
),
|
|
})
|
|
```
|
|
|
|
- [ ] **Step 4: Create index route**
|
|
|
|
Create `src/routes/index.tsx`:
|
|
```tsx
|
|
import { createFileRoute } from "@tanstack/react-router"
|
|
|
|
export const Route = createFileRoute("/")({
|
|
component: () => <h1 className="text-2xl font-bold">TherapyFinder</h1>,
|
|
})
|
|
```
|
|
|
|
- [ ] **Step 5: Create app entry**
|
|
|
|
Create `src/app.tsx`:
|
|
```tsx
|
|
import { RouterProvider, createRouter } from "@tanstack/react-router"
|
|
import { routeTree } from "./routeTree.gen"
|
|
|
|
const router = createRouter({ routeTree })
|
|
|
|
declare module "@tanstack/react-router" {
|
|
interface Register {
|
|
router: typeof router
|
|
}
|
|
}
|
|
|
|
export function App() {
|
|
return <RouterProvider router={router} />
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 6: Update `src/main.tsx`**
|
|
|
|
```tsx
|
|
import { StrictMode } from "react"
|
|
import { createRoot } from "react-dom/client"
|
|
import { App } from "./app"
|
|
import "./index.css"
|
|
|
|
createRoot(document.getElementById("root")!).render(
|
|
<StrictMode>
|
|
<App />
|
|
</StrictMode>,
|
|
)
|
|
```
|
|
|
|
- [ ] **Step 7: Delete unused template files**
|
|
|
|
Remove: `src/App.tsx`, `src/App.css`, `src/assets/` (Vite template leftovers).
|
|
|
|
- [ ] **Step 8: Verify routing works**
|
|
|
|
Run: `bun dev` — navigate to `/` and confirm "TherapyFinder" heading appears.
|
|
|
|
- [ ] **Step 9: Commit**
|
|
|
|
```bash
|
|
git add -A && git commit -m "add tanstack router with file-based routing"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 3: Add Tailwind CSS v4 + shadcn/ui
|
|
|
|
**Files:**
|
|
- Modify: `package.json`
|
|
- Modify: `src/index.css`
|
|
- Create: `src/shared/lib/utils.ts`
|
|
- Create: `src/shared/components/ui/` (generated by shadcn)
|
|
|
|
- [ ] **Step 1: Install Tailwind CSS v4**
|
|
|
|
```bash
|
|
bun add tailwindcss @tailwindcss/vite
|
|
```
|
|
|
|
Update `vite.config.ts` to add the Tailwind plugin:
|
|
```ts
|
|
import tailwindcss from "@tailwindcss/vite"
|
|
import { TanStackRouterVite } from "@tanstack/router-plugin/vite"
|
|
import react from "@vitejs/plugin-react"
|
|
import { defineConfig } from "vite"
|
|
|
|
export default defineConfig({
|
|
plugins: [TanStackRouterVite(), react(), tailwindcss()],
|
|
})
|
|
```
|
|
|
|
- [ ] **Step 2: Configure CSS entry**
|
|
|
|
Replace `src/index.css` contents with:
|
|
```css
|
|
@import "tailwindcss";
|
|
```
|
|
|
|
- [ ] **Step 3: Install and configure shadcn/ui**
|
|
|
|
Run: `bunx shadcn@latest init`
|
|
|
|
Select: New York style, Zinc color, CSS variables: yes.
|
|
|
|
This creates `components.json` and sets up the `src/shared/components/ui/` directory.
|
|
|
|
- [ ] **Step 4: Create `cn()` utility**
|
|
|
|
Create `src/shared/lib/utils.ts`:
|
|
```ts
|
|
import { type ClassValue, clsx } from "clsx"
|
|
import { twMerge } from "tailwind-merge"
|
|
|
|
export function cn(...inputs: ClassValue[]) {
|
|
return twMerge(clsx(inputs))
|
|
}
|
|
```
|
|
|
|
Run: `bun add clsx tailwind-merge`
|
|
|
|
- [ ] **Step 5: Add initial shadcn components**
|
|
|
|
```bash
|
|
bunx shadcn@latest add button card badge progress separator
|
|
```
|
|
|
|
- [ ] **Step 6: Verify styling works**
|
|
|
|
Update `src/routes/index.tsx` to use a Button, run `bun dev`, confirm styled button renders.
|
|
|
|
- [ ] **Step 7: Commit**
|
|
|
|
```bash
|
|
git add -A && git commit -m "add tailwind css v4, shadcn/ui with initial components"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 4: Add PGlite (local database)
|
|
|
|
**Files:**
|
|
- Create: `src/shared/db/client.ts`
|
|
- Create: `src/shared/db/migrations/001_init.sql`
|
|
- Create: `src/shared/db/schema.ts`
|
|
- Create: `src/shared/hooks/use-db.ts`
|
|
|
|
- [ ] **Step 1: Install PGlite**
|
|
|
|
Run: `bun add @electric-sql/pglite`
|
|
|
|
- [ ] **Step 2: Create PGlite singleton**
|
|
|
|
Create `src/shared/db/client.ts`:
|
|
```ts
|
|
import { PGlite } from "@electric-sql/pglite"
|
|
|
|
let _db: PGlite | null = null
|
|
|
|
export async function getDb(): Promise<PGlite> {
|
|
if (!_db) {
|
|
_db = new PGlite("idb://therapyfinder")
|
|
await runMigrations(_db)
|
|
}
|
|
return _db
|
|
}
|
|
|
|
async function runMigrations(db: PGlite): Promise<void> {
|
|
await db.exec(`
|
|
CREATE TABLE IF NOT EXISTS _migrations (
|
|
id SERIAL PRIMARY KEY,
|
|
name TEXT NOT NULL UNIQUE,
|
|
applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
)
|
|
`)
|
|
|
|
const migrations = import.meta.glob("./migrations/*.sql", {
|
|
query: "?raw",
|
|
import: "default",
|
|
})
|
|
|
|
const sortedPaths = Object.keys(migrations).sort()
|
|
|
|
for (const path of sortedPaths) {
|
|
const name = path.split("/").pop()!
|
|
const result = await db.query(
|
|
"SELECT 1 FROM _migrations WHERE name = $1",
|
|
[name],
|
|
)
|
|
if (result.rows.length > 0) continue
|
|
|
|
const sql = (await migrations[path]()) as string
|
|
await db.exec(sql)
|
|
await db.query(
|
|
"INSERT INTO _migrations (name) VALUES ($1)",
|
|
[name],
|
|
)
|
|
}
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 3: Create initial migration**
|
|
|
|
Create `src/shared/db/migrations/001_init.sql`:
|
|
```sql
|
|
-- User profile (single row per device)
|
|
CREATE TABLE nutzer (
|
|
id SERIAL PRIMARY KEY,
|
|
name TEXT,
|
|
plz TEXT,
|
|
ort TEXT,
|
|
krankenkasse TEXT,
|
|
aktueller_schritt TEXT NOT NULL DEFAULT 'neu',
|
|
dringlichkeitscode BOOLEAN NOT NULL DEFAULT FALSE,
|
|
dringlichkeitscode_datum DATE,
|
|
tss_beantragt BOOLEAN NOT NULL DEFAULT FALSE,
|
|
tss_beantragt_datum DATE,
|
|
erstellt_am TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
aktualisiert_am TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
-- Therapist contacts
|
|
CREATE TABLE therapeut (
|
|
id SERIAL PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
adresse TEXT,
|
|
plz TEXT,
|
|
stadt TEXT,
|
|
telefon TEXT,
|
|
email TEXT,
|
|
website TEXT,
|
|
therapieform TEXT,
|
|
kassenzulassung TEXT DEFAULT 'gkv',
|
|
erstellt_am TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
-- Contact attempts
|
|
CREATE TABLE kontakt (
|
|
id SERIAL PRIMARY KEY,
|
|
therapeut_id INTEGER NOT NULL REFERENCES therapeut(id) ON DELETE CASCADE,
|
|
datum DATE NOT NULL DEFAULT CURRENT_DATE,
|
|
kanal TEXT NOT NULL DEFAULT 'telefon',
|
|
ergebnis TEXT NOT NULL DEFAULT 'keine_antwort',
|
|
notiz TEXT,
|
|
antwort_datum DATE,
|
|
erstellt_am TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
-- Sprechstunden visits
|
|
CREATE TABLE sprechstunde (
|
|
id SERIAL PRIMARY KEY,
|
|
therapeut_id INTEGER NOT NULL REFERENCES therapeut(id) ON DELETE CASCADE,
|
|
datum DATE NOT NULL,
|
|
ergebnis TEXT,
|
|
diagnose TEXT,
|
|
dringlichkeitscode BOOLEAN NOT NULL DEFAULT FALSE,
|
|
erstellt_am TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
```
|
|
|
|
- [ ] **Step 4: Create Zod schemas**
|
|
|
|
Create `src/shared/db/schema.ts`:
|
|
```ts
|
|
import { z } from "zod"
|
|
|
|
export const prozessSchrittEnum = z.enum([
|
|
"neu",
|
|
"sprechstunde_absolviert",
|
|
"diagnose_erhalten",
|
|
"tss_beantragt",
|
|
"eigensuche",
|
|
"antrag_gestellt",
|
|
])
|
|
export type ProzessSchritt = z.infer<typeof prozessSchrittEnum>
|
|
|
|
export const kontaktKanalEnum = z.enum([
|
|
"telefon",
|
|
"email",
|
|
"online_formular",
|
|
"persoenlich",
|
|
])
|
|
export type KontaktKanal = z.infer<typeof kontaktKanalEnum>
|
|
|
|
export const kontaktErgebnisEnum = z.enum([
|
|
"keine_antwort",
|
|
"absage",
|
|
"warteliste",
|
|
"zusage",
|
|
])
|
|
export type KontaktErgebnis = z.infer<typeof kontaktErgebnisEnum>
|
|
|
|
export const therapieformEnum = z.enum([
|
|
"verhaltenstherapie",
|
|
"tiefenpsychologisch",
|
|
"analytisch",
|
|
"systemisch",
|
|
])
|
|
export type Therapieform = z.infer<typeof therapieformEnum>
|
|
|
|
export const nutzerSchema = z.object({
|
|
id: z.number(),
|
|
name: z.string().nullable(),
|
|
plz: z.string().nullable(),
|
|
ort: z.string().nullable(),
|
|
krankenkasse: z.string().nullable(),
|
|
aktueller_schritt: prozessSchrittEnum,
|
|
dringlichkeitscode: z.boolean(),
|
|
dringlichkeitscode_datum: z.string().nullable(),
|
|
tss_beantragt: z.boolean(),
|
|
tss_beantragt_datum: z.string().nullable(),
|
|
})
|
|
|
|
export const therapeutSchema = z.object({
|
|
id: z.number(),
|
|
name: z.string(),
|
|
adresse: z.string().nullable(),
|
|
plz: z.string().nullable(),
|
|
stadt: z.string().nullable(),
|
|
telefon: z.string().nullable(),
|
|
email: z.string().nullable(),
|
|
website: z.string().nullable(),
|
|
therapieform: z.string().nullable(),
|
|
kassenzulassung: z.string().nullable(),
|
|
})
|
|
|
|
export const kontaktSchema = z.object({
|
|
id: z.number(),
|
|
therapeut_id: z.number(),
|
|
datum: z.string(),
|
|
kanal: kontaktKanalEnum,
|
|
ergebnis: kontaktErgebnisEnum,
|
|
notiz: z.string().nullable(),
|
|
antwort_datum: z.string().nullable(),
|
|
})
|
|
|
|
export const sprechstundeSchema = z.object({
|
|
id: z.number(),
|
|
therapeut_id: z.number(),
|
|
datum: z.string(),
|
|
ergebnis: z.string().nullable(),
|
|
diagnose: z.string().nullable(),
|
|
dringlichkeitscode: z.boolean(),
|
|
})
|
|
```
|
|
|
|
Run: `bun add zod`
|
|
|
|
- [ ] **Step 5: Create database hook**
|
|
|
|
Create `src/shared/hooks/use-db.ts`:
|
|
```ts
|
|
import { useCallback, useEffect, useState } from "react"
|
|
import { getDb } from "../db/client"
|
|
|
|
export function useDbQuery<T>(
|
|
query: string,
|
|
params: unknown[] = [],
|
|
deps: unknown[] = [],
|
|
) {
|
|
const [data, setData] = useState<T[]>([])
|
|
const [loading, setLoading] = useState(true)
|
|
const [error, setError] = useState<Error | null>(null)
|
|
|
|
const refetch = useCallback(async () => {
|
|
setLoading(true)
|
|
try {
|
|
const db = await getDb()
|
|
const result = await db.query(query, params)
|
|
setData(result.rows as T[])
|
|
setError(null)
|
|
} catch (e) {
|
|
setError(e instanceof Error ? e : new Error(String(e)))
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}, [query, ...deps])
|
|
|
|
useEffect(() => {
|
|
refetch()
|
|
}, [refetch])
|
|
|
|
return { data, loading, error, refetch }
|
|
}
|
|
|
|
export async function dbExec(query: string, params: unknown[] = []) {
|
|
const db = await getDb()
|
|
return db.query(query, params)
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 6: Verify database initializes**
|
|
|
|
Update `src/routes/index.tsx` temporarily to test DB connection. Run `bun dev` — confirm DB initializes (check DevTools → Application → IndexedDB for `therapyfinder`).
|
|
|
|
- [ ] **Step 7: Commit**
|
|
|
|
```bash
|
|
git add -A && git commit -m "add pglite with migration runner, zod schemas, db hook"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 5: Add Vitest + lint-staged + simple-git-hooks
|
|
|
|
**Files:**
|
|
- Modify: `package.json`
|
|
- Create: `vitest.config.ts`
|
|
- Create: `src/test-setup.ts`
|
|
- Create: `src/shared/db/schema.test.ts`
|
|
|
|
- [ ] **Step 1: Install test and code quality tooling**
|
|
|
|
```bash
|
|
bun add -d vitest @testing-library/react @testing-library/jest-dom jsdom
|
|
bun add -d lint-staged simple-git-hooks
|
|
```
|
|
|
|
- [ ] **Step 2: Create Vitest config**
|
|
|
|
Create `vitest.config.ts`:
|
|
```ts
|
|
import { defineConfig } from "vitest/config"
|
|
import react from "@vitejs/plugin-react"
|
|
import { resolve } from "node:path"
|
|
|
|
export default defineConfig({
|
|
plugins: [react()],
|
|
test: {
|
|
environment: "jsdom",
|
|
setupFiles: ["./src/test-setup.ts"],
|
|
include: ["src/**/*.test.{ts,tsx}"],
|
|
},
|
|
resolve: {
|
|
alias: {
|
|
"@": resolve(__dirname, "src"),
|
|
},
|
|
},
|
|
})
|
|
```
|
|
|
|
Create `src/test-setup.ts`:
|
|
```ts
|
|
import "@testing-library/jest-dom/vitest"
|
|
```
|
|
|
|
- [ ] **Step 3: Configure lint-staged + simple-git-hooks in `package.json`**
|
|
|
|
Add to `package.json`:
|
|
```json
|
|
{
|
|
"simple-git-hooks": {
|
|
"pre-commit": "bunx lint-staged"
|
|
},
|
|
"lint-staged": {
|
|
"*.{ts,tsx,json,css}": ["biome check --write"]
|
|
}
|
|
}
|
|
```
|
|
|
|
Run: `bunx simple-git-hooks`
|
|
|
|
- [ ] **Step 4: Write a smoke test to verify setup**
|
|
|
|
Create `src/shared/db/schema.test.ts`:
|
|
```ts
|
|
import { describe, expect, it } from "vitest"
|
|
import { prozessSchrittEnum } from "./schema"
|
|
|
|
describe("prozessSchrittEnum", () => {
|
|
it("accepts valid steps", () => {
|
|
expect(prozessSchrittEnum.parse("neu")).toBe("neu")
|
|
expect(prozessSchrittEnum.parse("eigensuche")).toBe("eigensuche")
|
|
})
|
|
|
|
it("rejects invalid steps", () => {
|
|
expect(() => prozessSchrittEnum.parse("invalid")).toThrow()
|
|
})
|
|
})
|
|
```
|
|
|
|
- [ ] **Step 5: Run tests**
|
|
|
|
Run: `bun test`
|
|
Expected: 2 tests pass.
|
|
|
|
- [ ] **Step 6: Commit**
|
|
|
|
```bash
|
|
git add -A && git commit -m "add vitest, testing library, lint-staged, simple-git-hooks"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 6: Add PWA support
|
|
|
|
**Files:**
|
|
- Modify: `vite.config.ts`
|
|
- Create: `public/manifest.webmanifest`
|
|
- Create: `public/icons/icon-192.png` (placeholder)
|
|
- Create: `public/icons/icon-512.png` (placeholder)
|
|
|
|
- [ ] **Step 1: Install vite-plugin-pwa**
|
|
|
|
Run: `bun add -d vite-plugin-pwa`
|
|
|
|
- [ ] **Step 2: Configure PWA plugin**
|
|
|
|
Update `vite.config.ts` to add `VitePWA` with `registerType: "autoUpdate"` and `globPatterns: ["**/*.{js,css,html,wasm,data}"]`. Use `manifest: false` (external manifest file).
|
|
|
|
- [ ] **Step 3: Create manifest**
|
|
|
|
Create `public/manifest.webmanifest`:
|
|
```json
|
|
{
|
|
"name": "TherapyFinder",
|
|
"short_name": "TherapyFinder",
|
|
"start_url": "/",
|
|
"display": "standalone",
|
|
"background_color": "#ffffff",
|
|
"theme_color": "#09090b",
|
|
"icons": [
|
|
{
|
|
"src": "/icons/icon-192.png",
|
|
"sizes": "192x192",
|
|
"type": "image/png"
|
|
},
|
|
{
|
|
"src": "/icons/icon-512.png",
|
|
"sizes": "512x512",
|
|
"type": "image/png"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 4: Add manifest link to `index.html`**
|
|
|
|
Add to `<head>`:
|
|
```html
|
|
<link rel="manifest" href="/manifest.webmanifest" />
|
|
<meta name="theme-color" content="#09090b" />
|
|
```
|
|
|
|
- [ ] **Step 5: Create placeholder icons**
|
|
|
|
Generate simple placeholder PNGs (solid color squares) for 192x192 and 512x512. Replace with proper icons later.
|
|
|
|
- [ ] **Step 6: Commit**
|
|
|
|
```bash
|
|
git add -A && git commit -m "add pwa support with vite-plugin-pwa, manifest, placeholder icons"
|
|
```
|
|
|
|
---
|
|
|
|
## Chunk 2: Onboarding and Process Stepper
|
|
|
|
### Task 7: Define constants and label maps
|
|
|
|
**Files:**
|
|
- Create: `src/shared/lib/constants.ts`
|
|
- Create: `src/shared/lib/constants.test.ts`
|
|
|
|
- [ ] **Step 1: Create constants file**
|
|
|
|
Create `src/shared/lib/constants.ts` with:
|
|
- `PROZESS_SCHRITTE`: array of `{ key, label, beschreibung }` for all 6 process steps (neu → antrag_gestellt)
|
|
- `KANAL_LABELS`: `Record<KontaktKanal, string>` mapping enum values to German labels
|
|
- `ERGEBNIS_LABELS`: `Record<KontaktErgebnis, string>` mapping enum values to German labels
|
|
- `THERAPIEFORM_LABELS`: `Record<Therapieform, string>` mapping enum values to German labels
|
|
|
|
Labels:
|
|
- neu → "Noch nicht begonnen"
|
|
- sprechstunde_absolviert → "Sprechstunde absolviert"
|
|
- diagnose_erhalten → "Diagnose erhalten"
|
|
- tss_beantragt → "TSS kontaktiert"
|
|
- eigensuche → "Eigensuche läuft"
|
|
- antrag_gestellt → "Kostenerstattung beantragt"
|
|
- telefon → "Telefon", email → "E-Mail", online_formular → "Online-Formular", persoenlich → "Persönlich"
|
|
- keine_antwort → "Keine Antwort", absage → "Absage", warteliste → "Warteliste", zusage → "Zusage"
|
|
- verhaltenstherapie → "Verhaltenstherapie (VT)", tiefenpsychologisch → "Tiefenpsychologisch fundierte PT (TP)", analytisch → "Analytische Psychotherapie (AP)", systemisch → "Systemische Therapie"
|
|
|
|
- [ ] **Step 2: Write test for constants**
|
|
|
|
Create `src/shared/lib/constants.test.ts`: verify PROZESS_SCHRITTE has 6 entries, correct order, and all have non-empty label/beschreibung.
|
|
|
|
- [ ] **Step 3: Run tests, commit**
|
|
|
|
Run: `bun test`
|
|
|
|
```bash
|
|
git add -A && git commit -m "add process step constants, channel/result label maps"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 8: Onboarding flow
|
|
|
|
**Files:**
|
|
- Create: `src/features/onboarding/schema.ts`
|
|
- Create: `src/features/onboarding/schema.test.ts`
|
|
- Create: `src/features/onboarding/components/onboarding-form.tsx`
|
|
- Create: `src/features/onboarding/index.ts`
|
|
- Create: `src/routes/onboarding/index.tsx`
|
|
- Modify: `src/routes/index.tsx` (redirect logic)
|
|
|
|
- [ ] **Step 1: Install TanStack Form**
|
|
|
|
```bash
|
|
bun add @tanstack/react-form
|
|
```
|
|
|
|
- [ ] **Step 2: Add shadcn form components**
|
|
|
|
```bash
|
|
bunx shadcn@latest add input label select
|
|
```
|
|
|
|
- [ ] **Step 3: Create onboarding schema**
|
|
|
|
Create `src/features/onboarding/schema.ts` with a Zod schema for: `name` (min 1), `plz` (regex 5 digits), `ort` (min 1), `krankenkasse` (min 1), `aktueller_schritt` (prozessSchrittEnum).
|
|
|
|
- [ ] **Step 4: Write schema tests**
|
|
|
|
Create `src/features/onboarding/schema.test.ts`: test valid data accepts, invalid PLZ rejects, empty name rejects.
|
|
|
|
- [ ] **Step 5: Run tests**
|
|
|
|
Run: `bun test` — all pass.
|
|
|
|
- [ ] **Step 6: Create onboarding form component**
|
|
|
|
Create `src/features/onboarding/components/onboarding-form.tsx`:
|
|
- Uses `useForm` from TanStack Form with the onboarding schema
|
|
- Fields: name, PLZ + Ort (grid), Krankenkasse, aktueller_schritt (select dropdown using PROZESS_SCHRITTE)
|
|
- On submit: INSERT into `nutzer` table via `dbExec`, then navigate to `/prozess`
|
|
- Welcoming header text explaining the app's purpose
|
|
|
|
- [ ] **Step 7: Create onboarding route**
|
|
|
|
Create `src/routes/onboarding/index.tsx` rendering `<OnboardingForm />`.
|
|
|
|
- [ ] **Step 8: Create feature index**
|
|
|
|
Create `src/features/onboarding/index.ts` re-exporting form and schema.
|
|
|
|
- [ ] **Step 9: Update root route with redirect logic**
|
|
|
|
Update `src/routes/index.tsx`: in `beforeLoad`, query nutzer table. If no rows → redirect to `/onboarding`. If rows exist → redirect to `/prozess`.
|
|
|
|
- [ ] **Step 10: Verify onboarding flow**
|
|
|
|
Run: `bun dev` → auto-redirect to `/onboarding` → fill form → submit → redirect to `/prozess`.
|
|
|
|
- [ ] **Step 11: Commit**
|
|
|
|
```bash
|
|
git add -A && git commit -m "add onboarding flow with form, validation, db persistence"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 9: Process stepper dashboard
|
|
|
|
**Files:**
|
|
- Create: `src/features/prozess/hooks.ts`
|
|
- Create: `src/features/prozess/components/phase-card.tsx`
|
|
- Create: `src/features/prozess/components/process-stepper.tsx`
|
|
- Create: `src/features/prozess/index.ts`
|
|
- Create: `src/routes/prozess/index.tsx`
|
|
|
|
- [ ] **Step 1: Create process data hooks**
|
|
|
|
Create `src/features/prozess/hooks.ts`:
|
|
- `useNutzer()`: query nutzer table, return first row
|
|
- `useKontaktStats()`: aggregate query on kontakt table returning gesamt, absagen, warteliste, keine_antwort counts
|
|
- `updateSchritt(schritt)`: UPDATE nutzer aktueller_schritt
|
|
|
|
- [ ] **Step 2: Create phase card component**
|
|
|
|
Create `src/features/prozess/components/phase-card.tsx`:
|
|
- Props: label, beschreibung, status ("erledigt" | "aktuell" | "offen"), index
|
|
- Renders a Card with a numbered circle (checkmark if erledigt), label, "Aktuell" badge if current, description only shown for current step
|
|
- Visual styling: ring/border highlight for current, opacity-60 for completed
|
|
|
|
- [ ] **Step 3: Create process stepper component**
|
|
|
|
Create `src/features/prozess/components/process-stepper.tsx`:
|
|
- Props: aktuellerSchritt, kontaktGesamt, absagen
|
|
- Renders heading "Dein Fortschritt" with step X of 6
|
|
- Maps PROZESS_SCHRITTE to PhaseCard components with correct status
|
|
- Shows contact stats card with links to `/kontakte` and `/antrag` when step >= "tss_beantragt"
|
|
|
|
- [ ] **Step 4: Create process route**
|
|
|
|
Create `src/routes/prozess/index.tsx`: uses `useNutzer()` and `useKontaktStats()` hooks, renders `<ProcessStepper>`.
|
|
|
|
- [ ] **Step 5: Create feature index**
|
|
|
|
Create `src/features/prozess/index.ts` re-exporting components and hooks.
|
|
|
|
- [ ] **Step 6: Verify process stepper**
|
|
|
|
Run: `bun dev` → complete onboarding → verify stepper shows correct highlighted phase.
|
|
|
|
- [ ] **Step 7: Commit**
|
|
|
|
```bash
|
|
git add -A && git commit -m "add process stepper dashboard with phase cards, contact stats"
|
|
```
|
|
|
|
---
|
|
|
|
## Chunk 3: Contact Tracker
|
|
|
|
### Task 10: Contact tracker — data layer and schemas
|
|
|
|
**Files:**
|
|
- Create: `src/features/kontakte/schema.ts`
|
|
- Create: `src/features/kontakte/schema.test.ts`
|
|
- Create: `src/features/kontakte/hooks.ts`
|
|
- Create: `src/features/kontakte/index.ts`
|
|
|
|
- [ ] **Step 1: Create contact feature schemas**
|
|
|
|
Create `src/features/kontakte/schema.ts`:
|
|
- `therapeutFormSchema`: name (required), optional adresse, plz (5-digit regex or empty), stadt, telefon, email (valid or empty), website, therapieform
|
|
- `kontaktFormSchema`: therapeut_id (number), datum (required), kanal (kontaktKanalEnum), ergebnis (kontaktErgebnisEnum), notiz (optional)
|
|
|
|
- [ ] **Step 2: Write schema tests**
|
|
|
|
Create `src/features/kontakte/schema.test.ts`: test valid therapist, required name, valid contact, required date.
|
|
|
|
- [ ] **Step 3: Run tests**
|
|
|
|
Run: `bun test` — all pass.
|
|
|
|
- [ ] **Step 4: Create contact hooks**
|
|
|
|
Create `src/features/kontakte/hooks.ts`:
|
|
- `useTherapeutenListe()`: query therapeut table joined with latest kontakt subqueries (letzter_kontakt, letztes_ergebnis, kontakte_gesamt)
|
|
- `useKontakteForTherapeut(therapeutId)`: query kontakt table filtered by therapeut_id
|
|
- `createTherapeut(data)`: INSERT into therapeut, RETURNING id
|
|
- `createKontakt(data)`: INSERT into kontakt
|
|
|
|
- [ ] **Step 5: Create feature index**
|
|
|
|
Create `src/features/kontakte/index.ts` re-exporting schemas and hooks.
|
|
|
|
- [ ] **Step 6: Commit**
|
|
|
|
```bash
|
|
git add -A && git commit -m "add contact tracker data layer: schemas, hooks, db queries"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 11: Contact tracker — UI components
|
|
|
|
**Files:**
|
|
- Create: `src/features/kontakte/components/contact-card.tsx`
|
|
- Create: `src/features/kontakte/components/contact-list.tsx`
|
|
- Create: `src/features/kontakte/components/contact-form.tsx`
|
|
- Create: `src/routes/kontakte/index.tsx`
|
|
- Create: `src/routes/kontakte/neu.tsx`
|
|
- Modify: `src/routes/__root.tsx` (add bottom navigation)
|
|
|
|
- [ ] **Step 1: Add shadcn components**
|
|
|
|
```bash
|
|
bunx shadcn@latest add textarea dialog
|
|
```
|
|
|
|
- [ ] **Step 2: Create contact card**
|
|
|
|
Create `src/features/kontakte/components/contact-card.tsx`:
|
|
- Shows therapist name, city, last contact date, result badge (color-coded: zusage=default, warteliste=secondary, absage=destructive, keine_antwort=outline), contact count
|
|
- Entire card is a Link to `/kontakte/neu?therapeutId=<id>`
|
|
|
|
- [ ] **Step 3: Create contact list**
|
|
|
|
Create `src/features/kontakte/components/contact-list.tsx`:
|
|
- Heading "Kontakte" with "+ Neu" button linking to `/kontakte/neu`
|
|
- Empty state with CTA to add first contact
|
|
- Maps useTherapeutenListe() results to ContactCard components
|
|
|
|
- [ ] **Step 4: Create combined contact form**
|
|
|
|
Create `src/features/kontakte/components/contact-form.tsx`:
|
|
- Two sections separated by `<Separator>`: "Therapeut:in" (name, stadt, telefon, email) and "Kontaktversuch" (datum, kanal select, ergebnis select, notiz textarea)
|
|
- On submit: createTherapeut() then createKontakt() with returned ID, navigate to `/kontakte`
|
|
- Cancel button navigates back
|
|
|
|
- [ ] **Step 5: Create contact routes**
|
|
|
|
Create `src/routes/kontakte/index.tsx` rendering `<ContactList />`.
|
|
Create `src/routes/kontakte/neu.tsx` rendering `<ContactForm />`.
|
|
|
|
- [ ] **Step 6: Add bottom navigation to root layout**
|
|
|
|
Update `src/routes/__root.tsx`: add a `<nav>` below `<main>` with three `<Link>` tabs: Fortschritt (/prozess), Kontakte (/kontakte), Antrag (/antrag). Use `[&.active]:font-bold [&.active]:text-primary` for active state.
|
|
|
|
- [ ] **Step 7: Verify contact flow**
|
|
|
|
Run: `bun dev` → navigate to `/kontakte` → add a new contact → verify it appears in the list with correct badge.
|
|
|
|
- [ ] **Step 8: Commit**
|
|
|
|
```bash
|
|
git add -A && git commit -m "add contact tracker: list, form, cards, bottom navigation"
|
|
```
|
|
|
|
---
|
|
|
|
## Chunk 4: Kostenerstattung and PDF Export
|
|
|
|
### Task 12: PDF export for rejection documentation
|
|
|
|
**Files:**
|
|
- Create: `src/features/antrag/pdf.ts`
|
|
- Create: `src/features/antrag/components/pdf-export-button.tsx`
|
|
|
|
- [ ] **Step 1: Install jspdf**
|
|
|
|
Run: `bun add jspdf`
|
|
|
|
- [ ] **Step 2: Create PDF generation logic**
|
|
|
|
Create `src/features/antrag/pdf.ts`:
|
|
- `generateAbsagenPdf()`: async function that:
|
|
- Queries nutzer (name, krankenkasse) and all kontakt rows joined with therapeut
|
|
- Creates a jsPDF document with: header (title, user info, date), summary (total contacts, absagen, keine_antwort), table with columns (Datum, Therapeut:in, Ort, Kontaktweg, Ergebnis), footer note
|
|
- Handles page breaks when y > 270
|
|
- Calls `doc.save("therapeutensuche-dokumentation.pdf")`
|
|
|
|
- [ ] **Step 3: Create PDF export button**
|
|
|
|
Create `src/features/antrag/components/pdf-export-button.tsx`:
|
|
- Button that calls `generateAbsagenPdf()` with loading state
|
|
|
|
- [ ] **Step 4: Commit**
|
|
|
|
```bash
|
|
git add -A && git commit -m "add pdf export for rejection documentation (jspdf)"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 13: Kostenerstattungs-Assistent
|
|
|
|
**Files:**
|
|
- Create: `src/features/antrag/components/antrag-checklist.tsx`
|
|
- Create: `src/features/antrag/index.ts`
|
|
- Create: `src/routes/antrag/index.tsx`
|
|
|
|
- [ ] **Step 1: Create Antrag checklist component**
|
|
|
|
Create `src/features/antrag/components/antrag-checklist.tsx`:
|
|
- Uses `useNutzer()` and `useKontaktStats()` from prozess hooks
|
|
- Renders 5 checklist items as Cards with green checkmark or grey number:
|
|
1. "Psychotherapeutische Sprechstunde besucht" — fulfilled if step > neu
|
|
2. "Diagnose / Dringlichkeitscode erhalten" — fulfilled if dringlichkeitscode is true
|
|
3. "Terminservicestelle (TSS) kontaktiert" — fulfilled if tss_beantragt is true
|
|
4. "Eigenständige Therapeutensuche dokumentiert" — fulfilled if absagen + keine_antwort >= 5
|
|
5. "Absagenliste exportiert" — always unchecked (manual step)
|
|
- Shows "Nächste Schritte" ordered list with guidance
|
|
- Renders `<PdfExportButton />` at the bottom
|
|
|
|
- [ ] **Step 2: Create feature index**
|
|
|
|
Create `src/features/antrag/index.ts` re-exporting components and pdf function.
|
|
|
|
- [ ] **Step 3: Create Antrag route**
|
|
|
|
Create `src/routes/antrag/index.tsx` rendering `<AntragChecklist />`.
|
|
|
|
- [ ] **Step 4: Verify full flow**
|
|
|
|
Run: `bun dev` → complete onboarding → add 5+ contacts with "Absage" → navigate to `/antrag` → verify checklist → click PDF export → verify PDF downloads.
|
|
|
|
- [ ] **Step 5: Commit**
|
|
|
|
```bash
|
|
git add -A && git commit -m "add kostenerstattungs-assistent with checklist, pdf export"
|
|
```
|
|
|
|
---
|
|
|
|
## Chunk 5: Polish, Testing, and E2E
|
|
|
|
### Task 14: Add dark mode toggle
|
|
|
|
**Files:**
|
|
- Create: `src/shared/hooks/use-theme.ts`
|
|
- Modify: `src/routes/__root.tsx`
|
|
|
|
- [ ] **Step 1: Install Zustand**
|
|
|
|
Run: `bun add zustand`
|
|
|
|
- [ ] **Step 2: Create theme store**
|
|
|
|
Create `src/shared/hooks/use-theme.ts`:
|
|
- Zustand store with `theme` ("light" | "dark" | "system") and `setTheme()`
|
|
- Persists to localStorage key "theme"
|
|
- `applyTheme()` toggles `dark` class on `document.documentElement`
|
|
- Applies theme on module load
|
|
|
|
- [ ] **Step 3: Add toggle to root layout**
|
|
|
|
Update `src/routes/__root.tsx`: add a simple theme toggle button in the header area.
|
|
|
|
- [ ] **Step 4: Commit**
|
|
|
|
```bash
|
|
git add -A && git commit -m "add dark mode toggle with zustand, local storage persistence"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 15: E2E tests with Playwright
|
|
|
|
**Files:**
|
|
- Create: `playwright.config.ts`
|
|
- Create: `e2e/onboarding.spec.ts`
|
|
- Create: `e2e/kontakte.spec.ts`
|
|
|
|
- [ ] **Step 1: Install Playwright**
|
|
|
|
```bash
|
|
bun add -d @playwright/test
|
|
bunx playwright install chromium
|
|
```
|
|
|
|
- [ ] **Step 2: Create Playwright config**
|
|
|
|
Create `playwright.config.ts`: testDir "e2e", webServer command "bun dev" on port 5173, baseURL "http://localhost:5173".
|
|
|
|
- [ ] **Step 3: Write onboarding E2E test**
|
|
|
|
Create `e2e/onboarding.spec.ts`:
|
|
- Test: go to "/" → redirects to /onboarding → fill name, PLZ, Ort, Krankenkasse → click "Weiter" → redirects to /prozess → "Dein Fortschritt" visible
|
|
|
|
- [ ] **Step 4: Write contact tracker E2E test**
|
|
|
|
Create `e2e/kontakte.spec.ts`:
|
|
- beforeEach: complete onboarding
|
|
- Test: navigate to Kontakte → click add → fill therapist name and city → save → see therapist in list
|
|
|
|
- [ ] **Step 5: Run E2E tests**
|
|
|
|
Run: `bun e2e`
|
|
Expected: both tests pass.
|
|
|
|
- [ ] **Step 6: Commit**
|
|
|
|
```bash
|
|
git add -A && git commit -m "add playwright e2e tests for onboarding, contact tracker"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 16: Deploy script for Uberspace
|
|
|
|
**Files:**
|
|
- Create: `scripts/deploy.sh`
|
|
|
|
- [ ] **Step 1: Create deploy script**
|
|
|
|
Create `scripts/deploy.sh`: idempotent script that takes SSH host as argument, runs `bun run build`, then `rsync` the `dist/` directory to `/var/www/virtual/<user>/html/tpf/` on the remote host.
|
|
|
|
- [ ] **Step 2: Make executable**
|
|
|
|
Run: `chmod +x scripts/deploy.sh`
|
|
|
|
- [ ] **Step 3: Commit**
|
|
|
|
```bash
|
|
git add -A && git commit -m "add uberspace deploy script for static pwa hosting"
|
|
```
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
| Chunk | Tasks | What it delivers |
|
|
|-------|-------|------------------|
|
|
| 1: Scaffolding | 1-6 | Vite + React + TanStack Router + Tailwind + shadcn + PGlite + PWA + Vitest + Biome |
|
|
| 2: Onboarding and Process | 7-9 | Onboarding form, process stepper dashboard with 6 phases |
|
|
| 3: Contact Tracker | 10-11 | Therapist + contact attempt CRUD, list view, badge statuses |
|
|
| 4: Kostenerstattung | 12-13 | PDF export of rejection documentation, Antrag checklist |
|
|
| 5: Polish and Testing | 14-16 | Dark mode, E2E tests, deploy script |
|
|
|
|
**Total: 16 tasks, ~80 steps**
|
|
|
|
After V1 ships, the main open items for V2 are:
|
|
- Therapist directory integration (KV-Verzeichnis API or scraped data)
|
|
- Push notifications / reminders
|
|
- i18n (DE/EN/ES/FR)
|
|
- DSGVO legal review
|