add contact tracker data layer: schemas, hooks, db queries

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 11:15:17 +01:00
parent b884e4e8c4
commit 4e4be03466
4 changed files with 153 additions and 0 deletions

View File

@@ -0,0 +1,71 @@
import { dbExec, useDbQuery } from "@/shared/hooks/use-db";
import type { KontaktFormData, TherapeutFormData } from "./schema";
interface TherapeutMitKontakte {
id: number;
name: string;
stadt: string | null;
therapieform: string | null;
letzter_kontakt: string | null;
letztes_ergebnis: string | null;
kontakte_gesamt: number;
}
interface KontaktRow {
id: number;
therapeut_id: number;
datum: string;
kanal: string;
ergebnis: string;
notiz: string | null;
antwort_datum: string | null;
}
export function useTherapeutenListe() {
return useDbQuery<TherapeutMitKontakte>(`
SELECT
t.id, t.name, t.stadt, t.therapieform,
(SELECT k.datum FROM kontakt k WHERE k.therapeut_id = t.id ORDER BY k.datum DESC LIMIT 1) as letzter_kontakt,
(SELECT k.ergebnis FROM kontakt k WHERE k.therapeut_id = t.id ORDER BY k.datum DESC LIMIT 1) as letztes_ergebnis,
(SELECT COUNT(*) FROM kontakt k WHERE k.therapeut_id = t.id) as kontakte_gesamt
FROM therapeut t
ORDER BY t.erstellt_am DESC
`);
}
export function useKontakteForTherapeut(therapeutId: number) {
return useDbQuery<KontaktRow>(
"SELECT * FROM kontakt WHERE therapeut_id = $1 ORDER BY datum DESC",
[therapeutId],
[therapeutId],
);
}
export async function createTherapeut(
data: TherapeutFormData,
): Promise<number> {
const result = await dbExec(
`INSERT INTO therapeut (name, adresse, plz, stadt, telefon, email, website, therapieform)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
RETURNING id`,
[
data.name,
data.adresse,
data.plz,
data.stadt,
data.telefon,
data.email,
data.website,
data.therapieform,
],
);
return (result.rows[0] as { id: number }).id;
}
export async function createKontakt(data: KontaktFormData) {
await dbExec(
`INSERT INTO kontakt (therapeut_id, datum, kanal, ergebnis, notiz)
VALUES ($1, $2, $3, $4, $5)`,
[data.therapeut_id, data.datum, data.kanal, data.ergebnis, data.notiz],
);
}

View File

@@ -0,0 +1,8 @@
export {
createKontakt,
createTherapeut,
useKontakteForTherapeut,
useTherapeutenListe,
} from "./hooks";
export type { KontaktFormData, TherapeutFormData } from "./schema";
export { kontaktFormSchema, therapeutFormSchema } from "./schema";

View File

@@ -0,0 +1,45 @@
import { describe, expect, it } from "vitest";
import { kontaktFormSchema, therapeutFormSchema } from "./schema";
describe("therapeutFormSchema", () => {
it("accepts valid therapist", () => {
const result = therapeutFormSchema.safeParse({
name: "Dr. Schmidt",
plz: "10115",
stadt: "Berlin",
});
expect(result.success).toBe(true);
});
it("requires name", () => {
const result = therapeutFormSchema.safeParse({ name: "" });
expect(result.success).toBe(false);
});
it("allows empty optional fields", () => {
const result = therapeutFormSchema.safeParse({ name: "Dr. Schmidt" });
expect(result.success).toBe(true);
});
});
describe("kontaktFormSchema", () => {
it("accepts valid contact", () => {
const result = kontaktFormSchema.safeParse({
therapeut_id: 1,
datum: "2026-03-10",
kanal: "telefon",
ergebnis: "absage",
});
expect(result.success).toBe(true);
});
it("rejects missing date", () => {
const result = kontaktFormSchema.safeParse({
therapeut_id: 1,
datum: "",
kanal: "telefon",
ergebnis: "absage",
});
expect(result.success).toBe(false);
});
});

View File

@@ -0,0 +1,29 @@
import { z } from "zod";
import { kontaktErgebnisEnum, kontaktKanalEnum } from "@/shared/db/schema";
export const therapeutFormSchema = z.object({
name: z.string().min(1, "Name ist erforderlich."),
adresse: z.string().optional().default(""),
plz: z
.string()
.regex(/^\d{5}$/, "Bitte gib eine gültige PLZ ein.")
.optional()
.or(z.literal("")),
stadt: z.string().optional().default(""),
telefon: z.string().optional().default(""),
email: z.string().email("Ungültige E-Mail.").optional().or(z.literal("")),
website: z.string().optional().default(""),
therapieform: z.string().optional().default(""),
});
export type TherapeutFormData = z.infer<typeof therapeutFormSchema>;
export const kontaktFormSchema = z.object({
therapeut_id: z.number(),
datum: z.string().min(1, "Datum ist erforderlich."),
kanal: kontaktKanalEnum,
ergebnis: kontaktErgebnisEnum,
notiz: z.string().optional().default(""),
});
export type KontaktFormData = z.infer<typeof kontaktFormSchema>;