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;
|
color: #7a5a14;
|
||||||
margin-left: 0.4rem;
|
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 { useApi } from '../hooks/useApi'
|
||||||
import { formatDate, genderLabel, statusLabel } from '../format/labels'
|
import { formatDate, genderLabel, statusLabel } from '../format/labels'
|
||||||
import { fromDisplayString, genotypeToFarbschlag, toDisplayString } from '../genetics'
|
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'
|
type DetailTab = 'photos' | 'health' | 'weight'
|
||||||
|
|
||||||
@@ -79,6 +84,7 @@ export default function GerbilDetailPage() {
|
|||||||
<section className="page">
|
<section className="page">
|
||||||
<header className="page-head">
|
<header className="page-head">
|
||||||
<div>
|
<div>
|
||||||
|
<GerbilProfilePhoto gerbilId={g.id} />
|
||||||
<h2>{g.name}</h2>
|
<h2>{g.name}</h2>
|
||||||
<span className={`badge badge--${g.status.toLowerCase()}`}>{statusLabel(g.status)}</span>
|
<span className={`badge badge--${g.status.toLowerCase()}`}>{statusLabel(g.status)}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -158,7 +164,9 @@ export default function GerbilDetailPage() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="tab-panel" role="tabpanel">
|
<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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user