feat(settings): edit emergency profile under Anamnese

Add a Notfalldaten card to the Anamnese settings section with a manager
for the three enum facts and the three free-text fields, writing through
the new emergency route. Add every string across the six shipped locales.
This commit is contained in:
Marc-André Bombeck committed 2026-08-10 05:32:59 +02:00
1 parent 310a9a4690
commit 57938982ea
9 files changed
+679 -11

No files matched your search

+79 -2
View File
@@ -295,7 +295,43 @@
"immunizationsColDate": "Datum",
"immunizationsColVaccine": "Impfstoff",
"immunizationsColDose": "Dosis",
"immunizationsColLot": "Charge"
"immunizationsColLot": "Charge",
"emergency": {
"title": "Notfalldaten",
"subtitle": "Für den Einsatz im Notfall. Selbst angegeben; wenn möglich prüfen.",
"none": "Nicht erfasst",
"unreadable": "Gespeicherter Wert konnte nicht gelesen werden",
"bloodType": "Blutgruppe",
"bloodTypeUnknown": "Unbekannt",
"bloodTypeValues": {
"A_POS": "A positiv",
"A_NEG": "A negativ",
"B_POS": "B positiv",
"B_NEG": "B negativ",
"AB_POS": "AB positiv",
"AB_NEG": "AB negativ",
"O_POS": "0 positiv",
"O_NEG": "0 negativ"
},
"severeAllergies": "Schwere Allergien",
"activeMedications": "Aktuelle Medikamente",
"chronicConditions": "Chronische Erkrankungen",
"implants": "Implantate und Geräte",
"advanceDirective": "Patientenverfügung",
"advanceDirectiveValues": {
"EXISTS": "Vorhanden",
"NONE": "Keine",
"UNKNOWN": "Keine Angabe"
},
"organDonor": "Organspender",
"organDonorValues": {
"YES": "Ja",
"NO": "Nein",
"UNKNOWN": "Keine Angabe"
},
"contacts": "Notfallkontakte",
"notes": "Weitere Hinweise"
}
},
"errorBoundary": {
"title": "Da ist etwas schiefgelaufen",
@@ -9311,6 +9347,46 @@
"loadError": "Vorerkrankungen konnten nicht geladen werden.",
"savedToast": "Erkrankungen gespeichert.",
"saveError": "Speichern fehlgeschlagen. Bitte versuche es erneut."
},
"emergency": {
"cardTitle": "Notfalldaten",
"cardDescription": "Was Rettungskräfte zuerst brauchen: Blutgruppe, Kontakte, Verfügungen.",
"bloodType": "Blutgruppe",
"organDonor": "Organspender",
"advanceDirective": "Patientenverfügung",
"contacts": "Notfallkontakte",
"implants": "Implantate und Geräte",
"note": "Weitere Hinweise",
"notRecorded": "Nicht erfasst",
"bloodTypeValues": {
"A_POS": "A positiv",
"A_NEG": "A negativ",
"B_POS": "B positiv",
"B_NEG": "B negativ",
"AB_POS": "AB positiv",
"AB_NEG": "AB negativ",
"O_POS": "0 positiv",
"O_NEG": "0 negativ",
"UNKNOWN": "Unbekannt"
},
"organDonorValues": {
"YES": "Ja",
"NO": "Nein",
"UNKNOWN": "Keine Angabe"
},
"advanceDirectiveValues": {
"EXISTS": "Vorhanden",
"NONE": "Keine",
"UNKNOWN": "Keine Angabe"
},
"contactsPlaceholder": "Name und Telefonnummer für den Notfall",
"implantsPlaceholder": "z. B. Herzschrittmacher, Insulinpumpe, Metallimplantat",
"notePlaceholder": "Alles, was im Notfall wichtig sein könnte",
"reportHint": "Dies erscheint auf der ersten Seite deines Arztberichts.",
"save": "Speichern",
"savedToast": "Notfalldaten gespeichert",
"saveError": "Notfalldaten konnten nicht gespeichert werden.",
"loadError": "Notfalldaten konnten nicht geladen werden."
}
},
"environment": {
@@ -9834,7 +9910,8 @@
"scopeCount": "{count} Einträge aus {groups} Bereichen",
"scopeSensitive": "inkl. {names}",
"leafVisits": "Arztbesuche",
"leafImmunizations": "Impfungen"
"leafImmunizations": "Impfungen",
"leafEmergency": "Notfalldaten (Blutgruppe, Kontakte, Verfügungen)"
},
"recordSharing": {
"banner": {
+78 -1
View File
@@ -150,6 +150,42 @@
"height": "Height",
"period": "Reporting period",
"createdOn": "Created on",
"emergency": {
"title": "Emergency information",
"subtitle": "For use in an acute situation. Self-reported; confirm where possible.",
"none": "Not recorded",
"unreadable": "Stored value could not be read",
"bloodType": "Blood type",
"bloodTypeUnknown": "Unknown",
"bloodTypeValues": {
"A_POS": "A positive",
"A_NEG": "A negative",
"B_POS": "B positive",
"B_NEG": "B negative",
"AB_POS": "AB positive",
"AB_NEG": "AB negative",
"O_POS": "O positive",
"O_NEG": "O negative"
},
"severeAllergies": "Severe allergies",
"activeMedications": "Current medications",
"chronicConditions": "Chronic conditions",
"implants": "Implants and devices",
"advanceDirective": "Advance directive",
"advanceDirectiveValues": {
"EXISTS": "Exists",
"NONE": "None",
"UNKNOWN": "Not stated"
},
"organDonor": "Organ donor",
"organDonorValues": {
"YES": "Yes",
"NO": "No",
"UNKNOWN": "Not stated"
},
"contacts": "Emergency contacts",
"notes": "Additional notes"
},
"vitalsTitle": "Vital signs — Summary",
"colParameter": "Parameter",
"colCurrent": "Current",
@@ -9311,6 +9347,46 @@
"loadError": "Couldn't load your conditions.",
"savedToast": "Conditions saved.",
"saveError": "Could not save. Please try again."
},
"emergency": {
"cardTitle": "Emergency information",
"cardDescription": "What a first responder needs first: blood type, contacts, directives.",
"bloodType": "Blood type",
"organDonor": "Organ donor",
"advanceDirective": "Advance directive",
"contacts": "Emergency contacts",
"implants": "Implants and devices",
"note": "Additional notes",
"notRecorded": "Not recorded",
"bloodTypeValues": {
"A_POS": "A positive",
"A_NEG": "A negative",
"B_POS": "B positive",
"B_NEG": "B negative",
"AB_POS": "AB positive",
"AB_NEG": "AB negative",
"O_POS": "O positive",
"O_NEG": "O negative",
"UNKNOWN": "Unknown"
},
"organDonorValues": {
"YES": "Yes",
"NO": "No",
"UNKNOWN": "Not stated"
},
"advanceDirectiveValues": {
"EXISTS": "Exists",
"NONE": "None",
"UNKNOWN": "Not stated"
},
"contactsPlaceholder": "Name and phone number of who to call",
"implantsPlaceholder": "e.g. pacemaker, insulin pump, metal implant",
"notePlaceholder": "Anything a clinician should know in an emergency",
"reportHint": "This appears on the first page of your doctor report.",
"save": "Save",
"savedToast": "Emergency information saved",
"saveError": "Couldn't save your emergency information.",
"loadError": "Couldn't load your emergency information."
}
},
"environment": {
@@ -9834,7 +9910,8 @@
"scopeCount": "{count} entries from {groups} areas",
"scopeSensitive": "incl. {names}",
"leafVisits": "Visits",
"leafImmunizations": "Immunizations"
"leafImmunizations": "Immunizations",
"leafEmergency": "Emergency information (blood type, contacts, directives)"
},
"recordSharing": {
"banner": {
+79 -2
View File
@@ -295,7 +295,43 @@
"immunizationsColDate": "Fecha",
"immunizationsColVaccine": "Vacuna",
"immunizationsColDose": "Dosis",
"immunizationsColLot": "Lote"
"immunizationsColLot": "Lote",
"emergency": {
"title": "Información de emergencia",
"subtitle": "Para uso en una situación aguda. Autoinformado; confirmar cuando sea posible.",
"none": "No registrado",
"unreadable": "No se pudo leer el valor guardado",
"bloodType": "Grupo sanguíneo",
"bloodTypeUnknown": "Desconocido",
"bloodTypeValues": {
"A_POS": "A positivo",
"A_NEG": "A negativo",
"B_POS": "B positivo",
"B_NEG": "B negativo",
"AB_POS": "AB positivo",
"AB_NEG": "AB negativo",
"O_POS": "O positivo",
"O_NEG": "O negativo"
},
"severeAllergies": "Alergias graves",
"activeMedications": "Medicación actual",
"chronicConditions": "Enfermedades crónicas",
"implants": "Implantes y dispositivos",
"advanceDirective": "Voluntades anticipadas",
"advanceDirectiveValues": {
"EXISTS": "Existe",
"NONE": "Ninguna",
"UNKNOWN": "Sin indicar"
},
"organDonor": "Donante de órganos",
"organDonorValues": {
"YES": "Sí",
"NO": "No",
"UNKNOWN": "Sin indicar"
},
"contacts": "Contactos de emergencia",
"notes": "Notas adicionales"
}
},
"errorBoundary": {
"title": "Algo ha salido mal",
@@ -9311,6 +9347,46 @@
"loadError": "No se pudieron cargar tus enfermedades.",
"savedToast": "Enfermedades guardadas.",
"saveError": "No se pudo guardar. Inténtalo de nuevo."
},
"emergency": {
"cardTitle": "Información de emergencia",
"cardDescription": "Lo que un servicio de emergencia necesita primero: grupo sanguíneo, contactos, voluntades.",
"bloodType": "Grupo sanguíneo",
"organDonor": "Donante de órganos",
"advanceDirective": "Voluntades anticipadas",
"contacts": "Contactos de emergencia",
"implants": "Implantes y dispositivos",
"note": "Notas adicionales",
"notRecorded": "No registrado",
"bloodTypeValues": {
"A_POS": "A positivo",
"A_NEG": "A negativo",
"B_POS": "B positivo",
"B_NEG": "B negativo",
"AB_POS": "AB positivo",
"AB_NEG": "AB negativo",
"O_POS": "O positivo",
"O_NEG": "O negativo",
"UNKNOWN": "Desconocido"
},
"organDonorValues": {
"YES": "Sí",
"NO": "No",
"UNKNOWN": "Sin indicar"
},
"advanceDirectiveValues": {
"EXISTS": "Existe",
"NONE": "Ninguna",
"UNKNOWN": "Sin indicar"
},
"contactsPlaceholder": "Nombre y teléfono de a quién llamar",
"implantsPlaceholder": "p. ej. marcapasos, bomba de insulina, implante metálico",
"notePlaceholder": "Cualquier cosa que deba saber un profesional en una emergencia",
"reportHint": "Aparece en la primera página de tu informe médico.",
"save": "Guardar",
"savedToast": "Información de emergencia guardada",
"saveError": "No se pudo guardar la información de emergencia.",
"loadError": "No se pudo cargar la información de emergencia."
}
},
"environment": {
@@ -9834,7 +9910,8 @@
"scopeCount": "{count} entradas de {groups} áreas",
"scopeSensitive": "incl. {names}",
"leafVisits": "Visitas médicas",
"leafImmunizations": "Vacunas"
"leafImmunizations": "Vacunas",
"leafEmergency": "Información de emergencia (grupo sanguíneo, contactos, voluntades)"
},
"recordSharing": {
"banner": {
+79 -2
View File
@@ -295,7 +295,43 @@
"immunizationsColDate": "Date",
"immunizationsColVaccine": "Vaccin",
"immunizationsColDose": "Dose",
"immunizationsColLot": "Lot"
"immunizationsColLot": "Lot",
"emergency": {
"title": "Informations d'urgence",
"subtitle": "À utiliser en situation aiguë. Déclaré par le patient ; à confirmer si possible.",
"none": "Non renseigné",
"unreadable": "La valeur enregistrée n'a pas pu être lue",
"bloodType": "Groupe sanguin",
"bloodTypeUnknown": "Inconnu",
"bloodTypeValues": {
"A_POS": "A positif",
"A_NEG": "A négatif",
"B_POS": "B positif",
"B_NEG": "B négatif",
"AB_POS": "AB positif",
"AB_NEG": "AB négatif",
"O_POS": "O positif",
"O_NEG": "O négatif"
},
"severeAllergies": "Allergies graves",
"activeMedications": "Médicaments actuels",
"chronicConditions": "Maladies chroniques",
"implants": "Implants et dispositifs",
"advanceDirective": "Directives anticipées",
"advanceDirectiveValues": {
"EXISTS": "Existent",
"NONE": "Aucune",
"UNKNOWN": "Non précisé"
},
"organDonor": "Don d'organes",
"organDonorValues": {
"YES": "Oui",
"NO": "Non",
"UNKNOWN": "Non précisé"
},
"contacts": "Contacts d'urgence",
"notes": "Notes complémentaires"
}
},
"errorBoundary": {
"title": "Une erreur est survenue",
@@ -9311,6 +9347,46 @@
"loadError": "Impossible de charger vos antécédents.",
"savedToast": "Affections enregistrées.",
"saveError": "Impossible d'enregistrer. Veuillez réessayer."
},
"emergency": {
"cardTitle": "Informations d'urgence",
"cardDescription": "Ce qu'un secouriste doit savoir d'abord : groupe sanguin, contacts, directives.",
"bloodType": "Groupe sanguin",
"organDonor": "Don d'organes",
"advanceDirective": "Directives anticipées",
"contacts": "Contacts d'urgence",
"implants": "Implants et dispositifs",
"note": "Notes complémentaires",
"notRecorded": "Non renseigné",
"bloodTypeValues": {
"A_POS": "A positif",
"A_NEG": "A négatif",
"B_POS": "B positif",
"B_NEG": "B négatif",
"AB_POS": "AB positif",
"AB_NEG": "AB négatif",
"O_POS": "O positif",
"O_NEG": "O négatif",
"UNKNOWN": "Inconnu"
},
"organDonorValues": {
"YES": "Oui",
"NO": "Non",
"UNKNOWN": "Non précisé"
},
"advanceDirectiveValues": {
"EXISTS": "Existent",
"NONE": "Aucune",
"UNKNOWN": "Non précisé"
},
"contactsPlaceholder": "Nom et numéro de téléphone à appeler",
"implantsPlaceholder": "ex. stimulateur cardiaque, pompe à insuline, implant métallique",
"notePlaceholder": "Tout ce qu'un soignant devrait savoir en urgence",
"reportHint": "Ceci apparaît sur la première page de votre rapport médical.",
"save": "Enregistrer",
"savedToast": "Informations d'urgence enregistrées",
"saveError": "Impossible d'enregistrer vos informations d'urgence.",
"loadError": "Impossible de charger vos informations d'urgence."
}
},
"environment": {
@@ -9834,7 +9910,8 @@
"scopeCount": "{count} éléments issus de {groups} domaines",
"scopeSensitive": "dont {names}",
"leafVisits": "Consultations",
"leafImmunizations": "Vaccinations"
"leafImmunizations": "Vaccinations",
"leafEmergency": "Informations d'urgence (groupe sanguin, contacts, directives)"
},
"recordSharing": {
"banner": {
+79 -2
View File
@@ -295,7 +295,43 @@
"immunizationsColDate": "Data",
"immunizationsColVaccine": "Vaccino",
"immunizationsColDose": "Dose",
"immunizationsColLot": "Lotto"
"immunizationsColLot": "Lotto",
"emergency": {
"title": "Informazioni di emergenza",
"subtitle": "Da usare in situazioni acute. Autodichiarato; confermare se possibile.",
"none": "Non registrato",
"unreadable": "Impossibile leggere il valore salvato",
"bloodType": "Gruppo sanguigno",
"bloodTypeUnknown": "Sconosciuto",
"bloodTypeValues": {
"A_POS": "A positivo",
"A_NEG": "A negativo",
"B_POS": "B positivo",
"B_NEG": "B negativo",
"AB_POS": "AB positivo",
"AB_NEG": "AB negativo",
"O_POS": "0 positivo",
"O_NEG": "0 negativo"
},
"severeAllergies": "Allergie gravi",
"activeMedications": "Farmaci attuali",
"chronicConditions": "Malattie croniche",
"implants": "Impianti e dispositivi",
"advanceDirective": "Disposizioni anticipate di trattamento",
"advanceDirectiveValues": {
"EXISTS": "Presenti",
"NONE": "Nessuna",
"UNKNOWN": "Non indicato"
},
"organDonor": "Donatore di organi",
"organDonorValues": {
"YES": "Sì",
"NO": "No",
"UNKNOWN": "Non indicato"
},
"contacts": "Contatti di emergenza",
"notes": "Note aggiuntive"
}
},
"errorBoundary": {
"title": "Qualcosa è andato storto",
@@ -9311,6 +9347,46 @@
"loadError": "Impossibile caricare le tue patologie.",
"savedToast": "Patologie salvate.",
"saveError": "Impossibile salvare. Riprova."
},
"emergency": {
"cardTitle": "Informazioni di emergenza",
"cardDescription": "Ciò che serve subito ai soccorritori: gruppo sanguigno, contatti, disposizioni.",
"bloodType": "Gruppo sanguigno",
"organDonor": "Donatore di organi",
"advanceDirective": "Disposizioni anticipate di trattamento",
"contacts": "Contatti di emergenza",
"implants": "Impianti e dispositivi",
"note": "Note aggiuntive",
"notRecorded": "Non registrato",
"bloodTypeValues": {
"A_POS": "A positivo",
"A_NEG": "A negativo",
"B_POS": "B positivo",
"B_NEG": "B negativo",
"AB_POS": "AB positivo",
"AB_NEG": "AB negativo",
"O_POS": "0 positivo",
"O_NEG": "0 negativo",
"UNKNOWN": "Sconosciuto"
},
"organDonorValues": {
"YES": "Sì",
"NO": "No",
"UNKNOWN": "Non indicato"
},
"advanceDirectiveValues": {
"EXISTS": "Presenti",
"NONE": "Nessuna",
"UNKNOWN": "Non indicato"
},
"contactsPlaceholder": "Nome e numero di telefono di chi chiamare",
"implantsPlaceholder": "es. pacemaker, microinfusore, impianto metallico",
"notePlaceholder": "Qualsiasi cosa un medico dovrebbe sapere in emergenza",
"reportHint": "Compare sulla prima pagina del referto medico.",
"save": "Salva",
"savedToast": "Informazioni di emergenza salvate",
"saveError": "Impossibile salvare le informazioni di emergenza.",
"loadError": "Impossibile caricare le informazioni di emergenza."
}
},
"environment": {
@@ -9834,7 +9910,8 @@
"scopeCount": "{count} voci da {groups} aree",
"scopeSensitive": "incl. {names}",
"leafVisits": "Visite mediche",
"leafImmunizations": "Vaccinazioni"
"leafImmunizations": "Vaccinazioni",
"leafEmergency": "Informazioni di emergenza (gruppo sanguigno, contatti, disposizioni)"
},
"recordSharing": {
"banner": {
+79 -2
View File
@@ -295,7 +295,43 @@
"immunizationsColDate": "Data",
"immunizationsColVaccine": "Szczepionka",
"immunizationsColDose": "Dawka",
"immunizationsColLot": "Seria"
"immunizationsColLot": "Seria",
"emergency": {
"title": "Informacje na wypadek nagły",
"subtitle": "Do użycia w sytuacji nagłej. Dane deklarowane; potwierdź, jeśli to możliwe.",
"none": "Nie podano",
"unreadable": "Nie udało się odczytać zapisanej wartości",
"bloodType": "Grupa krwi",
"bloodTypeUnknown": "Nieznana",
"bloodTypeValues": {
"A_POS": "A dodatnia",
"A_NEG": "A ujemna",
"B_POS": "B dodatnia",
"B_NEG": "B ujemna",
"AB_POS": "AB dodatnia",
"AB_NEG": "AB ujemna",
"O_POS": "0 dodatnia",
"O_NEG": "0 ujemna"
},
"severeAllergies": "Ciężkie alergie",
"activeMedications": "Aktualne leki",
"chronicConditions": "Choroby przewlekłe",
"implants": "Implanty i urządzenia",
"advanceDirective": "Oświadczenie woli pacjenta",
"advanceDirectiveValues": {
"EXISTS": "Istnieje",
"NONE": "Brak",
"UNKNOWN": "Nie podano"
},
"organDonor": "Dawca narządów",
"organDonorValues": {
"YES": "Tak",
"NO": "Nie",
"UNKNOWN": "Nie podano"
},
"contacts": "Kontakty w nagłych wypadkach",
"notes": "Dodatkowe uwagi"
}
},
"errorBoundary": {
"title": "Coś poszło nie tak",
@@ -9311,6 +9347,46 @@
"loadError": "Nie udało się wczytać Twoich chorób.",
"savedToast": "Choroby zapisane.",
"saveError": "Nie udało się zapisać. Spróbuj ponownie."
},
"emergency": {
"cardTitle": "Informacje na wypadek nagły",
"cardDescription": "To, czego ratownik potrzebuje najpierw: grupa krwi, kontakty, oświadczenia.",
"bloodType": "Grupa krwi",
"organDonor": "Dawca narządów",
"advanceDirective": "Oświadczenie woli pacjenta",
"contacts": "Kontakty w nagłych wypadkach",
"implants": "Implanty i urządzenia",
"note": "Dodatkowe uwagi",
"notRecorded": "Nie podano",
"bloodTypeValues": {
"A_POS": "A dodatnia",
"A_NEG": "A ujemna",
"B_POS": "B dodatnia",
"B_NEG": "B ujemna",
"AB_POS": "AB dodatnia",
"AB_NEG": "AB ujemna",
"O_POS": "0 dodatnia",
"O_NEG": "0 ujemna",
"UNKNOWN": "Nieznana"
},
"organDonorValues": {
"YES": "Tak",
"NO": "Nie",
"UNKNOWN": "Nie podano"
},
"advanceDirectiveValues": {
"EXISTS": "Istnieje",
"NONE": "Brak",
"UNKNOWN": "Nie podano"
},
"contactsPlaceholder": "Imię i numer telefonu osoby do kontaktu",
"implantsPlaceholder": "np. rozrusznik serca, pompa insulinowa, implant metalowy",
"notePlaceholder": "Wszystko, co lekarz powinien wiedzieć w nagłym wypadku",
"reportHint": "Pojawia się na pierwszej stronie raportu dla lekarza.",
"save": "Zapisz",
"savedToast": "Informacje na wypadek nagły zapisane",
"saveError": "Nie udało się zapisać informacji na wypadek nagły.",
"loadError": "Nie udało się wczytać informacji na wypadek nagły."
}
},
"environment": {
@@ -9834,7 +9910,8 @@
"scopeCount": "{count} pozycji z {groups} obszarów",
"scopeSensitive": "w tym {names}",
"leafVisits": "Wizyty lekarskie",
"leafImmunizations": "Szczepienia"
"leafImmunizations": "Szczepienia",
"leafEmergency": "Informacje na wypadek nagły (grupa krwi, kontakty, oświadczenia)"
},
"recordSharing": {
"banner": {
@@ -0,0 +1,192 @@
"use client";
import { useState } from "react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { Loader2 } from "lucide-react";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { QueryErrorCard } from "@/components/ui/query-error-card";
import { Textarea } from "@/components/ui/textarea";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { toastWrittenOutcome } from "@/components/outcome/outcome-toast";
import { apiGet, apiPatch } from "@/lib/api/api-fetch";
import { useTranslations } from "@/lib/i18n/context";
import { queryKeys } from "@/lib/query-keys";
import {
ADVANCE_DIRECTIVE_STATUS_VALUES,
EMERGENCY_BLOOD_TYPE_VALUES,
EMERGENCY_CONTACTS_MAX,
EMERGENCY_IMPLANTS_MAX,
EMERGENCY_NOTE_MAX,
ORGAN_DONOR_STATUS_VALUES,
type EmergencyProfileDto,
} from "@/lib/validations/emergency-profile";
interface Draft {
bloodType: string;
organDonor: string;
advanceDirective: string;
contacts: string;
implants: string;
note: string;
}
function draftFromDto(dto: EmergencyProfileDto): Draft {
return {
bloodType: dto.bloodType ?? "",
organDonor: dto.organDonor ?? "",
advanceDirective: dto.advanceDirective ?? "",
contacts: dto.contacts ?? "",
implants: dto.implants ?? "",
note: dto.note ?? "",
};
}
export function EmergencyProfileManager() {
const { t } = useTranslations();
const queryClient = useQueryClient();
const [draft, setDraft] = useState<Draft | null>(null);
const query = useQuery({
queryKey: queryKeys.emergencyProfile(),
queryFn: () => apiGet<EmergencyProfileDto>("/api/anamnesis/emergency"),
});
const current = draft ?? (query.data ? draftFromDto(query.data) : null);
const save = useMutation({
mutationKey: queryKeys.emergencyProfile(),
mutationFn: (input: Draft) =>
apiPatch<EmergencyProfileDto>("/api/anamnesis/emergency", {
// A select left at "" is "not recorded" and is omitted so a partial
// save leaves the column untouched; the free-text fields always ride,
// with an emptied field clearing its column server-side.
...(input.bloodType ? { bloodType: input.bloodType } : {}),
...(input.organDonor ? { organDonor: input.organDonor } : {}),
...(input.advanceDirective
? { advanceDirective: input.advanceDirective }
: {}),
contacts: input.contacts,
implants: input.implants,
note: input.note,
}),
onSuccess: (data) => {
setDraft(draftFromDto(data));
queryClient.setQueryData(queryKeys.emergencyProfile(), data);
toastWrittenOutcome("success", t("records.emergency.savedToast"));
},
onError: () => toast.error(t("records.emergency.saveError")),
});
if (query.isError) {
return (
<QueryErrorCard
title={t("records.emergency.loadError")}
onRetry={() => void query.refetch()}
/>
);
}
const disabled = query.isLoading || save.isPending || current === null;
const set = (patch: Partial<Draft>) =>
setDraft((prev) => ({ ...(prev ?? draftFromDto(query.data!)), ...patch }));
const enumField = (
key: "bloodType" | "organDonor" | "advanceDirective",
values: readonly string[],
valuePrefix: string,
) => (
<div className="space-y-2">
<Label htmlFor={`emergency-${key}`}>
{t(`records.emergency.${key}`)}
</Label>
<Select
value={current?.[key] ?? ""}
disabled={disabled}
onValueChange={(next) => set({ [key]: next } as Partial<Draft>)}
>
<SelectTrigger id={`emergency-${key}`}>
<SelectValue placeholder={t("records.emergency.notRecorded")} />
</SelectTrigger>
<SelectContent>
{values.map((option) => (
<SelectItem key={option} value={option}>
{t(`records.emergency.${valuePrefix}.${option}`)}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
);
const textField = (
key: "contacts" | "implants" | "note",
max: number,
rows: number,
) => (
<div className="space-y-2">
<Label htmlFor={`emergency-${key}`}>
{t(`records.emergency.${key}`)}
</Label>
<Textarea
id={`emergency-${key}`}
value={current?.[key] ?? ""}
maxLength={max}
rows={rows}
disabled={disabled}
placeholder={t(`records.emergency.${key}Placeholder`)}
onChange={(event) =>
set({ [key]: event.target.value } as Partial<Draft>)
}
/>
</div>
);
return (
<div className="space-y-6" data-slot="emergency-profile-manager">
<div className="grid gap-4 md:grid-cols-3">
{enumField("bloodType", EMERGENCY_BLOOD_TYPE_VALUES, "bloodTypeValues")}
{enumField("organDonor", ORGAN_DONOR_STATUS_VALUES, "organDonorValues")}
{enumField(
"advanceDirective",
ADVANCE_DIRECTIVE_STATUS_VALUES,
"advanceDirectiveValues",
)}
</div>
{textField("contacts", EMERGENCY_CONTACTS_MAX, 3)}
{textField("implants", EMERGENCY_IMPLANTS_MAX, 2)}
{textField("note", EMERGENCY_NOTE_MAX, 3)}
<p className="text-muted-foreground text-xs">
{t("records.emergency.reportHint")}
</p>
<div className="flex items-center justify-end">
<Button
type="button"
size="sm"
className="min-h-11 sm:min-h-9"
disabled={disabled || current === null}
onClick={() => current && save.mutate(current)}
>
{save.isPending && (
<Loader2
className="size-4 animate-spin motion-reduce:animate-none"
aria-hidden
/>
)}
{t("records.emergency.save")}
</Button>
</div>
</div>
);
}
@@ -27,6 +27,9 @@ vi.mock("@/components/records/allergy-manager", () => ({
vi.mock("@/components/records/family-history-manager", () => ({
FamilyHistoryManager: () => null,
}));
vi.mock("@/components/records/emergency-profile-manager", () => ({
EmergencyProfileManager: () => null,
}));
function renderSection(): string {
return renderToStaticMarkup(
@@ -23,6 +23,7 @@ import {
EyeOff,
HeartPulse,
ShieldAlert,
Siren,
Users,
} from "lucide-react";
@@ -33,6 +34,7 @@ import { useModuleEnabled } from "@/hooks/use-module-enabled";
import { AllergyManager } from "@/components/records/allergy-manager";
import { ConditionsManager } from "@/components/records/conditions-manager";
import { EmergencyProfileManager } from "@/components/records/emergency-profile-manager";
import { FamilyHistoryManager } from "@/components/records/family-history-manager";
import { AiProfileInclusionManager } from "@/components/records/ai-profile-inclusion-manager";
import { HealthProfileFactsManager } from "@/components/records/health-profile-facts-manager";
@@ -91,6 +93,15 @@ export function AnamnesisSection() {
<FamilyHistoryManager />
</SettingsCard>
<SettingsCard>
<SettingsCardHeader
icon={Siren}
title={t("records.emergency.cardTitle")}
description={t("records.emergency.cardDescription")}
/>
<EmergencyProfileManager />
</SettingsCard>
<p className="text-muted-foreground text-xs">{t("records.disclaimer")}</p>
</div>
);