update antrag checklist, scenarios, settings, barrel export, delete obsolete test
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,8 @@
|
||||
import { useKontaktStats, useNutzer } from "@/features/prozess/hooks";
|
||||
import {
|
||||
useErstgespraeche,
|
||||
useKontaktStats,
|
||||
useNutzer,
|
||||
} from "@/features/prozess/hooks";
|
||||
import { Card, CardContent } from "@/shared/components/ui/card";
|
||||
import { Separator } from "@/shared/components/ui/separator";
|
||||
import { PdfExportButton } from "./pdf-export-button";
|
||||
@@ -6,13 +10,15 @@ import { PdfExportButton } from "./pdf-export-button";
|
||||
interface ChecklistItem {
|
||||
label: string;
|
||||
fulfilled: boolean;
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
export function AntragChecklist() {
|
||||
const { data: nutzerRows, loading: nutzerLoading } = useNutzer();
|
||||
const { data: statsRows, loading: statsLoading } = useKontaktStats();
|
||||
const { data: erstgespraeche, loading: egLoading } = useErstgespraeche();
|
||||
|
||||
if (nutzerLoading || statsLoading) {
|
||||
if (nutzerLoading || statsLoading || egLoading) {
|
||||
return <p className="py-8 text-center text-muted-foreground">Laden…</p>;
|
||||
}
|
||||
|
||||
@@ -27,42 +33,51 @@ export function AntragChecklist() {
|
||||
);
|
||||
}
|
||||
|
||||
const hasDiagnose = erstgespraeche.some((e) => e.diagnose != null);
|
||||
const hasDringlichkeit = erstgespraeche.some((e) => e.dringlichkeitscode);
|
||||
|
||||
const items: ChecklistItem[] = [
|
||||
{
|
||||
label: "Psychotherapeutische Sprechstunde besucht",
|
||||
fulfilled: nutzer.aktueller_schritt !== "neu",
|
||||
label: "Erstgespräch durchgeführt",
|
||||
fulfilled: hasDiagnose,
|
||||
visible: true,
|
||||
},
|
||||
{
|
||||
label: "Diagnose / Dringlichkeitscode erhalten",
|
||||
fulfilled: nutzer.dringlichkeitscode,
|
||||
label: "Dringlichkeitscode erhalten",
|
||||
fulfilled: hasDringlichkeit,
|
||||
visible: true,
|
||||
},
|
||||
{
|
||||
label: "Terminservicestelle (TSS) kontaktiert",
|
||||
fulfilled: nutzer.tss_beantragt,
|
||||
fulfilled: nutzer.tss_kontaktiert_datum != null,
|
||||
visible: hasDringlichkeit,
|
||||
},
|
||||
{
|
||||
label: "Eigenständige Therapeutensuche dokumentiert",
|
||||
fulfilled: stats.absagen + stats.keine_antwort >= 5,
|
||||
label: "Therapeutensuche dokumentiert",
|
||||
fulfilled: Number(stats.absagen) + Number(stats.keine_antwort) >= 5,
|
||||
visible: true,
|
||||
},
|
||||
{
|
||||
label: "Absagenliste exportiert",
|
||||
fulfilled: false,
|
||||
visible: true,
|
||||
},
|
||||
];
|
||||
|
||||
const fulfilledCount = items.filter((i) => i.fulfilled).length;
|
||||
const visibleItems = items.filter((i) => i.visible);
|
||||
const fulfilledCount = visibleItems.filter((i) => i.fulfilled).length;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
<div>
|
||||
<h1 className="text-xl font-bold">Kostenerstattungs-Assistent</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{fulfilledCount} von {items.length} Voraussetzungen erfüllt
|
||||
{fulfilledCount} von {visibleItems.length} Voraussetzungen erfüllt
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-3">
|
||||
{items.map((item, index) => (
|
||||
{visibleItems.map((item, index) => (
|
||||
<Card key={item.label} className="py-4">
|
||||
<CardContent className="flex items-center gap-4">
|
||||
{item.fulfilled ? (
|
||||
|
||||
@@ -10,14 +10,16 @@ import {
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { seedToStep } from "@/features/einstellungen/scenarios";
|
||||
import {
|
||||
type Scenario,
|
||||
seedToScenario,
|
||||
} from "@/features/einstellungen/scenarios";
|
||||
import { deleteAllData } from "@/features/kontakte/hooks";
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
import { Card, CardContent } from "@/shared/components/ui/card";
|
||||
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 { useThemeStore } from "@/shared/hooks/use-theme";
|
||||
|
||||
type Theme = "light" | "dark" | "system";
|
||||
@@ -35,7 +37,7 @@ export function SettingsPage() {
|
||||
const [devMode, setDevMode] = useState(
|
||||
() => localStorage.getItem("tpf-dev-mode") === "true",
|
||||
);
|
||||
const [scenarioStep, setScenarioStep] = useState<ProzessSchritt>("neu");
|
||||
const [scenarioStep, setScenarioStep] = useState<Scenario>("erstgespraech");
|
||||
const [scenarioStatus, setScenarioStatus] = useState<"idle" | "done">("idle");
|
||||
|
||||
const {
|
||||
@@ -178,27 +180,29 @@ export function SettingsPage() {
|
||||
className="flex h-9 flex-1 rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-xs transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
value={scenarioStep}
|
||||
onChange={(e) => {
|
||||
setScenarioStep(e.target.value as ProzessSchritt);
|
||||
setScenarioStep(e.target.value as Scenario);
|
||||
setScenarioStatus("idle");
|
||||
}}
|
||||
>
|
||||
<option value="neu">Schritt 1 — Erstgespräch</option>
|
||||
<option value="erstgespraech">
|
||||
Schritt 1 — Erstgespräch
|
||||
</option>
|
||||
<option value="diagnose_erhalten">
|
||||
Schritt 2 — Diagnose erhalten
|
||||
</option>
|
||||
<option value="tss_beantragt">
|
||||
<option value="tss_kontaktiert">
|
||||
Schritt 3 — TSS kontaktiert
|
||||
</option>
|
||||
<option value="eigensuche">Schritt 4 — Eigensuche</option>
|
||||
<option value="antrag_gestellt">
|
||||
Schritt 5 — Antrag gestellt
|
||||
<option value="antrag_bereit">
|
||||
Schritt 5 — Antrag bereit
|
||||
</option>
|
||||
</select>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={async () => {
|
||||
await seedToStep(scenarioStep);
|
||||
await seedToScenario(scenarioStep);
|
||||
setScenarioStatus("done");
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { ProzessSchritt } from "@/shared/db/schema";
|
||||
import { dbExec } from "@/shared/hooks/use-db";
|
||||
|
||||
const MOCK_VORNAMEN = [
|
||||
@@ -80,6 +79,7 @@ function daysAgoISO(days: number) {
|
||||
}
|
||||
|
||||
async function clearData() {
|
||||
await dbExec("DELETE FROM sitzung");
|
||||
await dbExec("DELETE FROM kontakt");
|
||||
await dbExec("DELETE FROM sprechstunde");
|
||||
await dbExec("DELETE FROM therapeut");
|
||||
@@ -88,17 +88,13 @@ async function clearData() {
|
||||
async function resetNutzer() {
|
||||
await dbExec(
|
||||
`UPDATE nutzer SET
|
||||
aktueller_schritt = 'neu',
|
||||
dringlichkeitscode = FALSE,
|
||||
dringlichkeitscode_datum = NULL,
|
||||
tss_beantragt = FALSE,
|
||||
tss_beantragt_datum = NULL,
|
||||
tss_kontaktiert_datum = NULL,
|
||||
aktualisiert_am = NOW()
|
||||
WHERE id = 1`,
|
||||
);
|
||||
}
|
||||
|
||||
async function seedSprechstunde() {
|
||||
async function seedErstgespraech() {
|
||||
const result = await dbExec(
|
||||
`INSERT INTO therapeut (name, stadt, therapieform, telefon, email)
|
||||
VALUES ($1, $2, $3, $4, $5) RETURNING id`,
|
||||
@@ -112,31 +108,26 @@ async function seedSprechstunde() {
|
||||
);
|
||||
const therapeutId = (result.rows[0] as { id: number }).id;
|
||||
|
||||
await dbExec(
|
||||
`INSERT INTO sprechstunde (therapeut_id, datum, ergebnis, diagnose, dringlichkeitscode)
|
||||
VALUES ($1, $2, 'erstgespraech', 'F32.1', TRUE)`,
|
||||
[therapeutId, daysAgoISO(14)],
|
||||
const spResult = await dbExec(
|
||||
`INSERT INTO sprechstunde (therapeut_id, diagnose, dringlichkeitscode)
|
||||
VALUES ($1, 'F32.1', TRUE) RETURNING id`,
|
||||
[therapeutId],
|
||||
);
|
||||
const sprechstundeId = (spResult.rows[0] as { id: number }).id;
|
||||
|
||||
await dbExec(
|
||||
`UPDATE nutzer SET
|
||||
aktueller_schritt = 'diagnose_erhalten',
|
||||
dringlichkeitscode = TRUE,
|
||||
dringlichkeitscode_datum = $1::date,
|
||||
aktualisiert_am = NOW()
|
||||
WHERE id = 1`,
|
||||
[daysAgoISO(14)],
|
||||
);
|
||||
await dbExec("INSERT INTO sitzung (sprechstunde_id, datum) VALUES ($1, $2)", [
|
||||
sprechstundeId,
|
||||
daysAgoISO(14),
|
||||
]);
|
||||
await dbExec("INSERT INTO sitzung (sprechstunde_id, datum) VALUES ($1, $2)", [
|
||||
sprechstundeId,
|
||||
daysAgoISO(7),
|
||||
]);
|
||||
}
|
||||
|
||||
async function seedTssBeantragt() {
|
||||
async function seedTssKontaktiert() {
|
||||
await dbExec(
|
||||
`UPDATE nutzer SET
|
||||
aktueller_schritt = 'tss_beantragt',
|
||||
tss_beantragt = TRUE,
|
||||
tss_beantragt_datum = $1::date,
|
||||
aktualisiert_am = NOW()
|
||||
WHERE id = 1`,
|
||||
"UPDATE nutzer SET tss_kontaktiert_datum = $1, aktualisiert_am = NOW() WHERE id = 1",
|
||||
[daysAgoISO(10)],
|
||||
);
|
||||
}
|
||||
@@ -167,48 +158,45 @@ async function seedKontakte() {
|
||||
const ergebnis = MOCK_ERGEBNISSE[(i + j) % MOCK_ERGEBNISSE.length];
|
||||
const kanal = MOCK_KANALE[(i + j) % MOCK_KANALE.length];
|
||||
await dbExec(
|
||||
`INSERT INTO kontakt (therapeut_id, datum, kanal, ergebnis) VALUES ($1, $2, $3, $4)`,
|
||||
"INSERT INTO kontakt (therapeut_id, datum, kanal, ergebnis) VALUES ($1, $2, $3, $4)",
|
||||
[therapeutId, daysAgoISO(daysBack), kanal, ergebnis],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await dbExec(
|
||||
`UPDATE nutzer SET aktueller_schritt = 'eigensuche', aktualisiert_am = NOW() WHERE id = 1`,
|
||||
);
|
||||
}
|
||||
|
||||
// Steps in order — each builds on the previous
|
||||
const STEP_ORDER: ProzessSchritt[] = [
|
||||
"neu",
|
||||
export type Scenario =
|
||||
| "erstgespraech"
|
||||
| "diagnose_erhalten"
|
||||
| "tss_kontaktiert"
|
||||
| "eigensuche"
|
||||
| "antrag_bereit";
|
||||
|
||||
const SCENARIO_ORDER: Scenario[] = [
|
||||
"erstgespraech",
|
||||
"diagnose_erhalten",
|
||||
"tss_beantragt",
|
||||
"tss_kontaktiert",
|
||||
"eigensuche",
|
||||
"antrag_gestellt",
|
||||
"antrag_bereit",
|
||||
];
|
||||
|
||||
const STEP_SEEDERS: Record<ProzessSchritt, (() => Promise<void>) | null> = {
|
||||
neu: null,
|
||||
sprechstunde_absolviert: null,
|
||||
diagnose_erhalten: seedSprechstunde,
|
||||
tss_beantragt: seedTssBeantragt,
|
||||
const SCENARIO_SEEDERS: Record<Scenario, (() => Promise<void>) | null> = {
|
||||
erstgespraech: null,
|
||||
diagnose_erhalten: seedErstgespraech,
|
||||
tss_kontaktiert: seedTssKontaktiert,
|
||||
eigensuche: seedKontakte,
|
||||
antrag_gestellt: async () => {
|
||||
await dbExec(
|
||||
`UPDATE nutzer SET aktueller_schritt = 'antrag_gestellt', aktualisiert_am = NOW() WHERE id = 1`,
|
||||
);
|
||||
},
|
||||
antrag_bereit: null,
|
||||
};
|
||||
|
||||
export async function seedToStep(targetStep: ProzessSchritt) {
|
||||
export async function seedToScenario(target: Scenario) {
|
||||
await clearData();
|
||||
await resetNutzer();
|
||||
|
||||
const targetIndex = STEP_ORDER.indexOf(targetStep);
|
||||
const targetIndex = SCENARIO_ORDER.indexOf(target);
|
||||
if (targetIndex < 0) return;
|
||||
|
||||
for (let i = 0; i <= targetIndex; i++) {
|
||||
const seeder = STEP_SEEDERS[STEP_ORDER[i]];
|
||||
const seeder = SCENARIO_SEEDERS[SCENARIO_ORDER[i]];
|
||||
if (seeder) await seeder();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +129,7 @@ export async function deleteKontakt(id: number) {
|
||||
}
|
||||
|
||||
export async function deleteAllData() {
|
||||
await dbExec("DELETE FROM sitzung");
|
||||
await dbExec("DELETE FROM kontakt");
|
||||
await dbExec("DELETE FROM sprechstunde");
|
||||
await dbExec("DELETE FROM therapeut");
|
||||
|
||||
@@ -1,2 +1,8 @@
|
||||
export { ProcessStepper } from "./components/process-stepper";
|
||||
export { updateSchritt, useKontaktStats, useNutzer } from "./hooks";
|
||||
export type { ErstgespraechRow, ProcessStatus, StepStatus } from "./hooks";
|
||||
export {
|
||||
deriveProcessStatus,
|
||||
useErstgespraeche,
|
||||
useKontaktStats,
|
||||
useNutzer,
|
||||
} from "./hooks";
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user