FEAT-6: wire tab content into animal detail (Fotos/Gesundheit/Gewicht) + profile photo in header + tab styles
This commit is contained in:
20
gerbil-manager-web/src/components/GerbilProfilePhoto.tsx
Normal file
20
gerbil-manager-web/src/components/GerbilProfilePhoto.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* FEAT-6: Profilfoto im Detail-Kopf — erstes Foto nach sortOrder.
|
||||
* Selbstständig ladend; rendert nichts, solange es keine Fotos gibt
|
||||
* (oder die Foto-Endpunkte noch nicht live sind).
|
||||
*/
|
||||
import { listGerbilPhotos, photoSrc, profilePhoto } from '../api/photos'
|
||||
import { useApi } from '../hooks/useApi'
|
||||
|
||||
export default function GerbilProfilePhoto({ gerbilId }: { gerbilId: string }) {
|
||||
const photos = useApi(() => listGerbilPhotos(gerbilId), [gerbilId])
|
||||
const profile = profilePhoto(photos.data ?? [])
|
||||
if (!profile) return null
|
||||
return (
|
||||
<img
|
||||
className="profile-photo"
|
||||
src={photoSrc(profile)}
|
||||
alt={profile.caption ?? profile.fileName}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -602,3 +602,143 @@ textarea {
|
||||
color: #7a5a14;
|
||||
margin-left: 0.4rem;
|
||||
}
|
||||
|
||||
/* ── FEAT-6 (Oscar): Tier-Detail-Tabs — Gesundheit / Gewicht / Fotos ── */
|
||||
|
||||
/* Eintragskarten (Gesundheit, Gewichtsverlauf) */
|
||||
.record-card {
|
||||
padding: 0.75rem 1rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.6rem;
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
.record-card__head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.record-card__date {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.record-card__body {
|
||||
margin: 0.4rem 0;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.record-card__actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.record-card--row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
/* Gewicht-Schnellerfassung: großes, daumenfreundliches Eingabefeld */
|
||||
.weight-quick-add {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.weight-quick-add__row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.input--xl {
|
||||
font-size: 1.5rem;
|
||||
min-height: 56px;
|
||||
max-width: 9rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Gewichts-Diagramm (eigenes SVG) */
|
||||
.line-chart {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-width: 40rem;
|
||||
margin: 0.5rem 0 1.25rem;
|
||||
}
|
||||
|
||||
.line-chart__axis {
|
||||
stroke: var(--color-text-muted);
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
.line-chart__grid {
|
||||
stroke: var(--color-border);
|
||||
stroke-width: 1;
|
||||
stroke-dasharray: 3 3;
|
||||
}
|
||||
|
||||
.line-chart__label {
|
||||
fill: var(--color-text-muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.line-chart__line {
|
||||
stroke: var(--color-accent);
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
.line-chart__dot {
|
||||
fill: var(--color-accent);
|
||||
}
|
||||
|
||||
/* Foto-Galerie */
|
||||
.photo-grid {
|
||||
list-style: none;
|
||||
margin: 1rem 0 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.photo-card {
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.6rem;
|
||||
background: var(--color-surface);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.photo-card img {
|
||||
width: 100%;
|
||||
aspect-ratio: 1;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.photo-card__bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.4rem;
|
||||
padding: 0.4rem 0.6rem;
|
||||
}
|
||||
|
||||
.photo-card__caption {
|
||||
font-size: 0.8rem;
|
||||
color: var(--color-text-muted);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
/* Profilfoto im Detail-Kopf */
|
||||
.profile-photo {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
border: 2px solid var(--color-border);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,11 @@ import { listColorVarieties, listContacts, listEnclosures, listLitters } from '.
|
||||
import { useApi } from '../hooks/useApi'
|
||||
import { formatDate, genderLabel, statusLabel } from '../format/labels'
|
||||
import { fromDisplayString, genotypeToFarbschlag, toDisplayString } from '../genetics'
|
||||
// FEAT-6 (Oscar): Tab-Inhalte + Profilfoto
|
||||
import GerbilHealthTab from '../components/GerbilHealthTab'
|
||||
import GerbilPhotosTab from '../components/GerbilPhotosTab'
|
||||
import GerbilProfilePhoto from '../components/GerbilProfilePhoto'
|
||||
import GerbilWeightTab from '../components/GerbilWeightTab'
|
||||
|
||||
type DetailTab = 'photos' | 'health' | 'weight'
|
||||
|
||||
@@ -79,6 +84,7 @@ export default function GerbilDetailPage() {
|
||||
<section className="page">
|
||||
<header className="page-head">
|
||||
<div>
|
||||
<GerbilProfilePhoto gerbilId={g.id} />
|
||||
<h2>{g.name}</h2>
|
||||
<span className={`badge badge--${g.status.toLowerCase()}`}>{statusLabel(g.status)}</span>
|
||||
</div>
|
||||
@@ -158,7 +164,9 @@ export default function GerbilDetailPage() {
|
||||
))}
|
||||
</div>
|
||||
<div className="tab-panel" role="tabpanel">
|
||||
<p className="muted">{t.detail.tabPlaceholder}</p>
|
||||
{tab === 'photos' && <GerbilPhotosTab gerbilId={g.id} />}
|
||||
{tab === 'health' && <GerbilHealthTab gerbilId={g.id} />}
|
||||
{tab === 'weight' && <GerbilWeightTab gerbilId={g.id} />}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user