diff --git a/package.json b/package.json index 2eb72ad..b35959c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "therapyfinder", "private": true, - "version": "2026.03.11.1", + "version": "2026.03.11.2", "type": "module", "scripts": { "dev": "vite", diff --git a/src/features/kontakte/components/contact-card.tsx b/src/features/kontakte/components/contact-card.tsx index 2a5dbfa..aa117a6 100644 --- a/src/features/kontakte/components/contact-card.tsx +++ b/src/features/kontakte/components/contact-card.tsx @@ -1,37 +1,60 @@ -import { useNavigate } from "@tanstack/react-router"; -import { Check, Clock, X } from "lucide-react"; -import { Badge } from "@/shared/components/ui/badge"; -import { Card, CardContent } from "@/shared/components/ui/card"; +import { Link } from "@tanstack/react-router"; +import { Check, Clock, HelpCircle, X } from "lucide-react"; import type { KontaktErgebnis } from "@/shared/db/schema"; import { dbExec } from "@/shared/hooks/use-db"; -import { ERGEBNIS_LABELS } from "@/shared/lib/constants"; +import { cn } from "@/shared/lib/utils"; interface ContactCardProps { id: number; name: string; - stadt: string | null; letzterKontakt: string | null; letztesErgebnis: string | null; kontakteGesamt: number; onUpdate: () => void; } -const ergebnisVariant: Record< - KontaktErgebnis, - "default" | "secondary" | "destructive" | "outline" -> = { - zusage: "default", - warteliste: "secondary", - absage: "destructive", - keine_antwort: "outline", -}; +const statusButtons: { + ergebnis: KontaktErgebnis; + icon: typeof Check; + color: string; + selectedBg: string; + label: string; +}[] = [ + { + ergebnis: "zusage", + icon: Check, + color: "text-green-600 dark:text-green-400", + selectedBg: "bg-green-600 dark:bg-green-500 text-white", + label: "Zusage", + }, + { + ergebnis: "warteliste", + icon: Clock, + color: "text-amber-600 dark:text-amber-400", + selectedBg: "bg-amber-600 dark:bg-amber-500 text-white", + label: "Warteliste", + }, + { + ergebnis: "absage", + icon: X, + color: "text-red-600 dark:text-red-400", + selectedBg: "bg-red-600 dark:bg-red-500 text-white", + label: "Absage", + }, + { + ergebnis: "keine_antwort", + icon: HelpCircle, + color: "text-muted-foreground", + selectedBg: "bg-muted-foreground text-white", + label: "Keine Antwort", + }, +]; async function quickUpdate( therapeutId: number, ergebnis: KontaktErgebnis, onUpdate: () => void, ) { - // Update the most recent kontakt for this therapist, or create one if none exists const result = await dbExec( "SELECT id FROM kontakt WHERE therapeut_id = $1 ORDER BY datum DESC, id DESC LIMIT 1", [therapeutId], @@ -56,81 +79,47 @@ async function quickUpdate( export function ContactCard({ id, name, - stadt, letzterKontakt, letztesErgebnis, kontakteGesamt, onUpdate, }: ContactCardProps) { - const navigate = useNavigate(); - return ( - - navigate({ to: "/kontakte/$id", params: { id: String(id) } }) - } - > - -
-

{name}

- {stadt &&

{stadt}

} -

- {kontakteGesamt} Kontakt{kontakteGesamt !== 1 ? "e" : ""} - {letzterKontakt && <> · {letzterKontakt}} -

-
+
+ +

{name}

+

+ {kontakteGesamt} Kontakt{kontakteGesamt !== 1 ? "e" : ""} + {letzterKontakt && <> · {letzterKontakt}} +

+ -
- - - -
- - {letztesErgebnis && ( - - {ERGEBNIS_LABELS[letztesErgebnis as KontaktErgebnis] ?? - letztesErgebnis} - - )} - - +
+ {statusButtons.map((btn) => { + const Icon = btn.icon; + const isSelected = letztesErgebnis === btn.ergebnis; + return ( + + ); + })} +
+
); } diff --git a/src/features/kontakte/components/contact-detail.tsx b/src/features/kontakte/components/contact-detail.tsx index 626ae65..3075046 100644 --- a/src/features/kontakte/components/contact-detail.tsx +++ b/src/features/kontakte/components/contact-detail.tsx @@ -3,6 +3,7 @@ import { Link, useNavigate } from "@tanstack/react-router"; import { ArrowLeft, Plus, Trash2 } from "lucide-react"; import { createKontakt, + deleteKontakt, deleteTherapeut, updateKontakt, updateTherapeut, @@ -472,9 +473,23 @@ function KontaktEditCard({ )} - +
+ + +
diff --git a/src/features/kontakte/components/contact-list.tsx b/src/features/kontakte/components/contact-list.tsx index 726ec71..7701c5e 100644 --- a/src/features/kontakte/components/contact-list.tsx +++ b/src/features/kontakte/components/contact-list.tsx @@ -31,13 +31,12 @@ export function ContactList() { ) : ( -
+
{data.map((t) => ( Krankenkasse field.handleChange(e.target.value)} - placeholder="TK" + placeholder="z.B. Techniker Krankenkasse" /> + + {GKV_KASSEN.map((k) => ( +
)} diff --git a/src/features/prozess/components/phase-card.tsx b/src/features/prozess/components/phase-card.tsx index cf1e84d..d98b244 100644 --- a/src/features/prozess/components/phase-card.tsx +++ b/src/features/prozess/components/phase-card.tsx @@ -1,5 +1,5 @@ -import { Check } from "lucide-react"; -import type { ReactNode } from "react"; +import { Check, ChevronDown, ChevronRight } from "lucide-react"; +import { type ReactNode, useState } from "react"; import { Badge } from "@/shared/components/ui/badge"; import { Card, CardContent } from "@/shared/components/ui/card"; import { cn } from "@/shared/lib/utils"; @@ -21,13 +21,18 @@ export function PhaseCard({ index, children, }: PhaseCardProps) { + const [expanded, setExpanded] = useState(false); + const showContent = status === "aktuell" || expanded; + return ( status !== "aktuell" && setExpanded(!expanded)} >
@@ -43,12 +48,21 @@ export function PhaseCard({ > {status === "erledigt" ? : index + 1}
-
+
{label} {status === "aktuell" && Aktuell} + {status !== "aktuell" && ( + + {expanded ? ( + + ) : ( + + )} + + )}
- {status === "aktuell" && ( + {showContent && (

{beschreibung}

)}
diff --git a/src/features/prozess/components/process-stepper.tsx b/src/features/prozess/components/process-stepper.tsx index 562a4ad..4a4d0cd 100644 --- a/src/features/prozess/components/process-stepper.tsx +++ b/src/features/prozess/components/process-stepper.tsx @@ -5,6 +5,7 @@ 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 { Switch } from "@/shared/components/ui/switch"; import type { ProzessSchritt } from "@/shared/db/schema"; import { dbExec } from "@/shared/hooks/use-db"; import { PROZESS_SCHRITTE } from "@/shared/lib/constants"; @@ -31,8 +32,14 @@ export function ProcessStepper({ absagen, onUpdate, }: ProcessStepperProps) { + // Map legacy step to current steps + const effectiveSchritt = + aktuellerSchritt === "sprechstunde_absolviert" + ? "diagnose_erhalten" + : aktuellerSchritt; + const currentIndex = PROZESS_SCHRITTE.findIndex( - (s) => s.key === aktuellerSchritt, + (s) => s.key === effectiveSchritt, ); return ( @@ -91,14 +98,6 @@ function StepAction({ switch (schritt) { case "neu": return ; - case "sprechstunde_absolviert": - return ( - - ); case "diagnose_erhalten": return ( - +
+ +
); } @@ -186,17 +187,23 @@ function SprechstundeForm({ onDone }: { onDone: () => void }) { therapeut_id: "", datum: todayISO(), diagnose: "", + dringlichkeitscode: false, }, 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], + `INSERT INTO sprechstunde (therapeut_id, datum, ergebnis, diagnose, dringlichkeitscode) VALUES ($1, $2, 'erstgespraech', $3, $4)`, + [ + therapeutId, + value.datum, + value.diagnose || null, + value.dringlichkeitscode, + ], ); await dbExec( - "UPDATE nutzer SET aktueller_schritt = 'sprechstunde_absolviert', aktualisiert_am = NOW() WHERE id = 1", + "UPDATE nutzer SET aktueller_schritt = 'diagnose_erhalten', aktualisiert_am = NOW() WHERE id = 1", ); setSaved(true); onDone(); @@ -223,6 +230,12 @@ function SprechstundeForm({ onDone }: { onDone: () => void }) { className="space-y-3" >

Erstgespräch erfassen

+

+ Freie Termine findest du unter{" "} + 116117{" "} + (Telefon oder online). +

+ {(field) => (
@@ -273,7 +286,7 @@ function SprechstundeForm({ onDone }: { onDone: () => void }) { {(field) => (
- + void }) { )} - + + {(field) => ( +
+
+ field.handleChange(checked)} + /> + +
+ {!field.state.value && ( +

+ Ohne Dringlichkeitscode kann die TSS dich ggf. nicht + vermitteln. Frage in der Sprechstunde gezielt danach. +

+ )} +
+ )} +
+ +
+ +
); diff --git a/src/shared/lib/constants.ts b/src/shared/lib/constants.ts index 656b23d..278bb02 100644 --- a/src/shared/lib/constants.ts +++ b/src/shared/lib/constants.ts @@ -14,13 +14,7 @@ export const PROZESS_SCHRITTE: { key: "neu", 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", - label: "Sprechstunde absolviert", - beschreibung: - "Du hast eine psychotherapeutische Sprechstunde (PTS) besucht. Dort wurde eine erste Einschätzung vorgenommen.", + "Das Erstgespräch heißt in der Fachsprache psychotherapeutische Sprechstunde (PTS). Dort wird eine erste Einschätzung vorgenommen und du erhältst ggf. eine Diagnose und einen Dringlichkeitscode. Tipp: Unter 116117 findest du freie PTS-Termine.", }, { key: "diagnose_erhalten", @@ -32,7 +26,7 @@ export const PROZESS_SCHRITTE: { key: "tss_beantragt", label: "TSS kontaktiert", beschreibung: - "Du hast die Terminservicestelle (TSS) deiner Kassenärztlichen Vereinigung kontaktiert.", + "Du hast über die Terminservicestelle (TSS) deiner Kassenärztlichen Vereinigung (KV) nach einem Therapieplatz gesucht. Die TSS ist ein Service der KV, erreichbar unter 116117 oder online.", }, { key: "eigensuche", diff --git a/src/shared/lib/gkv-kassen.ts b/src/shared/lib/gkv-kassen.ts new file mode 100644 index 0000000..af12bb0 --- /dev/null +++ b/src/shared/lib/gkv-kassen.ts @@ -0,0 +1,76 @@ +export const GKV_KASSEN = [ + "AOK Baden-Württemberg", + "AOK Bayern", + "AOK Bremen/Bremerhaven", + "AOK Hessen", + "AOK Niedersachsen", + "AOK Nordost", + "AOK Nordwest", + "AOK Plus (Sachsen/Thüringen)", + "AOK Rheinland/Hamburg", + "AOK Rheinland-Pfalz/Saarland", + "AOK Sachsen-Anhalt", + "BARMER", + "BIG direkt gesund", + "BKK Achenbach Buschhütten", + "BKK Akzo Nobel Bayern", + "BKK Diakonie", + "BKK Euregio", + "BKK EWE", + "BKK exklusiv", + "BKK firmus", + "BKK Freudenberg", + "BKK Gildemeister Seidensticker", + "BKK Herkules", + "BKK Linde", + "BKK Melitta HMR", + "BKK Mobil Oil", + "BKK Pfalz", + "BKK ProVita", + "BKK Public", + "BKK Rieker Ricosta Weisser", + "BKK Scheufelen", + "BKK Schwarzwald-Baar-Heuberg", + "BKK Technoform", + "BKK Textilgruppe Hof", + "BKK VBU", + "BKK VDN", + "BKK Verbund Plus", + "BKK Werra-Meissner", + "BKK Wirtschaft & Finanzen", + "BKK ZF & Partner", + "BKK24", + "Bosch BKK", + "Continentale BKK", + "DAK-Gesundheit", + "Debeka BKK", + "Deutsche BKK", + "Die Bergische Krankenkasse", + "Die Schwenninger Krankenkasse", + "energie-BKK", + "Handelskrankenkasse (hkk)", + "Hanseatische Krankenkasse (HEK)", + "Heimat Krankenkasse", + "IKK Brandenburg und Berlin", + "IKK classic", + "IKK gesund plus", + "IKK Südwest", + "Kaufmännische Krankenkasse (KKH)", + "Knappschaft", + "Koenig & Bauer BKK", + "mhplus Krankenkasse", + "Mobil Krankenkasse", + "Novitas BKK", + "Pronova BKK", + "R+V BKK", + "Salus BKK", + "SECURVITA Krankenkasse", + "SIEMAG BKK", + "SKD BKK", + "Sozialversicherung für Landwirtschaft (SVLFG)", + "Techniker Krankenkasse (TK)", + "TUI BKK", + "Viactiv Krankenkasse", + "vivida bkk", + "WMF BKK", +];