diff --git a/gerbil-manager-web/e2e/mock-data.ts b/gerbil-manager-web/e2e/mock-data.ts index f61fcb3..11f197b 100644 --- a/gerbil-manager-web/e2e/mock-data.ts +++ b/gerbil-manager-web/e2e/mock-data.ts @@ -91,7 +91,7 @@ function gerbil( id, name, gender, - status: 'Active', + status: 'Breeding', dateOfBirth, dateOfDeath: null, causeOfDeath: null, diff --git a/gerbil-manager-web/e2e/status-model.spec.ts b/gerbil-manager-web/e2e/status-model.spec.ts new file mode 100644 index 0000000..9924b9a --- /dev/null +++ b/gerbil-manager-web/e2e/status-model.spec.ts @@ -0,0 +1,58 @@ +/** STATUS-MODEL: Zucht/Liebhaber wählbar; Verstorben/Abgegeben abgeleitet (read-only). */ +import { de, expect, skipUnlessMock, test, uniqueName } from './fixtures' + +const t = de.pages.gerbils + +test('Tier-Formular zeigt nur wählbare Statuswerte im Dropdown', async ({ page }) => { + skipUnlessMock() + await page.goto('/rennmaeuse/neu') + const statusField = page.locator('label.field', { + has: page.locator(`span:text-is("${t.fields.status}")`), + }) + // Zucht (Breeding) ist vorausgewählt + await expect(statusField.locator('select')).toHaveValue('Breeding') + // Optionen: nur Zucht, Liebhaber, Abzugeben (NICHT Verstorben/Abgegeben) + const opts = statusField.locator('select option') + await expect(opts.filter({ hasText: t.statusLabels.Breeding })).toHaveCount(1) + await expect(opts.filter({ hasText: t.statusLabels.Pet })).toHaveCount(1) + await expect(opts.filter({ hasText: t.statusLabels.ForSale })).toHaveCount(1) + await expect(opts.filter({ hasText: t.statusLabels.Deceased })).toHaveCount(0) + await expect(opts.filter({ hasText: t.statusLabels.GivenAway })).toHaveCount(0) +}) + +test('Tier-Formular zeigt abgeleiteten Status Verstorben als Text + Hinweis', async ({ page }) => { + skipUnlessMock() + // Willi ist Deceased + await page.goto('/rennmaeuse/willi/bearbeiten') + // Status-Select ist NICHT vorhanden (abgeleitet) + const statusField = page.locator('.field', { + has: page.locator(`span:text-is("${t.fields.status}")`), + }) + await expect(statusField).toBeVisible() + await expect(statusField.locator('select')).toHaveCount(0) + await expect(statusField).toContainText(t.statusLabels.Deceased) + await expect(statusField).toContainText(t.form.statusDerivedHint.Deceased) +}) + +test('Tiere-Liste zeigt Zucht als Standard-Status-Filter', async ({ page }) => { + skipUnlessMock() + await page.goto('/rennmaeuse') + const statusSelect = page + .locator('label.field', { has: page.locator(`span:text-is("${t.filters.status}")`) }) + .locator('select') + await expect(statusSelect).toHaveValue('Breeding') +}) + +test('Neues Tier anlegen mit Status Liebhaber', async ({ page }) => { + skipUnlessMock() + const name = uniqueName('Tier') + await page.goto('/rennmaeuse/neu') + await page.getByLabel(`${t.fields.name} *`).fill(name) + await page.getByLabel(`${t.fields.gender} *`).selectOption({ label: t.genderLabels.male }) + const statusField = page.locator('label.field', { + has: page.locator(`span:text-is("${t.fields.status}")`), + }) + await statusField.locator('select').selectOption({ label: t.statusLabels.Pet }) + await page.getByRole('button', { name: t.form.save, exact: true }).click() + await expect(page.getByRole('heading', { name })).toBeVisible() +}) diff --git a/gerbil-manager-web/src/api/types.ts b/gerbil-manager-web/src/api/types.ts index e9b75f9..879cef1 100644 --- a/gerbil-manager-web/src/api/types.ts +++ b/gerbil-manager-web/src/api/types.ts @@ -10,9 +10,11 @@ export type Gender = 'unknown' | 'male' | 'female' export const GENDERS: Gender[] = ['unknown', 'male', 'female'] -/** Gerbil.Status — C# enum { Active, Deceased, GivenAway, ForSale }. */ -export type GerbilStatus = 'Active' | 'Deceased' | 'GivenAway' | 'ForSale' -export const GERBIL_STATUSES: GerbilStatus[] = ['Active', 'ForSale', 'Deceased', 'GivenAway'] +/** Gerbil.Status — C# enum { Breeding, Pet, Deceased, GivenAway, ForSale }. STATUS-MODEL: Active→Breeding (gated on Pam feature/status-model). */ +export type GerbilStatus = 'Breeding' | 'Pet' | 'Deceased' | 'GivenAway' | 'ForSale' +export const GERBIL_STATUSES: GerbilStatus[] = ['Breeding', 'Pet', 'ForSale', 'Deceased', 'GivenAway'] +/** Statuses the user can pick freely in the form; Deceased+GivenAway are derived by the backend. */ +export const SELECTABLE_STATUSES: GerbilStatus[] = ['Breeding', 'Pet', 'ForSale'] /** ISO date string "YYYY-MM-DD" (maps to C# DateOnly). */ export type DateOnlyString = string diff --git a/gerbil-manager-web/src/pages/GerbilFormPage.tsx b/gerbil-manager-web/src/pages/GerbilFormPage.tsx index 7f7ecdd..fb1d9e4 100644 --- a/gerbil-manager-web/src/pages/GerbilFormPage.tsx +++ b/gerbil-manager-web/src/pages/GerbilFormPage.tsx @@ -3,7 +3,7 @@ import { Link, useNavigate, useParams, useSearchParams } from 'react-router-dom' import { de } from '../strings/de' import { createGerbil, getGerbil, updateGerbil } from '../api/gerbils' import { listColorVarieties, listContacts, listEnclosures, listLitters } from '../api/lookups' -import { GENDERS, GERBIL_STATUSES, type CreateGerbil, type Gender, type GerbilStatus } from '../api/types' +import { GENDERS, SELECTABLE_STATUSES, type CreateGerbil, type Gender, type GerbilStatus } from '../api/types' import { useApi, useMutation } from '../hooks/useApi' import { genderLabel, statusLabel } from '../format/labels' import { fromDisplayString, genotypeToFarbschlag, UNKNOWN_FARBSCHLAG } from '../genetics' @@ -34,7 +34,7 @@ interface FormState { const EMPTY: FormState = { name: '', gender: '', - status: 'Active', + status: 'Breeding', dateOfBirth: '', dateOfDeath: '', causeOfDeath: '', @@ -245,16 +245,29 @@ export default function GerbilFormPage() { {errors.gender && {errors.gender}} - + {/* STATUS-MODEL: Deceased/GivenAway are derived by the backend — show as read-only with hint */} + {form.status === 'Deceased' || form.status === 'GivenAway' ? ( +
+ {t.fields.status} + + {statusLabel(form.status)} + + ({t.form.statusDerivedHint[form.status]}) + + +
+ ) : ( + + )}