Added gender
This commit is contained in:
+91
-2
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user