Compare commits
5 Commits
feature/wu
...
feature/ui
| Author | SHA1 | Date | |
|---|---|---|---|
| 55c298ad1f | |||
| 0b9b03eb2d | |||
| d4c9be23c8 | |||
| 162a0fafb1 | |||
| 2bbdfe2b71 |
@@ -7,7 +7,7 @@ const tk = de.pages.kontakte
|
|||||||
test.describe('Becken', () => {
|
test.describe('Becken', () => {
|
||||||
test('Belegung: Detail zeigt Bewohner mit Link zur Tierakte', async ({ page }) => {
|
test('Belegung: Detail zeigt Bewohner mit Link zur Tierakte', async ({ page }) => {
|
||||||
skipUnlessMock()
|
skipUnlessMock()
|
||||||
await page.goto('/becken')
|
await page.goto('/gehege')
|
||||||
await page.getByRole('link', { name: /Großbecken/ }).click()
|
await page.getByRole('link', { name: /Großbecken/ }).click()
|
||||||
await expect(page.getByRole('heading', { name: 'Großbecken' })).toBeVisible()
|
await expect(page.getByRole('heading', { name: 'Großbecken' })).toBeVisible()
|
||||||
await expect(page.getByText(`2 ${tb.occupancy.countMany}`)).toBeVisible()
|
await expect(page.getByText(`2 ${tb.occupancy.countMany}`)).toBeVisible()
|
||||||
@@ -18,7 +18,7 @@ test.describe('Becken', () => {
|
|||||||
|
|
||||||
test('Löschen eines belegten Beckens meldet den 409-Konflikt auf Deutsch', async ({ page }) => {
|
test('Löschen eines belegten Beckens meldet den 409-Konflikt auf Deutsch', async ({ page }) => {
|
||||||
skipUnlessMock()
|
skipUnlessMock()
|
||||||
await page.goto('/becken')
|
await page.goto('/gehege')
|
||||||
await page.getByRole('link', { name: /Großbecken/ }).click()
|
await page.getByRole('link', { name: /Großbecken/ }).click()
|
||||||
acceptNextDialog(page)
|
acceptNextDialog(page)
|
||||||
await page.getByRole('button', { name: tb.delete.action }).click()
|
await page.getByRole('button', { name: tb.delete.action }).click()
|
||||||
|
|||||||
@@ -149,6 +149,8 @@ export function seedDb(): MockDb {
|
|||||||
enclosureId: 'enc-leer',
|
enclosureId: 'enc-leer',
|
||||||
notes: 'neugieriger Entdecker',
|
notes: 'neugieriger Entdecker',
|
||||||
},
|
},
|
||||||
|
// UI-POLISH-1: Import-Stub ohne Namen — testet den '(ohne Namen)'-Platzhalter in Liste + Detail.
|
||||||
|
gerbil('nameless-stub', '', 'male', '2023-01-01', null, null),
|
||||||
]
|
]
|
||||||
|
|
||||||
const litters: Litter[] = [
|
const litters: Litter[] = [
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { de, expect, gotoSection, test } from './fixtures'
|
|||||||
test('alle Bereiche sind über die Navigation erreichbar', async ({ page }) => {
|
test('alle Bereiche sind über die Navigation erreichbar', async ({ page }) => {
|
||||||
await page.goto('/')
|
await page.goto('/')
|
||||||
await expect(page.getByRole('heading', { name: de.app.title })).toBeVisible()
|
await expect(page.getByRole('heading', { name: de.app.title })).toBeVisible()
|
||||||
await expect(page.getByRole('heading', { name: de.pages.home.title })).toBeVisible()
|
await expect(page.getByRole('heading', { name: de.pages.gerbils.title, exact: true })).toBeVisible()
|
||||||
|
|
||||||
await gotoSection(page, de.nav.gerbils)
|
await gotoSection(page, de.nav.gerbils)
|
||||||
await expect(page.getByRole('heading', { name: de.pages.gerbils.title, exact: true })).toBeVisible()
|
await expect(page.getByRole('heading', { name: de.pages.gerbils.title, exact: true })).toBeVisible()
|
||||||
@@ -30,5 +30,5 @@ test('unbekannte Route zeigt die deutsche 404-Seite', async ({ page }) => {
|
|||||||
await page.goto('/diese-seite-gibt-es-nicht')
|
await page.goto('/diese-seite-gibt-es-nicht')
|
||||||
await expect(page.getByText(de.pages.notFound.message)).toBeVisible()
|
await expect(page.getByText(de.pages.notFound.message)).toBeVisible()
|
||||||
await page.getByRole('link', { name: de.pages.notFound.backHome }).click()
|
await page.getByRole('link', { name: de.pages.notFound.backHome }).click()
|
||||||
await expect(page.getByRole('heading', { name: de.pages.home.title })).toBeVisible()
|
await expect(page.getByRole('heading', { name: de.pages.gerbils.title, exact: true })).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -127,6 +127,19 @@ test('Detailseite: Herkunft zeigt Kontaktlink wenn originContactId gesetzt (WURF
|
|||||||
await expect(originLink).toHaveAttribute('href', '/kontakte/con-meier')
|
await expect(originLink).toHaveAttribute('href', '/kontakte/con-meier')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Namenlose Tiere zeigen Platzhalter in der Liste (UI-POLISH-1)', async ({ page }) => {
|
||||||
|
skipUnlessMock()
|
||||||
|
await page.goto('/rennmaeuse')
|
||||||
|
// 'nameless-stub' hat name='' → Platzhalter '(ohne Namen)' soll als Link sichtbar sein
|
||||||
|
await expect(page.getByRole('link', { name: de.pages.gerbils.nameless })).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Namenlose Tiere zeigen Platzhalter im Detail-Header (UI-POLISH-1)', async ({ page }) => {
|
||||||
|
skipUnlessMock()
|
||||||
|
await page.goto('/rennmaeuse/nameless-stub')
|
||||||
|
await expect(page.getByRole('heading', { name: de.pages.gerbils.nameless })).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
test('Tier bearbeiten — Notizen ändern', async ({ page }) => {
|
test('Tier bearbeiten — Notizen ändern', async ({ page }) => {
|
||||||
skipUnlessMock()
|
skipUnlessMock()
|
||||||
await page.goto('/rennmaeuse/kruemel/bearbeiten')
|
await page.goto('/rennmaeuse/kruemel/bearbeiten')
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { BrowserRouter, Route, Routes } from 'react-router-dom'
|
import { BrowserRouter, Navigate, Route, Routes, useParams } from 'react-router-dom'
|
||||||
import AppShell from './components/AppShell'
|
import AppShell from './components/AppShell'
|
||||||
import BeckenPage from './pages/BeckenPage'
|
import BeckenPage from './pages/BeckenPage'
|
||||||
import BeckenDetailPage from './pages/BeckenDetailPage'
|
import BeckenDetailPage from './pages/BeckenDetailPage'
|
||||||
@@ -8,7 +8,6 @@ import GerbilDetailPage from './pages/GerbilDetailPage'
|
|||||||
import GerbilFormPage from './pages/GerbilFormPage'
|
import GerbilFormPage from './pages/GerbilFormPage'
|
||||||
import GenetikPage from './pages/GenetikPage'
|
import GenetikPage from './pages/GenetikPage'
|
||||||
import AbgabePage from './pages/AbgabePage'
|
import AbgabePage from './pages/AbgabePage'
|
||||||
import HomePage from './pages/HomePage'
|
|
||||||
import KontaktePage from './pages/KontaktePage'
|
import KontaktePage from './pages/KontaktePage'
|
||||||
import KontaktDetailPage from './pages/KontaktDetailPage'
|
import KontaktDetailPage from './pages/KontaktDetailPage'
|
||||||
import KontaktFormPage from './pages/KontaktFormPage'
|
import KontaktFormPage from './pages/KontaktFormPage'
|
||||||
@@ -28,12 +27,17 @@ import WebseiteVorschauPage from './pages/WebseiteVorschauPage'
|
|||||||
import AnfragenPage from './pages/AnfragenPage'
|
import AnfragenPage from './pages/AnfragenPage'
|
||||||
import AnfrageDetailPage from './pages/AnfrageDetailPage'
|
import AnfrageDetailPage from './pages/AnfrageDetailPage'
|
||||||
|
|
||||||
|
function BeckenRedirect() {
|
||||||
|
const { '*': splat } = useParams()
|
||||||
|
return <Navigate to={`/gehege${splat ? `/${splat}` : ''}`} replace />
|
||||||
|
}
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route element={<AppShell />}>
|
<Route element={<AppShell />}>
|
||||||
<Route index element={<HomePage />} />
|
<Route index element={<Navigate to="/rennmaeuse" replace />} />
|
||||||
<Route path="rennmaeuse">
|
<Route path="rennmaeuse">
|
||||||
<Route index element={<GerbilsPage />} />
|
<Route index element={<GerbilsPage />} />
|
||||||
<Route path="neu" element={<GerbilFormPage />} />
|
<Route path="neu" element={<GerbilFormPage />} />
|
||||||
@@ -48,7 +52,9 @@ export default function App() {
|
|||||||
<Route path=":id/bearbeiten" element={<WurfFormPage />} />
|
<Route path=":id/bearbeiten" element={<WurfFormPage />} />
|
||||||
</Route>
|
</Route>
|
||||||
{/* FEAT-2: Kontakte ersetzt das Züchter-Platzhalter-Routing (Breeder -> Contact) */}
|
{/* FEAT-2: Kontakte ersetzt das Züchter-Platzhalter-Routing (Breeder -> Contact) */}
|
||||||
<Route path="becken">
|
{/* GEHEGE-RENAME: /becken → /gehege; redirect preserves bookmarks */}
|
||||||
|
<Route path="becken/*" element={<BeckenRedirect />} />
|
||||||
|
<Route path="gehege">
|
||||||
<Route index element={<BeckenPage />} />
|
<Route index element={<BeckenPage />} />
|
||||||
<Route path="neu" element={<BeckenFormPage />} />
|
<Route path="neu" element={<BeckenFormPage />} />
|
||||||
<Route path=":id" element={<BeckenDetailPage />} />
|
<Route path=":id" element={<BeckenDetailPage />} />
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { de } from '../strings/de'
|
import { de } from '../strings/de'
|
||||||
|
|
||||||
|
const nameless = de.pages.gerbils.nameless
|
||||||
import { listGerbils } from '../api/gerbils'
|
import { listGerbils } from '../api/gerbils'
|
||||||
import { andFilter, condition } from '../api/gridify'
|
import { andFilter, condition } from '../api/gridify'
|
||||||
import type { Gender, Gerbil, Paged } from '../api/types'
|
import type { Gender, Gerbil, Paged } from '../api/types'
|
||||||
@@ -62,7 +64,7 @@ export default function AnimalPicker({ gender, onPick }: AnimalPickerProps) {
|
|||||||
setOpen(false)
|
setOpen(false)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{g.name}
|
{g.name || nameless}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -13,13 +13,12 @@ interface NavItem {
|
|||||||
// Primary items are always visible in the phone bottom bar; secondary items
|
// Primary items are always visible in the phone bottom bar; secondary items
|
||||||
// move into the "Mehr" sheet on phones and appear inline in the desktop sidebar.
|
// move into the "Mehr" sheet on phones and appear inline in the desktop sidebar.
|
||||||
const PRIMARY: NavItem[] = [
|
const PRIMARY: NavItem[] = [
|
||||||
{ to: '/', label: de.nav.home, icon: '🏠', end: true },
|
|
||||||
{ to: '/rennmaeuse', label: de.nav.gerbils, icon: '🐭' },
|
{ to: '/rennmaeuse', label: de.nav.gerbils, icon: '🐭' },
|
||||||
{ to: '/wuerfe', label: de.nav.litters, icon: '🍼' },
|
{ to: '/wuerfe', label: de.nav.litters, icon: '🍼' },
|
||||||
{ to: '/genetik', label: de.nav.genetics, icon: '🧬' },
|
{ to: '/genetik', label: de.nav.genetics, icon: '🧬' },
|
||||||
]
|
]
|
||||||
const SECONDARY: NavItem[] = [
|
const SECONDARY: NavItem[] = [
|
||||||
{ to: '/becken', label: de.nav.enclosures, icon: '🛁' },
|
{ to: '/gehege', label: de.nav.enclosures, icon: '🏜️' },
|
||||||
{ to: '/kontakte', label: de.nav.contacts, icon: '📇' },
|
{ to: '/kontakte', label: de.nav.contacts, icon: '📇' },
|
||||||
{ to: '/abgabe', label: de.nav.forSale, icon: '🏡' },
|
{ to: '/abgabe', label: de.nav.forSale, icon: '🏡' },
|
||||||
// INBOX-1: Anfragen-Posteingang
|
// INBOX-1: Anfragen-Posteingang
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export default function BeckenDetailPage() {
|
|||||||
setDeleteError(null)
|
setDeleteError(null)
|
||||||
const result = await removal.run()
|
const result = await removal.run()
|
||||||
if (result.ok) {
|
if (result.ok) {
|
||||||
navigate('/becken')
|
navigate('/gehege')
|
||||||
} else if (result.cause instanceof ApiError && result.cause.status === 409) {
|
} else if (result.cause instanceof ApiError && result.cause.status === 409) {
|
||||||
// Backend meldet Konflikt, wenn noch Tiere im Becken wohnen.
|
// Backend meldet Konflikt, wenn noch Tiere im Becken wohnen.
|
||||||
setDeleteError(t.delete.conflict)
|
setDeleteError(t.delete.conflict)
|
||||||
@@ -58,7 +58,7 @@ export default function BeckenDetailPage() {
|
|||||||
return (
|
return (
|
||||||
<section className="page">
|
<section className="page">
|
||||||
<p className="muted">{enclosure.error ?? t.detail.notFound}</p>
|
<p className="muted">{enclosure.error ?? t.detail.notFound}</p>
|
||||||
<Link to="/becken" className="btn">
|
<Link to="/gehege" className="btn">
|
||||||
{t.detail.back}
|
{t.detail.back}
|
||||||
</Link>
|
</Link>
|
||||||
</section>
|
</section>
|
||||||
@@ -81,7 +81,7 @@ export default function BeckenDetailPage() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="head-actions">
|
<div className="head-actions">
|
||||||
<Link to={`/becken/${e.id}/bearbeiten`} className="btn btn--primary">
|
<Link to={`/gehege/${e.id}/bearbeiten`} className="btn btn--primary">
|
||||||
{t.detail.edit}
|
{t.detail.edit}
|
||||||
</Link>
|
</Link>
|
||||||
<button
|
<button
|
||||||
@@ -92,7 +92,7 @@ export default function BeckenDetailPage() {
|
|||||||
>
|
>
|
||||||
{t.delete.action}
|
{t.delete.action}
|
||||||
</button>
|
</button>
|
||||||
<Link to="/becken" className="btn">
|
<Link to="/gehege" className="btn">
|
||||||
{t.detail.back}
|
{t.detail.back}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export default function BeckenFormPage() {
|
|||||||
}
|
}
|
||||||
setErrors({})
|
setErrors({})
|
||||||
const result = await mutation.run({ name: form.name.trim(), notes: nn(form.notes) })
|
const result = await mutation.run({ name: form.name.trim(), notes: nn(form.notes) })
|
||||||
if (result.ok) navigate(`/becken/${result.value.id}`)
|
if (result.ok) navigate(`/gehege/${result.value.id}`)
|
||||||
// On failure mutation.error drives the inline alert; run() never throws.
|
// On failure mutation.error drives the inline alert; run() never throws.
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ export default function BeckenFormPage() {
|
|||||||
<button type="submit" className="btn btn--primary" disabled={mutation.pending}>
|
<button type="submit" className="btn btn--primary" disabled={mutation.pending}>
|
||||||
{mutation.pending ? t.form.saving : t.form.save}
|
{mutation.pending ? t.form.saving : t.form.save}
|
||||||
</button>
|
</button>
|
||||||
<Link to={isEdit && id ? `/becken/${id}` : '/becken'} className="btn">
|
<Link to={isEdit && id ? `/gehege/${id}` : '/gehege'} className="btn">
|
||||||
{t.form.cancel}
|
{t.form.cancel}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export default function BeckenPage() {
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Link to="/becken/neu" className="btn btn--primary">
|
<Link to="/gehege/neu" className="btn btn--primary">
|
||||||
+ {t.newButton}
|
+ {t.newButton}
|
||||||
</Link>
|
</Link>
|
||||||
</header>
|
</header>
|
||||||
@@ -72,7 +72,7 @@ export default function BeckenPage() {
|
|||||||
<ul className="card-list">
|
<ul className="card-list">
|
||||||
{items.map((e) => (
|
{items.map((e) => (
|
||||||
<li key={e.id}>
|
<li key={e.id}>
|
||||||
<Link to={`/becken/${e.id}`} className="gerbil-card">
|
<Link to={`/gehege/${e.id}`} className="gerbil-card">
|
||||||
<span className="gerbil-card__name">{e.name}</span>
|
<span className="gerbil-card__name">{e.name}</span>
|
||||||
<span className="gerbil-card__meta">{e.notes ?? ''}</span>
|
<span className="gerbil-card__meta">{e.notes ?? ''}</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export default function GerbilDetailPage() {
|
|||||||
<header className="page-head">
|
<header className="page-head">
|
||||||
<div>
|
<div>
|
||||||
<GerbilProfilePhoto gerbilId={g.id} />
|
<GerbilProfilePhoto gerbilId={g.id} />
|
||||||
<h2>{g.name}</h2>
|
<h2>{g.name || de.pages.gerbils.nameless}</h2>
|
||||||
<span className={`badge badge--${g.status.toLowerCase()}`}>{statusLabel(g.status)}</span>
|
<span className={`badge badge--${g.status.toLowerCase()}`}>{statusLabel(g.status)}</span>
|
||||||
{g.isResident === false && (
|
{g.isResident === false && (
|
||||||
<span className="badge badge--external" title={de.pages.gerbils.externalTitle}>
|
<span className="badge badge--external" title={de.pages.gerbils.externalTitle}>
|
||||||
@@ -153,7 +153,7 @@ export default function GerbilDetailPage() {
|
|||||||
{g.status === 'Deceased' && (
|
{g.status === 'Deceased' && (
|
||||||
<>
|
<>
|
||||||
<Row label={t.fields.dateOfDeath} value={formatDate(g.dateOfDeath)} />
|
<Row label={t.fields.dateOfDeath} value={formatDate(g.dateOfDeath)} />
|
||||||
<Row label={t.fields.causeOfDeath} value={g.causeOfDeath} />
|
<Row label={t.fields.causeOfDeath} value={g.causeOfDeath || null} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{g.status === 'GivenAway' && (
|
{g.status === 'GivenAway' && (
|
||||||
@@ -190,7 +190,7 @@ export default function GerbilDetailPage() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Row label={t.fields.receiver} value={lookup(contactName, g.receiverContactId)} />
|
<Row label={t.fields.receiver} value={lookup(contactName, g.receiverContactId)} />
|
||||||
<Row label={t.fields.notes} value={g.notes} />
|
<Row label={t.fields.notes} value={g.notes || null} />
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
<h3>{t.detail.genetics}</h3>
|
<h3>{t.detail.genetics}</h3>
|
||||||
|
|||||||
@@ -256,11 +256,11 @@ export default function GerbilsPage() {
|
|||||||
className="gerbil-row__check"
|
className="gerbil-row__check"
|
||||||
checked={selected.has(g.id)}
|
checked={selected.has(g.id)}
|
||||||
onChange={() => toggleSelect(g.id)}
|
onChange={() => toggleSelect(g.id)}
|
||||||
aria-label={g.name}
|
aria-label={g.name || t.nameless}
|
||||||
/>
|
/>
|
||||||
<Link to={`/rennmaeuse/${g.id}`} className="gerbil-card">
|
<Link to={`/rennmaeuse/${g.id}`} className="gerbil-card">
|
||||||
<span className="gerbil-card__name">
|
<span className="gerbil-card__name">
|
||||||
{g.name}
|
{g.name || t.nameless}
|
||||||
{g.isResident === false && (
|
{g.isResident === false && (
|
||||||
<span className="badge badge--external" title={t.externalTitle}>
|
<span className="badge badge--external" title={t.externalTitle}>
|
||||||
{t.externalBadge}
|
{t.externalBadge}
|
||||||
|
|||||||
@@ -38,12 +38,12 @@ const sections: Section[] = [
|
|||||||
<p>Die Navigation erreichst du unten (Smartphone) oder links (Laptop). Mit <strong>„{nav.more}"</strong> öffnest du alle weiteren Bereiche.</p>
|
<p>Die Navigation erreichst du unten (Smartphone) oder links (Laptop). Mit <strong>„{nav.more}"</strong> öffnest du alle weiteren Bereiche.</p>
|
||||||
<p>Empfohlene Reihenfolge beim ersten Mal:</p>
|
<p>Empfohlene Reihenfolge beim ersten Mal:</p>
|
||||||
<ol>
|
<ol>
|
||||||
<li>Becken anlegen (wo leben deine Tiere?)</li>
|
<li>Gehege anlegen (wo leben deine Tiere?)</li>
|
||||||
<li>Rennmäuse anlegen mit Foto</li>
|
<li>Rennmäuse anlegen mit Foto</li>
|
||||||
<li>Würfe eintragen</li>
|
<li>Würfe eintragen</li>
|
||||||
<li>Genetik erkunden</li>
|
<li>Genetik erkunden</li>
|
||||||
</ol>
|
</ol>
|
||||||
<p className="hilfe-hint">Tipp: Lege zuerst die Becken an — dann kannst du beim Erfassen der Tiere direkt das richtige Becken auswählen.</p>
|
<p className="hilfe-hint">Tipp: Lege zuerst die Gehege an — dann kannst du beim Erfassen der Tiere direkt das richtige Gehege auswählen.</p>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -53,7 +53,7 @@ const sections: Section[] = [
|
|||||||
content: (
|
content: (
|
||||||
<>
|
<>
|
||||||
<p>Unter <strong>„{nav.gerbils}"</strong> siehst du alle deine Rennmäuse. Mit <strong>„{g.newButton}"</strong> legst du ein neues Tier an.</p>
|
<p>Unter <strong>„{nav.gerbils}"</strong> siehst du alle deine Rennmäuse. Mit <strong>„{g.newButton}"</strong> legst du ein neues Tier an.</p>
|
||||||
<p>Pflichtfelder sind <strong>„{g.fields.name}"</strong> und <strong>„{g.fields.gender}"</strong>. Optional kannst du Geburtsdatum, Farbschlag, Genotyp, Becken und Herkunft eintragen.</p>
|
<p>Pflichtfelder sind <strong>„{g.fields.name}"</strong> und <strong>„{g.fields.gender}"</strong>. Optional kannst du Geburtsdatum, Farbschlag, Genotyp, Gehege und Herkunft eintragen.</p>
|
||||||
<p>Auf der Detailseite findest du drei Tabs:</p>
|
<p>Auf der Detailseite findest du drei Tabs:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>„{g.detail.tabs.photos}"</strong> — Fotos hochladen; das erste Foto wird als Profilfoto angezeigt.</li>
|
<li><strong>„{g.detail.tabs.photos}"</strong> — Fotos hochladen; das erste Foto wird als Profilfoto angezeigt.</li>
|
||||||
@@ -69,9 +69,9 @@ const sections: Section[] = [
|
|||||||
title: t.sections.becken,
|
title: t.sections.becken,
|
||||||
content: (
|
content: (
|
||||||
<>
|
<>
|
||||||
<p>Unter <strong>„{nav.enclosures}"</strong> verwaltest du deine Gehege. Mit <strong>„{b.newButton}"</strong> legst du ein neues Becken an (nur Name ist Pflicht).</p>
|
<p>Unter <strong>„{nav.enclosures}"</strong> verwaltest du deine Gehege. Mit <strong>„{b.newButton}"</strong> legst du ein neues Gehege an (nur Name ist Pflicht).</p>
|
||||||
<p>Die Detailseite zeigt dir die aktuellen <strong>„{b.occupancy.title}"</strong> — mit Links direkt zur Tierakte. So siehst du auf einen Blick, wer wo lebt.</p>
|
<p>Die Detailseite zeigt dir die aktuellen <strong>„{b.occupancy.title}"</strong> — mit Links direkt zur Tierakte. So siehst du auf einen Blick, wer wo lebt.</p>
|
||||||
<p>Ein Becken lässt sich nur löschen, wenn es leer ist — das schützt vor versehentlichem Datenverlust.</p>
|
<p>Ein Gehege lässt sich nur löschen, wenn es leer ist — das schützt vor versehentlichem Datenverlust.</p>
|
||||||
<p>Unter <strong>„{nav.contacts}"</strong> kannst du Züchter, Käufer und andere Kontakte anlegen. Diese lassen sich dann als Herkunft oder Abnehmer bei Tieren eintragen.</p>
|
<p>Unter <strong>„{nav.contacts}"</strong> kannst du Züchter, Käufer und andere Kontakte anlegen. Diese lassen sich dann als Herkunft oder Abnehmer bei Tieren eintragen.</p>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
import { de } from '../strings/de'
|
|
||||||
|
|
||||||
export default function HomePage() {
|
|
||||||
return (
|
|
||||||
<section>
|
|
||||||
<h2>{de.pages.home.title}</h2>
|
|
||||||
<p>{de.pages.home.intro}</p>
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -9,13 +9,12 @@ export const de = {
|
|||||||
tagline: 'Verwaltung für Rennmäuse, Würfe und Züchter',
|
tagline: 'Verwaltung für Rennmäuse, Würfe und Züchter',
|
||||||
},
|
},
|
||||||
nav: {
|
nav: {
|
||||||
home: 'Start',
|
|
||||||
gerbils: 'Rennmäuse',
|
gerbils: 'Rennmäuse',
|
||||||
litters: 'Würfe',
|
litters: 'Würfe',
|
||||||
breeders: 'Züchter',
|
breeders: 'Züchter',
|
||||||
genetics: 'Genetik',
|
genetics: 'Genetik',
|
||||||
// FEAT-2 (Oscar): Becken + Kontakte
|
// FEAT-2 (Oscar): Becken + Kontakte (GEHEGE-RENAME: Becken → Gehege)
|
||||||
enclosures: 'Becken',
|
enclosures: 'Gehege',
|
||||||
contacts: 'Kontakte',
|
contacts: 'Kontakte',
|
||||||
// FEAT-7 (Kelly): Statistik
|
// FEAT-7 (Kelly): Statistik
|
||||||
statistics: 'Statistik',
|
statistics: 'Statistik',
|
||||||
@@ -37,13 +36,9 @@ export const de = {
|
|||||||
website: 'Webseite',
|
website: 'Webseite',
|
||||||
},
|
},
|
||||||
pages: {
|
pages: {
|
||||||
home: {
|
|
||||||
title: 'Willkommen',
|
|
||||||
intro:
|
|
||||||
'Hier verwaltest du deine Rennmäuse, Würfe und Züchter. Wähle einen Bereich aus der Navigation.',
|
|
||||||
},
|
|
||||||
gerbils: {
|
gerbils: {
|
||||||
title: 'Rennmäuse',
|
title: 'Rennmäuse',
|
||||||
|
nameless: '(ohne Namen)',
|
||||||
// Liste
|
// Liste
|
||||||
newButton: 'Neue Rennmaus',
|
newButton: 'Neue Rennmaus',
|
||||||
empty: 'Keine Rennmäuse gefunden.',
|
empty: 'Keine Rennmäuse gefunden.',
|
||||||
@@ -82,7 +77,7 @@ export const de = {
|
|||||||
causeOfDeath: 'Todesursache',
|
causeOfDeath: 'Todesursache',
|
||||||
goHomeDate: 'Abgabedatum',
|
goHomeDate: 'Abgabedatum',
|
||||||
litter: 'Wurf',
|
litter: 'Wurf',
|
||||||
enclosure: 'Becken',
|
enclosure: 'Gehege',
|
||||||
colorVariety: 'Farbschlag',
|
colorVariety: 'Farbschlag',
|
||||||
origin: 'Herkunft',
|
origin: 'Herkunft',
|
||||||
receiver: 'Abnehmer',
|
receiver: 'Abnehmer',
|
||||||
@@ -253,8 +248,8 @@ export const de = {
|
|||||||
markMultiAction: 'Ausgewählte zur Abgabe stellen',
|
markMultiAction: 'Ausgewählte zur Abgabe stellen',
|
||||||
grouping: {
|
grouping: {
|
||||||
title: 'Gruppen',
|
title: 'Gruppen',
|
||||||
byEnclosure: 'Nach Becken vorgeschlagen',
|
byEnclosure: 'Nach Gehege vorgeschlagen',
|
||||||
ungrouped: 'Ohne Becken',
|
ungrouped: 'Ohne Gehege',
|
||||||
moveTo: 'Verschieben nach',
|
moveTo: 'Verschieben nach',
|
||||||
newGroup: 'Neue Gruppe',
|
newGroup: 'Neue Gruppe',
|
||||||
groupLabel: 'Gruppe',
|
groupLabel: 'Gruppe',
|
||||||
@@ -298,12 +293,12 @@ export const de = {
|
|||||||
notConfigured: 'KI-Funktion: API-Schlüssel noch nicht konfiguriert.',
|
notConfigured: 'KI-Funktion: API-Schlüssel noch nicht konfiguriert.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// ── FEAT-2 (Oscar): Becken (Enclosures) ──
|
// ── FEAT-2 (Oscar): Gehege (Enclosures) — GEHEGE-RENAME: Becken → Gehege ──
|
||||||
becken: {
|
becken: {
|
||||||
title: 'Becken',
|
title: 'Gehege',
|
||||||
newButton: 'Neues Becken',
|
newButton: 'Neues Gehege',
|
||||||
empty: 'Keine Becken gefunden.',
|
empty: 'Keine Gehege gefunden.',
|
||||||
countLabel: 'Becken',
|
countLabel: 'Gehege',
|
||||||
searchPlaceholder: 'Name suchen …',
|
searchPlaceholder: 'Name suchen …',
|
||||||
fields: {
|
fields: {
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
@@ -311,18 +306,18 @@ export const de = {
|
|||||||
},
|
},
|
||||||
occupancy: {
|
occupancy: {
|
||||||
title: 'Bewohner',
|
title: 'Bewohner',
|
||||||
empty: 'Dieses Becken ist derzeit leer.',
|
empty: 'Dieses Gehege ist derzeit leer.',
|
||||||
countOne: 'Tier',
|
countOne: 'Tier',
|
||||||
countMany: 'Tiere',
|
countMany: 'Tiere',
|
||||||
},
|
},
|
||||||
detail: {
|
detail: {
|
||||||
edit: 'Bearbeiten',
|
edit: 'Bearbeiten',
|
||||||
back: 'Zurück zur Liste',
|
back: 'Zurück zur Liste',
|
||||||
notFound: 'Dieses Becken wurde nicht gefunden.',
|
notFound: 'Dieses Gehege wurde nicht gefunden.',
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
createTitle: 'Neues Becken anlegen',
|
createTitle: 'Neues Gehege anlegen',
|
||||||
editTitle: 'Becken bearbeiten',
|
editTitle: 'Gehege bearbeiten',
|
||||||
save: 'Speichern',
|
save: 'Speichern',
|
||||||
cancel: 'Abbrechen',
|
cancel: 'Abbrechen',
|
||||||
saving: 'Speichern …',
|
saving: 'Speichern …',
|
||||||
@@ -332,9 +327,9 @@ export const de = {
|
|||||||
},
|
},
|
||||||
delete: {
|
delete: {
|
||||||
action: 'Löschen',
|
action: 'Löschen',
|
||||||
confirmMessage: 'Soll dieses Becken wirklich gelöscht werden?',
|
confirmMessage: 'Soll dieses Gehege wirklich gelöscht werden?',
|
||||||
conflict:
|
conflict:
|
||||||
'Becken ist nicht leer — bitte zuerst alle Tiere in ein anderes Becken umsetzen.',
|
'Gehege ist nicht leer — bitte zuerst alle Tiere in ein anderes Gehege umsetzen.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// ── FEAT-4 (Kelly): Stammbaum (Ahnentafel) ──
|
// ── FEAT-4 (Kelly): Stammbaum (Ahnentafel) ──
|
||||||
@@ -731,7 +726,7 @@ export const de = {
|
|||||||
sections: {
|
sections: {
|
||||||
ersteSchritte: 'Erste Schritte',
|
ersteSchritte: 'Erste Schritte',
|
||||||
tiere: 'Rennmäuse anlegen & Fotos',
|
tiere: 'Rennmäuse anlegen & Fotos',
|
||||||
becken: 'Becken',
|
becken: 'Gehege',
|
||||||
wuerfe: 'Würfe & Jungtiere',
|
wuerfe: 'Würfe & Jungtiere',
|
||||||
genetik: 'Probeverpaarung & Genetik',
|
genetik: 'Probeverpaarung & Genetik',
|
||||||
stammbaum: 'Stammbaum',
|
stammbaum: 'Stammbaum',
|
||||||
|
|||||||
Reference in New Issue
Block a user