feat(gehege): Reinigungszyklus (Maße, Kapazität, letzte/nächste Reinigung)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 22:43:19 +02:00
parent 5e14124322
commit 4e7cd21b70
15 changed files with 2071 additions and 14 deletions

View File

@@ -7,17 +7,27 @@ import { useMemo, useState } from 'react'
import { Link, useNavigate, useParams } from 'react-router-dom'
import { de } from '../strings/de'
import { ApiError } from '../api/client'
import { deleteEnclosure, getEnclosure } from '../api/enclosures'
import { deleteEnclosure, getEnclosure, markEnclosureCleaned } from '../api/enclosures'
import { listGerbils } from '../api/gerbils'
import { listColorVarieties } from '../api/lookups'
import { condition } from '../api/gridify'
import { useApi, useMutation } from '../hooks/useApi'
import { formatDate } from '../format/labels'
import { useToast } from '../components/toast'
import EnclosurePhotosSection from '../components/EnclosurePhotosSection'
/** true, wenn die nächste fällige Reinigung am/vor heute liegt. */
function isCleaningDue(nextCleaningDate: string | null): boolean {
if (!nextCleaningDate) return false
const today = new Date().toISOString().slice(0, 10)
return nextCleaningDate <= today
}
export default function BeckenDetailPage() {
const t = de.pages.becken
const { id = '' } = useParams()
const navigate = useNavigate()
const toast = useToast()
const [deleteError, setDeleteError] = useState<string | null>(null)
const enclosure = useApi(() => getEnclosure(id), [id])
@@ -39,6 +49,17 @@ export default function BeckenDetailPage() {
)
const removal = useMutation(() => deleteEnclosure(id))
const cleaning = useMutation(() => markEnclosureCleaned(id))
async function onMarkCleaned() {
const result = await cleaning.run()
if (result.ok) {
toast.success(t.cleaning.marked)
enclosure.reload()
} else {
toast.error(result.error)
}
}
async function onDelete() {
if (!window.confirm(t.delete.confirmMessage)) return
@@ -101,15 +122,62 @@ export default function BeckenDetailPage() {
{deleteError && <div className="alert alert--error">{deleteError}</div>}
{e.notes && (
{(e.notes || e.size || e.capacity != null) && (
<dl className="def-list">
<div className="def-row">
<dt>{t.fields.notes}</dt>
<dd>{e.notes}</dd>
</div>
{e.notes && (
<div className="def-row">
<dt>{t.fields.notes}</dt>
<dd>{e.notes}</dd>
</div>
)}
{e.size && (
<div className="def-row">
<dt>{t.fields.size}</dt>
<dd>{e.size}</dd>
</div>
)}
{e.capacity != null && (
<div className="def-row">
<dt>{t.fields.capacity}</dt>
<dd>{t.cleaning.capacityUnit(e.capacity)}</dd>
</div>
)}
</dl>
)}
<h3>{t.cleaning.title}</h3>
{isCleaningDue(e.nextCleaningDate) && (
<div className="alert alert--warning" role="status">
{e.nextCleaningDate
? t.cleaning.dueSince(formatDate(e.nextCleaningDate))
: t.cleaning.due}
</div>
)}
<dl className="def-list">
<div className="def-row">
<dt>{t.fields.lastCleanedDate}</dt>
<dd>{e.lastCleanedDate ? formatDate(e.lastCleanedDate) : t.cleaning.neverCleaned}</dd>
</div>
<div className="def-row">
<dt>{t.fields.cleaningCycleDays}</dt>
<dd>{e.cleaningCycleDays != null ? t.cleaning.cycleUnit(e.cleaningCycleDays) : t.cleaning.noCycle}</dd>
</div>
{e.nextCleaningDate && (
<div className="def-row">
<dt>{t.fields.nextCleaningDate}</dt>
<dd>{formatDate(e.nextCleaningDate)}</dd>
</div>
)}
</dl>
<button
type="button"
className="btn"
onClick={onMarkCleaned}
disabled={cleaning.pending}
>
{cleaning.pending ? t.cleaning.marking : t.cleaning.markCleaned}
</button>
<h3>{t.photosTitle}</h3>
<EnclosurePhotosSection enclosureId={e.id} />