Allow medications with only a generic name (no commercial name required) (#311)

* Initial plan

* feat: allow generic name only for medications (frontend changes)

- Add getMedDisplayName() helper for consistent name display
- Update validation to require either commercial or generic name
- Update all display locations to use display name fallback
- Add i18n keys for nameOrGenericRequired in en.json and de.json
- Remove required attribute from commercial name field
- Update FIELD_LIMITS.name.min from 1 to 0

Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com>

* feat: allow generic name only for medications (backend changes)

- Update Zod schema to allow empty name with cross-field refinement
- Update reminder scheduler to use name || genericName for display
- Update planner routes to match medications by display name
- Update existing tests to match new validation behavior

Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com>

* fix: update placeholder text and fix FIELD_LIMITS test

- Remove "(optional)" from generic name placeholder in en/de
- Update types.test.ts to expect FIELD_LIMITS.name.min = 0

Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com>
This commit is contained in:
Copilot
2026-02-25 21:29:25 +01:00
committed by GitHub
parent 691550fb33
commit 2a9ca39c24
23 changed files with 151 additions and 114 deletions
+7 -5
View File
@@ -3,6 +3,7 @@
// =============================================================================
import type { Medication } from "../types";
import { getMedDisplayName } from "../types";
/**
* Format a Date for ICS format (YYYYMMDDTHHMMSSZ)
@@ -18,6 +19,7 @@ function formatICSDate(date: Date): string {
* Generate and download an ICS calendar file for a medication's schedule
*/
export function generateICS(med: Medication): void {
const displayName = getMedDisplayName(med);
const events = med.blisters
.map((blister, idx) => {
const start = new Date(blister.start);
@@ -25,9 +27,9 @@ export function generateICS(med: Medication): void {
const interval = blister.every;
const pillInfo = `${blister.usage} pill${blister.usage !== 1 ? "s" : ""}${med.pillWeightMg ? ` (${blister.usage * med.pillWeightMg} mg)` : ""}`;
const summary = `💊 ${med.name} - ${pillInfo}`;
const summary = `💊 ${displayName} - ${pillInfo}`;
const description = [
`Medication: ${med.name}`,
`Medication: ${displayName}`,
med.genericName ? `Generic: ${med.genericName}` : "",
med.takenBy && med.takenBy.length > 0 ? `For: ${med.takenBy.join(", ")}` : "",
`Dosage: ${pillInfo}`,
@@ -48,7 +50,7 @@ DESCRIPTION:${description}
BEGIN:VALARM
TRIGGER:-PT5M
ACTION:DISPLAY
DESCRIPTION:Time to take ${med.name}
DESCRIPTION:Time to take ${displayName}
END:VALARM
END:VEVENT`;
})
@@ -59,7 +61,7 @@ VERSION:2.0
PRODID:-//MedAssist-ng//Medication Schedule//EN
CALSCALE:GREGORIAN
METHOD:PUBLISH
X-WR-CALNAME:${med.name} Schedule
X-WR-CALNAME:${displayName} Schedule
${events}
END:VCALENDAR`;
@@ -67,7 +69,7 @@ END:VCALENDAR`;
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = `${med.name.replace(/[^a-zA-Z0-9]/g, "_")}_schedule.ics`;
link.download = `${displayName.replace(/[^a-zA-Z0-9]/g, "_")}_schedule.ics`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);