Updated bird profile view
This commit is contained in:
+77
-2
@@ -1698,6 +1698,12 @@ function App() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const vetVisitDueBirdIds = useMemo(() => new Set(vetVisitDueBirds.map((bird) => bird.id)), [vetVisitDueBirds]);
|
const vetVisitDueBirdIds = useMemo(() => new Set(vetVisitDueBirds.map((bird) => bird.id)), [vetVisitDueBirds]);
|
||||||
|
const selectedBirdWeightRangeAlert = selectedBird
|
||||||
|
? outOfRangeBirds.find((alert) => alert.bird.id === selectedBird.id) ?? null
|
||||||
|
: null;
|
||||||
|
const selectedBirdWeightDropAlerts = selectedBird ? weightDropAlerts.filter((alert) => alert.bird.id === selectedBird.id) : [];
|
||||||
|
const selectedBirdVetVisitAlertSignature = selectedBird ? getVetVisitAlertSignature(selectedBird.id) : '';
|
||||||
|
const selectedBirdHasVetVisitAlert = selectedBird ? vetVisitDueBirdIds.has(selectedBird.id) : false;
|
||||||
|
|
||||||
const activeMedications = useMemo(
|
const activeMedications = useMemo(
|
||||||
() => medications.filter((medication) => !medication.endDate || parseDateValue(medication.endDate) >= parseDateValue(new Date().toISOString().slice(0, 10))),
|
() => medications.filter((medication) => !medication.endDate || parseDateValue(medication.endDate) >= parseDateValue(new Date().toISOString().slice(0, 10))),
|
||||||
@@ -2336,6 +2342,18 @@ function App() {
|
|||||||
setPhotoDrag(null);
|
setPhotoDrag(null);
|
||||||
}, [editingBird, editingBirdId]);
|
}, [editingBird, editingBirdId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (activePage === 'flock' || !birdEditorOpen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setBirdEditorOpen(false);
|
||||||
|
setEditingBirdId('');
|
||||||
|
setBirdPhotoName('');
|
||||||
|
setPhotoCrop(null);
|
||||||
|
setPhotoDrag(null);
|
||||||
|
}, [activePage, birdEditorOpen]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setBulkWeightRows((current) => {
|
setBulkWeightRows((current) => {
|
||||||
const nextEntries = birds.map((bird) => [bird.id, current[bird.id] ?? { weightGrams: '' }] as const);
|
const nextEntries = birds.map((bird) => [bird.id, current[bird.id] ?? { weightGrams: '' }] as const);
|
||||||
@@ -3969,7 +3987,9 @@ function App() {
|
|||||||
return (
|
return (
|
||||||
<main className="auth-shell public-profile-shell">
|
<main className="auth-shell public-profile-shell">
|
||||||
<section className="panel public-profile-card">
|
<section className="panel public-profile-card">
|
||||||
<img className="public-profile-logo" src={flockPalLandingArt} alt="FlockPal" />
|
<a className="public-profile-logo-link" href="/" aria-label="Go to FlockPal sign in">
|
||||||
|
<img className="public-profile-logo" src={flockPalLandingArt} alt="FlockPal" />
|
||||||
|
</a>
|
||||||
{publicProfileLoading || authLoading ? (
|
{publicProfileLoading || authLoading ? (
|
||||||
<p>Loading bird profile...</p>
|
<p>Loading bird profile...</p>
|
||||||
) : publicProfileError || !publicProfile ? (
|
) : publicProfileError || !publicProfile ? (
|
||||||
@@ -4972,6 +4992,10 @@ function App() {
|
|||||||
onChange={(event) => setBirdForm({ ...birdForm, chartColor: event.target.value })}
|
onChange={(event) => setBirdForm({ ...birdForm, chartColor: event.target.value })}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
<div className="color-preview-card">
|
||||||
|
<span className="legend-swatch large-swatch" style={{ background: birdForm.chartColor }} />
|
||||||
|
<p className="muted">This color will follow this bird across the overview graph and its individual weight trend.</p>
|
||||||
|
</div>
|
||||||
<label className="toggle-field wide-field">
|
<label className="toggle-field wide-field">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@@ -5221,10 +5245,61 @@ function App() {
|
|||||||
<p className="eyebrow">Flock member</p>
|
<p className="eyebrow">Flock member</p>
|
||||||
<h2>{selectedBird.name}</h2>
|
<h2>{selectedBird.name}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="button-row">
|
<div className="member-header-actions">
|
||||||
|
{selectedBirdWeightRangeAlert || selectedBirdWeightDropAlerts.length || selectedBirdHasVetVisitAlert ? (
|
||||||
|
<div className="bird-alert-stack" aria-label={`Critical alerts for ${selectedBird.name}`}>
|
||||||
|
{selectedBirdWeightRangeAlert ? (
|
||||||
|
<span className="bird-alert-badge">
|
||||||
|
{selectedBirdWeightRangeAlert.assessment.status === 'below' ? 'Below chart range' : 'Above chart range'}
|
||||||
|
<button
|
||||||
|
className="alert-dismiss-button"
|
||||||
|
onClick={() =>
|
||||||
|
dismissAlert(
|
||||||
|
selectedBird.id,
|
||||||
|
'weight-range',
|
||||||
|
getWeightRangeAlertSignature(selectedBird, selectedBirdWeightRangeAlert.assessment),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
type="button"
|
||||||
|
aria-label={`Dismiss weight range alert for ${selectedBird.name}`}
|
||||||
|
>
|
||||||
|
Dismiss
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
{selectedBirdWeightDropAlerts.map((alert) => (
|
||||||
|
<span className="bird-alert-badge" key={`selected-drop-${alert.latestWeight.id}`}>
|
||||||
|
Down {alert.dropPercent.toFixed(1)}%
|
||||||
|
<button
|
||||||
|
className="alert-dismiss-button"
|
||||||
|
onClick={() => dismissAlert(selectedBird.id, 'weight-drop', getWeightDropAlertSignature(alert))}
|
||||||
|
type="button"
|
||||||
|
aria-label={`Dismiss weight drop alert for ${selectedBird.name}`}
|
||||||
|
>
|
||||||
|
Dismiss
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
{selectedBirdHasVetVisitAlert ? (
|
||||||
|
<span className="bird-alert-badge">
|
||||||
|
Annual vet visit due
|
||||||
|
<button
|
||||||
|
className="alert-dismiss-button"
|
||||||
|
onClick={() => dismissAlert(selectedBird.id, 'vet-visit', selectedBirdVetVisitAlertSignature)}
|
||||||
|
type="button"
|
||||||
|
aria-label={`Dismiss annual vet visit alert for ${selectedBird.name}`}
|
||||||
|
>
|
||||||
|
Dismiss
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<div className="button-row">
|
||||||
<button className="secondary-button" onClick={() => startEditBird(selectedBird)} type="button">
|
<button className="secondary-button" onClick={() => startEditBird(selectedBird)} type="button">
|
||||||
Edit details
|
Edit details
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -236,6 +236,12 @@ textarea {
|
|||||||
filter: drop-shadow(0 10px 18px rgba(86, 63, 34, 0.12));
|
filter: drop-shadow(0 10px 18px rgba(86, 63, 34, 0.12));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.public-profile-logo-link {
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.public-profile-photo {
|
.public-profile-photo {
|
||||||
width: min(260px, 100%);
|
width: min(260px, 100%);
|
||||||
aspect-ratio: 1;
|
aspect-ratio: 1;
|
||||||
@@ -745,6 +751,18 @@ textarea {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.member-header-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.55rem;
|
||||||
|
justify-content: end;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-header-actions .bird-alert-stack {
|
||||||
|
justify-content: end;
|
||||||
|
}
|
||||||
|
|
||||||
.billing-inline-action {
|
.billing-inline-action {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
Reference in New Issue
Block a user