Added gender

This commit is contained in:
blaisadmin
2026-04-14 23:34:15 -04:00
parent 40900a0968
commit 43c32a5efc
8 changed files with 237 additions and 16 deletions
+91 -2
View File
@@ -7,6 +7,7 @@ type HouseholdBillingPlan = Exclude<BillingPlan, 'rescue_free'>;
type WorkspaceType = 'standard' | 'rescue';
type WorkspaceRole = 'owner' | 'manager' | 'staff' | 'viewer';
type IntegrationTokenScope = 'read_only' | 'read_write';
type BirdGender = 'unknown' | 'male' | 'female';
type Bird = {
id: string;
@@ -14,6 +15,7 @@ type Bird = {
name: string;
tagId: string;
species: string;
gender: BirdGender;
dateOfBirth: string | null;
gotchaDay: string | null;
chartColor: string;
@@ -113,6 +115,7 @@ type BirdFormState = {
name: string;
tagId: string;
species: string;
gender: BirdGender;
dateOfBirth: string;
gotchaDay: string;
chartColor: string;
@@ -207,6 +210,7 @@ const emptyBirdForm: BirdFormState = {
name: '',
tagId: '',
species: '',
gender: 'unknown',
dateOfBirth: '',
gotchaDay: '',
chartColor: '#cb3a35',
@@ -303,6 +307,7 @@ const toBirdForm = (bird: Bird): BirdFormState => ({
name: bird.name,
tagId: bird.tagId,
species: bird.species,
gender: bird.gender,
dateOfBirth: bird.dateOfBirth ?? '',
gotchaDay: bird.gotchaDay ?? '',
chartColor: bird.chartColor,
@@ -334,6 +339,26 @@ const formatShortDate = (value: string | null) => {
}).format(new Date(`${value}T00:00:00`));
};
const getBirdGenderLabel = (bird: Pick<Bird, 'gender'>) => {
if (bird.gender === 'female') {
return 'Female';
}
if (bird.gender === 'male') {
return 'Male';
}
return 'Unknown';
};
const getBirdGenderSymbol = (bird: Pick<Bird, 'gender'>) => {
if (bird.gender === 'female') {
return '♀';
}
if (bird.gender === 'male') {
return '♂';
}
return '?';
};
const formatDateTime = (value: string | null) => {
if (!value) {
return 'Never';
@@ -2376,7 +2401,12 @@ function App() {
</div>
)}
<div className="bird-card-copy">
<span>{bird.name}</span>
<span className="bird-card-title">
<span>{bird.name}</span>
<span aria-label={getBirdGenderLabel(bird)} className={`gender-inline ${bird.gender}`}>
{getBirdGenderSymbol(bird)}
</span>
</span>
<small>{bird.species}</small>
</div>
</div>
@@ -2482,7 +2512,15 @@ function App() {
)}
<div className="profile-copy">
<p className="eyebrow">Profile</p>
<h3>{selectedBird.name}</h3>
<h3 className="profile-title">
<span>{selectedBird.name}</span>
<span
aria-label={getBirdGenderLabel(selectedBird)}
className={`gender-symbol ${selectedBird.gender}`}
>
{getBirdGenderSymbol(selectedBird)}
</span>
</h3>
<p className="muted">
{selectedBird.species} Band {selectedBird.tagId}
</p>
@@ -2511,6 +2549,15 @@ function App() {
<span>Species</span>
<strong>{selectedBird.species}</strong>
</article>
<article className="detail-card">
<span>Gender</span>
<strong className="detail-gender">
<span aria-hidden="true" className={`gender-symbol ${selectedBird.gender}`}>
{getBirdGenderSymbol(selectedBird)}
</span>
{getBirdGenderLabel(selectedBird)}
</strong>
</article>
<article className="detail-card">
<span>Latest weight</span>
<strong>{formatWeight(selectedBird.latestWeightGrams)}</strong>
@@ -3206,6 +3253,48 @@ function App() {
</div>
<small className="muted">Search or select a species so alerts and chart references stay consistent.</small>
</label>
<div className="segmented-field">
<span>Gender</span>
<div className="segmented-control" role="radiogroup" aria-label="Bird gender">
<button
className={`segmented-option ${birdForm.gender === 'unknown' ? 'active' : ''}`}
onClick={() => setBirdForm({ ...birdForm, gender: 'unknown' })}
type="button"
role="radio"
aria-checked={birdForm.gender === 'unknown'}
>
<span className="gender-symbol unknown" aria-hidden="true">
?
</span>
Unknown
</button>
<button
className={`segmented-option ${birdForm.gender === 'male' ? 'active' : ''}`}
onClick={() => setBirdForm({ ...birdForm, gender: 'male' })}
type="button"
role="radio"
aria-checked={birdForm.gender === 'male'}
>
<span className="gender-symbol male" aria-hidden="true">
</span>
Male
</button>
<button
className={`segmented-option ${birdForm.gender === 'female' ? 'active' : ''}`}
onClick={() => setBirdForm({ ...birdForm, gender: 'female' })}
type="button"
role="radio"
aria-checked={birdForm.gender === 'female'}
>
<span className="gender-symbol female" aria-hidden="true">
</span>
Female
</button>
</div>
<small className="muted">Shown on the bird profile card as a symbol.</small>
</div>
<label>
DOB
<input
+92
View File
@@ -578,6 +578,34 @@ textarea {
font-weight: 600;
}
.bird-card-title {
display: inline-flex;
align-items: center;
gap: 0.45rem;
}
.bird-card-title span {
display: inline;
}
.gender-inline {
font-size: 1.2rem;
font-weight: 700;
line-height: 1;
}
.gender-inline.male {
color: var(--accent-blue);
}
.gender-inline.female {
color: var(--accent-red);
}
.gender-inline.unknown {
color: var(--muted);
}
.bird-avatar,
.profile-photo {
width: 56px;
@@ -723,6 +751,70 @@ textarea {
font-size: 1.6rem;
}
.profile-title,
.detail-gender {
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.gender-symbol {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 1.9rem;
height: 1.9rem;
border-radius: 999px;
font-size: 1.2rem;
font-weight: 700;
line-height: 1;
}
.gender-symbol.male {
background: rgba(39, 105, 179, 0.12);
color: var(--accent-blue);
}
.gender-symbol.female {
background: rgba(203, 58, 53, 0.12);
color: var(--accent-red);
}
.gender-symbol.unknown {
background: rgba(93, 95, 89, 0.12);
color: var(--muted);
}
.segmented-field {
display: grid;
gap: 0.65rem;
}
.segmented-control {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 0.55rem;
}
.segmented-option {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.45rem;
border: 1px solid rgba(39, 105, 179, 0.14);
border-radius: 16px;
padding: 0.85rem 1rem;
background: rgba(255, 254, 250, 0.92);
color: var(--ink);
transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
}
.segmented-option.active {
border-color: rgba(35, 138, 90, 0.4);
box-shadow: 0 10px 20px rgba(39, 105, 179, 0.1);
transform: translateY(-1px);
}
.inline-form {
grid-template-columns: repeat(2, minmax(0, 1fr));
}