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 { Card, CardContent } from "@/shared/components/ui/card";
|
||||||
import { Separator } from "@/shared/components/ui/separator";
|
import { Separator } from "@/shared/components/ui/separator";
|
||||||
import { PdfExportButton } from "./pdf-export-button";
|
import { PdfExportButton } from "./pdf-export-button";
|
||||||
@@ -6,13 +10,15 @@ import { PdfExportButton } from "./pdf-export-button";
|
|||||||
interface ChecklistItem {
|
interface ChecklistItem {
|
||||||
label: string;
|
label: string;
|
||||||
fulfilled: boolean;
|
fulfilled: boolean;
|
||||||
|
visible: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AntragChecklist() {
|
export function AntragChecklist() {
|
||||||
const { data: nutzerRows, loading: nutzerLoading } = useNutzer();
|
const { data: nutzerRows, loading: nutzerLoading } = useNutzer();
|
||||||
const { data: statsRows, loading: statsLoading } = useKontaktStats();
|
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>;
|
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[] = [
|
const items: ChecklistItem[] = [
|
||||||
{
|
{
|
||||||
label: "Psychotherapeutische Sprechstunde besucht",
|
label: "Erstgespräch durchgeführt",
|
||||||
fulfilled: nutzer.aktueller_schritt !== "neu",
|
fulfilled: hasDiagnose,
|
||||||
|
visible: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Diagnose / Dringlichkeitscode erhalten",
|
label: "Dringlichkeitscode erhalten",
|
||||||
fulfilled: nutzer.dringlichkeitscode,
|
fulfilled: hasDringlichkeit,
|
||||||
|
visible: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Terminservicestelle (TSS) kontaktiert",
|
label: "Terminservicestelle (TSS) kontaktiert",
|
||||||
fulfilled: nutzer.tss_beantragt,
|
fulfilled: nutzer.tss_kontaktiert_datum != null,
|
||||||
|
visible: hasDringlichkeit,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Eigenständige Therapeutensuche dokumentiert",
|
label: "Therapeutensuche dokumentiert",
|
||||||
fulfilled: stats.absagen + stats.keine_antwort >= 5,
|
fulfilled: Number(stats.absagen) + Number(stats.keine_antwort) >= 5,
|
||||||
|
visible: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Absagenliste exportiert",
|
label: "Absagenliste exportiert",
|
||||||
fulfilled: false,
|
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 (
|
return (
|
||||||
<div className="flex flex-col gap-6">
|
<div className="flex flex-col gap-6">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-xl font-bold">Kostenerstattungs-Assistent</h1>
|
<h1 className="text-xl font-bold">Kostenerstattungs-Assistent</h1>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{fulfilledCount} von {items.length} Voraussetzungen erfüllt
|
{fulfilledCount} von {visibleItems.length} Voraussetzungen erfüllt
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
{items.map((item, index) => (
|
{visibleItems.map((item, index) => (
|
||||||
<Card key={item.label} className="py-4">
|
<Card key={item.label} className="py-4">
|
||||||
<CardContent className="flex items-center gap-4">
|
<CardContent className="flex items-center gap-4">
|
||||||
{item.fulfilled ? (
|
{item.fulfilled ? (
|
||||||
|
|||||||
@@ -10,14 +10,16 @@ import {
|
|||||||
Trash2,
|
Trash2,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useState } from "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 { deleteAllData } from "@/features/kontakte/hooks";
|
||||||
import { Button } from "@/shared/components/ui/button";
|
import { Button } from "@/shared/components/ui/button";
|
||||||
import { Card, CardContent } from "@/shared/components/ui/card";
|
import { Card, CardContent } from "@/shared/components/ui/card";
|
||||||
import { Label } from "@/shared/components/ui/label";
|
import { Label } from "@/shared/components/ui/label";
|
||||||
import { Separator } from "@/shared/components/ui/separator";
|
import { Separator } from "@/shared/components/ui/separator";
|
||||||
import { Switch } from "@/shared/components/ui/switch";
|
import { Switch } from "@/shared/components/ui/switch";
|
||||||
import type { ProzessSchritt } from "@/shared/db/schema";
|
|
||||||
import { useThemeStore } from "@/shared/hooks/use-theme";
|
import { useThemeStore } from "@/shared/hooks/use-theme";
|
||||||
|
|
||||||
type Theme = "light" | "dark" | "system";
|
type Theme = "light" | "dark" | "system";
|
||||||
@@ -35,7 +37,7 @@ export function SettingsPage() {
|
|||||||
const [devMode, setDevMode] = useState(
|
const [devMode, setDevMode] = useState(
|
||||||
() => localStorage.getItem("tpf-dev-mode") === "true",
|
() => 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 [scenarioStatus, setScenarioStatus] = useState<"idle" | "done">("idle");
|
||||||
|
|
||||||
const {
|
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"
|
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}
|
value={scenarioStep}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setScenarioStep(e.target.value as ProzessSchritt);
|
setScenarioStep(e.target.value as Scenario);
|
||||||
setScenarioStatus("idle");
|
setScenarioStatus("idle");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<option value="neu">Schritt 1 — Erstgespräch</option>
|
<option value="erstgespraech">
|
||||||
|
Schritt 1 — Erstgespräch
|
||||||
|
</option>
|
||||||
<option value="diagnose_erhalten">
|
<option value="diagnose_erhalten">
|
||||||
Schritt 2 — Diagnose erhalten
|
Schritt 2 — Diagnose erhalten
|
||||||
</option>
|
</option>
|
||||||
<option value="tss_beantragt">
|
<option value="tss_kontaktiert">
|
||||||
Schritt 3 — TSS kontaktiert
|
Schritt 3 — TSS kontaktiert
|
||||||
</option>
|
</option>
|
||||||
<option value="eigensuche">Schritt 4 — Eigensuche</option>
|
<option value="eigensuche">Schritt 4 — Eigensuche</option>
|
||||||
<option value="antrag_gestellt">
|
<option value="antrag_bereit">
|
||||||
Schritt 5 — Antrag gestellt
|
Schritt 5 — Antrag bereit
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await seedToStep(scenarioStep);
|
await seedToScenario(scenarioStep);
|
||||||
setScenarioStatus("done");
|
setScenarioStatus("done");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import type { ProzessSchritt } from "@/shared/db/schema";
|
|
||||||
import { dbExec } from "@/shared/hooks/use-db";
|
import { dbExec } from "@/shared/hooks/use-db";
|
||||||
|
|
||||||
const MOCK_VORNAMEN = [
|
const MOCK_VORNAMEN = [
|
||||||
@@ -80,6 +79,7 @@ function daysAgoISO(days: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function clearData() {
|
async function clearData() {
|
||||||
|
await dbExec("DELETE FROM sitzung");
|
||||||
await dbExec("DELETE FROM kontakt");
|
await dbExec("DELETE FROM kontakt");
|
||||||
await dbExec("DELETE FROM sprechstunde");
|
await dbExec("DELETE FROM sprechstunde");
|
||||||
await dbExec("DELETE FROM therapeut");
|
await dbExec("DELETE FROM therapeut");
|
||||||
@@ -88,17 +88,13 @@ async function clearData() {
|
|||||||
async function resetNutzer() {
|
async function resetNutzer() {
|
||||||
await dbExec(
|
await dbExec(
|
||||||
`UPDATE nutzer SET
|
`UPDATE nutzer SET
|
||||||
aktueller_schritt = 'neu',
|
tss_kontaktiert_datum = NULL,
|
||||||
dringlichkeitscode = FALSE,
|
|
||||||
dringlichkeitscode_datum = NULL,
|
|
||||||
tss_beantragt = FALSE,
|
|
||||||
tss_beantragt_datum = NULL,
|
|
||||||
aktualisiert_am = NOW()
|
aktualisiert_am = NOW()
|
||||||
WHERE id = 1`,
|
WHERE id = 1`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function seedSprechstunde() {
|
async function seedErstgespraech() {
|
||||||
const result = await dbExec(
|
const result = await dbExec(
|
||||||
`INSERT INTO therapeut (name, stadt, therapieform, telefon, email)
|
`INSERT INTO therapeut (name, stadt, therapieform, telefon, email)
|
||||||
VALUES ($1, $2, $3, $4, $5) RETURNING id`,
|
VALUES ($1, $2, $3, $4, $5) RETURNING id`,
|
||||||
@@ -112,31 +108,26 @@ async function seedSprechstunde() {
|
|||||||
);
|
);
|
||||||
const therapeutId = (result.rows[0] as { id: number }).id;
|
const therapeutId = (result.rows[0] as { id: number }).id;
|
||||||
|
|
||||||
await dbExec(
|
const spResult = await dbExec(
|
||||||
`INSERT INTO sprechstunde (therapeut_id, datum, ergebnis, diagnose, dringlichkeitscode)
|
`INSERT INTO sprechstunde (therapeut_id, diagnose, dringlichkeitscode)
|
||||||
VALUES ($1, $2, 'erstgespraech', 'F32.1', TRUE)`,
|
VALUES ($1, 'F32.1', TRUE) RETURNING id`,
|
||||||
[therapeutId, daysAgoISO(14)],
|
[therapeutId],
|
||||||
);
|
);
|
||||||
|
const sprechstundeId = (spResult.rows[0] as { id: number }).id;
|
||||||
|
|
||||||
await dbExec(
|
await dbExec("INSERT INTO sitzung (sprechstunde_id, datum) VALUES ($1, $2)", [
|
||||||
`UPDATE nutzer SET
|
sprechstundeId,
|
||||||
aktueller_schritt = 'diagnose_erhalten',
|
daysAgoISO(14),
|
||||||
dringlichkeitscode = TRUE,
|
]);
|
||||||
dringlichkeitscode_datum = $1::date,
|
await dbExec("INSERT INTO sitzung (sprechstunde_id, datum) VALUES ($1, $2)", [
|
||||||
aktualisiert_am = NOW()
|
sprechstundeId,
|
||||||
WHERE id = 1`,
|
daysAgoISO(7),
|
||||||
[daysAgoISO(14)],
|
]);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function seedTssBeantragt() {
|
async function seedTssKontaktiert() {
|
||||||
await dbExec(
|
await dbExec(
|
||||||
`UPDATE nutzer SET
|
"UPDATE nutzer SET tss_kontaktiert_datum = $1, aktualisiert_am = NOW() WHERE id = 1",
|
||||||
aktueller_schritt = 'tss_beantragt',
|
|
||||||
tss_beantragt = TRUE,
|
|
||||||
tss_beantragt_datum = $1::date,
|
|
||||||
aktualisiert_am = NOW()
|
|
||||||
WHERE id = 1`,
|
|
||||||
[daysAgoISO(10)],
|
[daysAgoISO(10)],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -167,48 +158,45 @@ async function seedKontakte() {
|
|||||||
const ergebnis = MOCK_ERGEBNISSE[(i + j) % MOCK_ERGEBNISSE.length];
|
const ergebnis = MOCK_ERGEBNISSE[(i + j) % MOCK_ERGEBNISSE.length];
|
||||||
const kanal = MOCK_KANALE[(i + j) % MOCK_KANALE.length];
|
const kanal = MOCK_KANALE[(i + j) % MOCK_KANALE.length];
|
||||||
await dbExec(
|
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],
|
[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
|
export type Scenario =
|
||||||
const STEP_ORDER: ProzessSchritt[] = [
|
| "erstgespraech"
|
||||||
"neu",
|
| "diagnose_erhalten"
|
||||||
|
| "tss_kontaktiert"
|
||||||
|
| "eigensuche"
|
||||||
|
| "antrag_bereit";
|
||||||
|
|
||||||
|
const SCENARIO_ORDER: Scenario[] = [
|
||||||
|
"erstgespraech",
|
||||||
"diagnose_erhalten",
|
"diagnose_erhalten",
|
||||||
"tss_beantragt",
|
"tss_kontaktiert",
|
||||||
"eigensuche",
|
"eigensuche",
|
||||||
"antrag_gestellt",
|
"antrag_bereit",
|
||||||
];
|
];
|
||||||
|
|
||||||
const STEP_SEEDERS: Record<ProzessSchritt, (() => Promise<void>) | null> = {
|
const SCENARIO_SEEDERS: Record<Scenario, (() => Promise<void>) | null> = {
|
||||||
neu: null,
|
erstgespraech: null,
|
||||||
sprechstunde_absolviert: null,
|
diagnose_erhalten: seedErstgespraech,
|
||||||
diagnose_erhalten: seedSprechstunde,
|
tss_kontaktiert: seedTssKontaktiert,
|
||||||
tss_beantragt: seedTssBeantragt,
|
|
||||||
eigensuche: seedKontakte,
|
eigensuche: seedKontakte,
|
||||||
antrag_gestellt: async () => {
|
antrag_bereit: null,
|
||||||
await dbExec(
|
|
||||||
`UPDATE nutzer SET aktueller_schritt = 'antrag_gestellt', aktualisiert_am = NOW() WHERE id = 1`,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function seedToStep(targetStep: ProzessSchritt) {
|
export async function seedToScenario(target: Scenario) {
|
||||||
await clearData();
|
await clearData();
|
||||||
await resetNutzer();
|
await resetNutzer();
|
||||||
|
|
||||||
const targetIndex = STEP_ORDER.indexOf(targetStep);
|
const targetIndex = SCENARIO_ORDER.indexOf(target);
|
||||||
if (targetIndex < 0) return;
|
if (targetIndex < 0) return;
|
||||||
|
|
||||||
for (let i = 0; i <= targetIndex; i++) {
|
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();
|
if (seeder) await seeder();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,6 +129,7 @@ export async function deleteKontakt(id: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteAllData() {
|
export async function deleteAllData() {
|
||||||
|
await dbExec("DELETE FROM sitzung");
|
||||||
await dbExec("DELETE FROM kontakt");
|
await dbExec("DELETE FROM kontakt");
|
||||||
await dbExec("DELETE FROM sprechstunde");
|
await dbExec("DELETE FROM sprechstunde");
|
||||||
await dbExec("DELETE FROM therapeut");
|
await dbExec("DELETE FROM therapeut");
|
||||||
|
|||||||
@@ -1,2 +1,8 @@
|
|||||||
export { ProcessStepper } from "./components/process-stepper";
|
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