automated dev db build

This commit is contained in:
blaisadmin
2026-05-21 17:27:57 -04:00
parent 4715306d14
commit df3fcbf885
3 changed files with 177 additions and 87 deletions
+120 -81
View File
@@ -472,6 +472,23 @@ const parseImportGender = (value: unknown): BirdGender | null => {
const getBirdImportKey = (name: string, tagId: string) => (tagId ? `band:${tagId.toLowerCase()}` : `name:${name.toLowerCase()}`);
const mergeImportText = (current: string, next: string) => current || next;
const birdProfileListLimit = 3;
const parseBirdProfileList = (value: string | null | undefined) =>
(value ?? '')
.split(/\r?\n/)
.map((item) => item.trim())
.filter(Boolean)
.slice(0, birdProfileListLimit);
const getBirdProfileListFields = (value: string) =>
Array.from({ length: birdProfileListLimit }, (_, index) => parseBirdProfileList(value)[index] ?? '');
const updateBirdProfileListField = (value: string, index: number, nextItem: string) => {
const items = getBirdProfileListFields(value);
items[index] = nextItem;
return items.map((item) => item.trim()).filter(Boolean).join('\n');
};
const parseBirdImportRows = (rows: Record<string, unknown>[]): BirdImportPreview => {
const errors: string[] = [];
@@ -701,8 +718,8 @@ const toBirdForm = (bird: Bird): BirdFormState => ({
name: bird.name,
tagId: bird.tagId ?? '',
species: bird.species,
motivators: bird.motivators ?? '',
demotivators: bird.demotivators ?? '',
motivators: parseBirdProfileList(bird.motivators).join('\n'),
demotivators: parseBirdProfileList(bird.demotivators).join('\n'),
favoriteSnack: bird.favoriteSnack ?? '',
gender: bird.gender,
dateOfBirth: bird.dateOfBirth ?? '',
@@ -4804,26 +4821,33 @@ function App() {
<span>Gotcha day</span>
<strong>{formatDate(selectedBird.gotchaDay)}</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>Favorite snack</span>
<strong>{selectedBird.favoriteSnack || 'Not recorded'}</strong>
</article>
<article className="detail-card">
<span>Motivators</span>
<strong>{selectedBird.motivators || 'Not recorded'}</strong>
{parseBirdProfileList(selectedBird.motivators).length ? (
<ul className="detail-item-list">
{parseBirdProfileList(selectedBird.motivators).map((motivator, index) => (
<li key={`${motivator}-${index}`}>{motivator}</li>
))}
</ul>
) : (
<strong>Not recorded</strong>
)}
</article>
<article className="detail-card">
<span>Demotivators</span>
<strong>{selectedBird.demotivators || 'Not recorded'}</strong>
{parseBirdProfileList(selectedBird.demotivators).length ? (
<ul className="detail-item-list">
{parseBirdProfileList(selectedBird.demotivators).map((demotivator, index) => (
<li key={`${demotivator}-${index}`}>{demotivator}</li>
))}
</ul>
) : (
<strong>Not recorded</strong>
)}
</article>
</div>
@@ -5577,7 +5601,7 @@ function App() {
</article>
) : null}
<article className="summary-card">
<strong>{workspace?.billingEmail || authSession.user.email}</strong>
<strong className="billing-contact-email">{workspace?.billingEmail || authSession.user.email}</strong>
<span>Billing contact for invoices, receipts, and account notices.</span>
</article>
<article className="summary-card">
@@ -5895,30 +5919,6 @@ function App() {
</div>
<small className="muted">Search or select a species so alerts and chart references stay consistent.</small>
</label>
<label>
Favorite snack
<input
value={birdForm.favoriteSnack}
onChange={(event) => setBirdForm({ ...birdForm, favoriteSnack: event.target.value })}
placeholder="Optional"
/>
</label>
<label className="wide-field">
Motivators
<textarea
value={birdForm.motivators}
onChange={(event) => setBirdForm({ ...birdForm, motivators: event.target.value })}
placeholder="Training rewards, sounds, people, toys, routines"
/>
</label>
<label className="wide-field">
Demotivates
<textarea
value={birdForm.demotivators}
onChange={(event) => setBirdForm({ ...birdForm, demotivators: event.target.value })}
placeholder="Stressors, disliked handling, noises, situations"
/>
</label>
<div className="segmented-field wide-field">
<span>Gender</span>
<div className="segmented-control" role="radiogroup" aria-label="Bird gender">
@@ -5961,49 +5961,88 @@ function App() {
</div>
<small className="muted">Shown on the bird profile card as a symbol.</small>
</div>
</div>
</section>
<section className="settings-nested-card">
<div className="settings-nested-header">
<p className="eyebrow">Dates</p>
<h3>Milestones and reminders</h3>
</div>
<div className="settings-nested-grid">
<label>
Hatch Day
<input
type="date"
value={birdForm.dateOfBirth}
onChange={(event) => setBirdForm({ ...birdForm, dateOfBirth: event.target.value })}
/>
</label>
<label>
Gotcha day
<input
type="date"
value={birdForm.gotchaDay}
onChange={(event) => setBirdForm({ ...birdForm, gotchaDay: event.target.value })}
/>
</label>
<label className="toggle-card">
<span>Notify on Hatch Day</span>
<input
type="checkbox"
checked={birdForm.notifyOnDob}
onChange={(event) => setBirdForm({ ...birdForm, notifyOnDob: event.target.checked })}
/>
<small className="muted">Send a reminder on this bird's Hatch Day each year.</small>
</label>
<label className="toggle-card">
<span>Notify on gotcha day</span>
<input
type="checkbox"
checked={birdForm.notifyOnGotchaDay}
onChange={(event) => setBirdForm({ ...birdForm, notifyOnGotchaDay: event.target.checked })}
/>
<small className="muted">Send a reminder on this bird's gotcha day anniversary.</small>
</label>
<div className="settings-inline-header wide-field">
<p className="eyebrow">Dates</p>
<h3>Milestones and reminders</h3>
</div>
<label>
Hatch Day
<input
type="date"
value={birdForm.dateOfBirth}
onChange={(event) => setBirdForm({ ...birdForm, dateOfBirth: event.target.value })}
/>
</label>
<label>
Gotcha day
<input
type="date"
value={birdForm.gotchaDay}
onChange={(event) => setBirdForm({ ...birdForm, gotchaDay: event.target.value })}
/>
</label>
<label className="toggle-card">
<span>Notify on Hatch Day</span>
<input
type="checkbox"
checked={birdForm.notifyOnDob}
onChange={(event) => setBirdForm({ ...birdForm, notifyOnDob: event.target.checked })}
/>
<small className="muted">Send a reminder on this bird's Hatch Day each year.</small>
</label>
<label className="toggle-card">
<span>Notify on gotcha day</span>
<input
type="checkbox"
checked={birdForm.notifyOnGotchaDay}
onChange={(event) => setBirdForm({ ...birdForm, notifyOnGotchaDay: event.target.checked })}
/>
<small className="muted">Send a reminder on this bird's gotcha day anniversary.</small>
</label>
<label>
Favorite snack
<input
value={birdForm.favoriteSnack}
onChange={(event) => setBirdForm({ ...birdForm, favoriteSnack: event.target.value })}
placeholder="Optional"
/>
</label>
<label className="wide-field">
Motivators
<div className="profile-list-fields">
{getBirdProfileListFields(birdForm.motivators).map((motivator, index) => (
<input
key={`motivator-${index}`}
value={motivator}
onChange={(event) =>
setBirdForm({
...birdForm,
motivators: updateBirdProfileListField(birdForm.motivators, index, event.target.value),
})
}
placeholder={index === 0 ? 'Training reward, sound, person, toy, or routine' : `Motivator ${index + 1}`}
/>
))}
</div>
</label>
<label className="wide-field">
Demotivators
<div className="profile-list-fields">
{getBirdProfileListFields(birdForm.demotivators).map((demotivator, index) => (
<input
key={`demotivator-${index}`}
value={demotivator}
onChange={(event) =>
setBirdForm({
...birdForm,
demotivators: updateBirdProfileListField(birdForm.demotivators, index, event.target.value),
})
}
placeholder={index === 0 ? 'Stressor, disliked handling, noise, or situation' : `Demotivator ${index + 1}`}
/>
))}
</div>
</label>
</div>
</section>
+45 -4
View File
@@ -1110,8 +1110,7 @@ textarea {
font-size: 1.6rem;
}
.profile-title,
.detail-gender {
.profile-title {
display: inline-flex;
align-items: center;
gap: 0.5rem;
@@ -1209,6 +1208,25 @@ textarea {
grid-column: 1 / -1;
}
.settings-inline-header {
display: grid;
gap: 0.15rem;
padding-top: 0.35rem;
}
.settings-inline-header h3 {
margin: 0;
}
.profile-list-fields {
display: grid;
gap: 0.65rem;
}
.profile-list-fields input {
margin-top: 0;
}
.care-form-actions {
align-self: start;
margin-top: 0;
@@ -1277,6 +1295,21 @@ textarea {
font-size: 1.05rem;
}
.billing-contact-email {
min-width: 0;
overflow-wrap: anywhere;
}
.detail-item-list {
margin: 0;
padding-left: 1.15rem;
font-weight: 600;
}
.detail-item-list li + li {
margin-top: 0.2rem;
}
.summary-list {
display: grid;
gap: 0.2rem;
@@ -1833,16 +1866,24 @@ label {
.side-rail {
position: static;
grid-template-columns: auto minmax(0, 1fr);
align-items: center;
gap: 0.55rem;
}
.brand-lockup {
display: none;
justify-items: start;
padding-left: 0;
}
.side-nav-logo {
width: min(120px, 27vw);
}
.side-nav.panel {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
min-width: 0;
align-items: center;
gap: 0.65rem;
padding: 0.65rem;
@@ -1850,7 +1891,7 @@ label {
}
.page-tabs {
grid-template-columns: repeat(auto-fit, minmax(82px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(64px, 1fr));
gap: 0.4rem;
}