Finished medication tracking and UI enhancements

This commit is contained in:
Corey Blais
2026-04-19 13:20:02 -04:00
parent 263b98d3d8
commit 872b6c8663
6 changed files with 589 additions and 253 deletions
+388 -251
View File
@@ -59,6 +59,16 @@ type Medication = {
notes: string | null;
};
type MedicationAdministration = {
id: string;
medicationId: string;
birdId: string;
administeredOn: string;
status: 'administered' | 'missed';
notes: string | null;
createdAt: string;
};
type Workspace = {
id: number;
name: string;
@@ -981,6 +991,7 @@ function App() {
const [weights, setWeights] = useState<WeightRecord[]>([]);
const [vetVisits, setVetVisits] = useState<VetVisit[]>([]);
const [medications, setMedications] = useState<Medication[]>([]);
const [medicationAdministrations, setMedicationAdministrations] = useState<MedicationAdministration[]>([]);
const [allBirdWeights, setAllBirdWeights] = useState<Record<string, WeightRecord[]>>({});
const [allBirdVetVisits, setAllBirdVetVisits] = useState<Record<string, VetVisit[]>>({});
const [dismissedAlerts, setDismissedAlerts] = useState<DismissedAlertMap>({});
@@ -1048,6 +1059,7 @@ function App() {
const [deletingVetVisitId, setDeletingVetVisitId] = useState('');
const [editingMedicationId, setEditingMedicationId] = useState('');
const [deletingMedicationId, setDeletingMedicationId] = useState('');
const [savingMedicationAdministrationId, setSavingMedicationAdministrationId] = useState('');
const [removingWorkspaceMemberId, setRemovingWorkspaceMemberId] = useState('');
const [expandedSettingsSection, setExpandedSettingsSection] = useState<SettingsSection | null>(null);
@@ -1491,6 +1503,7 @@ function App() {
setWeights([]);
setVetVisits([]);
setMedications([]);
setMedicationAdministrations([]);
setAllBirdWeights({});
setAllBirdVetVisits({});
setSelectedBirdId('');
@@ -1659,24 +1672,28 @@ function App() {
setWeights([]);
setVetVisits([]);
setMedications([]);
setMedicationAdministrations([]);
return;
}
const loadBirdDetail = async () => {
try {
const [weightsResponse, visitsResponse, medicationsResponse] = await Promise.all([
const [weightsResponse, visitsResponse, medicationsResponse, medicationAdministrationsResponse] = await Promise.all([
apiFetch(`/birds/${selectedBird.id}/weights?days=${OVERVIEW_HISTORY_DAYS}`, authToken),
apiFetch(`/birds/${selectedBird.id}/vet-visits`, authToken),
apiFetch(`/birds/${selectedBird.id}/medications`, authToken),
apiFetch(`/birds/${selectedBird.id}/medication-administrations`, authToken),
]);
if (!weightsResponse.ok || !visitsResponse.ok || !medicationsResponse.ok) {
if (!weightsResponse.ok || !visitsResponse.ok || !medicationsResponse.ok || !medicationAdministrationsResponse.ok) {
throw new Error('Unable to load flock member details.');
}
const weightsData = (await readJsonSafely<{ weights?: WeightRecord[] }>(weightsResponse)) ?? {};
const visitsData = (await readJsonSafely<{ vetVisits?: VetVisit[] }>(visitsResponse)) ?? {};
const medicationsData = (await readJsonSafely<{ medications?: Medication[] }>(medicationsResponse)) ?? {};
const medicationAdministrationsData =
(await readJsonSafely<{ administrations?: MedicationAdministration[] }>(medicationAdministrationsResponse)) ?? {};
setWeights(weightsData.weights ?? []);
const nextVetVisits = visitsData.vetVisits ?? [];
@@ -1686,10 +1703,12 @@ function App() {
[selectedBird.id]: nextVetVisits,
}));
setMedications(medicationsData.medications ?? []);
setMedicationAdministrations(medicationAdministrationsData.administrations ?? []);
setEditingVetVisitId('');
setDeletingVetVisitId('');
setEditingMedicationId('');
setDeletingMedicationId('');
setSavingMedicationAdministrationId('');
} catch (loadError) {
setError(loadError instanceof Error ? loadError.message : 'Unable to load flock member details.');
}
@@ -1914,6 +1933,7 @@ function App() {
setWeights([]);
setVetVisits([]);
setMedications([]);
setMedicationAdministrations([]);
setAllBirdVetVisits({});
setActivePage('overview');
} catch (switchError) {
@@ -2618,6 +2638,7 @@ function App() {
}
setMedications((current) => current.filter((medication) => medication.id !== medicationId));
setMedicationAdministrations((current) => current.filter((administration) => administration.medicationId !== medicationId));
if (editingMedicationId === medicationId) {
handleCancelMedicationEdit();
}
@@ -2628,6 +2649,49 @@ function App() {
}
};
const handleMedicationAdministrationSubmit = async (medicationId: string, status: MedicationAdministration['status']) => {
if (!selectedBird || savingMedicationAdministrationId) {
return;
}
setSavingMedicationAdministrationId(`${medicationId}-${status}`);
setError('');
try {
const administeredOn = new Date().toISOString().slice(0, 10);
const response = await apiFetch(`/birds/${selectedBird.id}/medications/${medicationId}/administrations`, authToken, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ administeredOn, status }),
});
if (!response.ok) {
throw new Error(await readErrorMessage(response, 'Unable to update medication administration.'));
}
const data = await readJsonSafely<{ administration: MedicationAdministration }>(response);
if (!data?.administration) {
throw new Error('Unable to update medication administration.');
}
setMedicationAdministrations((current) =>
[data.administration, ...current.filter((administration) => administration.id !== data.administration.id)]
.filter(
(administration, index, all) =>
all.findIndex(
(candidate) =>
candidate.medicationId === administration.medicationId && candidate.administeredOn === administration.administeredOn,
) === index,
)
.sort((left, right) => right.administeredOn.localeCompare(left.administeredOn) || right.createdAt.localeCompare(left.createdAt)),
);
} catch (submitError) {
setError(submitError instanceof Error ? submitError.message : 'Unable to update medication administration.');
} finally {
setSavingMedicationAdministrationId('');
}
};
const handleRemoveBird = async () => {
if (!selectedBird || deletingBird) {
return;
@@ -2669,10 +2733,12 @@ function App() {
setWeights([]);
setVetVisits([]);
setMedications([]);
setMedicationAdministrations([]);
setEditingVetVisitId('');
setDeletingVetVisitId('');
setEditingMedicationId('');
setDeletingMedicationId('');
setSavingMedicationAdministrationId('');
if (editingBirdId === selectedBird.id) {
setEditingBirdId('');
@@ -3226,6 +3292,93 @@ function App() {
}
const showWorkspaceSwitcher = authSession.workspaces.length > 1;
const todayDate = new Date().toISOString().slice(0, 10);
const renderMedicationCard = (medication: Medication, options: { showActions?: boolean; showAdministrationControls?: boolean }) => {
const latestAdministration = medicationAdministrations.find((administration) => administration.medicationId === medication.id);
const todayAdministration = medicationAdministrations.find(
(administration) => administration.medicationId === medication.id && administration.administeredOn === todayDate,
);
const givenActionId = `${medication.id}-administered`;
const missedActionId = `${medication.id}-missed`;
return (
<article key={medication.id} className="vet-visit-card">
<strong>{medication.name}</strong>
<span>
{medication.dosage} {medication.frequency}
{medication.route ? `${medication.route}` : ''}
</span>
<small>
{formatDate(medication.startDate)} to {formatDate(medication.endDate)}
</small>
<small>{medication.notes || 'No notes recorded.'}</small>
{latestAdministration ? (
<small>
Last update: {latestAdministration.status === 'administered' ? 'Given' : 'Missed'} on {formatShortDate(latestAdministration.administeredOn)}
</small>
) : null}
{options.showAdministrationControls ? (
<div className="medication-admin-actions">
<small>
Today:{' '}
{todayAdministration
? `${todayAdministration.status === 'administered' ? 'Given' : 'Missed'} on ${formatShortDate(todayAdministration.administeredOn)}`
: 'Not updated yet'}
</small>
<div className="button-row">
<button
className="primary-button"
onClick={() => handleMedicationAdministrationSubmit(medication.id, 'administered')}
type="button"
disabled={Boolean(savingMedicationAdministrationId)}
>
{savingMedicationAdministrationId === givenActionId ? 'Saving...' : 'Given today'}
</button>
<button
className="secondary-button"
onClick={() => handleMedicationAdministrationSubmit(medication.id, 'missed')}
type="button"
disabled={Boolean(savingMedicationAdministrationId)}
>
{savingMedicationAdministrationId === missedActionId ? 'Saving...' : 'Missed today'}
</button>
</div>
</div>
) : null}
{options.showActions ? (
<div className="button-row">
<button className="secondary-button" onClick={() => handleEditMedication(medication)} type="button">
Edit
</button>
{editingMedicationId === medication.id ? (
<button
className="secondary-button"
onClick={() => handleDeleteMedication(medication.id)}
type="button"
disabled={deletingMedicationId === medication.id}
>
{deletingMedicationId === medication.id ? 'Deleting...' : 'Delete'}
</button>
) : null}
</div>
) : null}
</article>
);
};
const renderMedicationList = (options: { showActions?: boolean; showAdministrationControls?: boolean }) =>
medications.length ? (
<>
{activeMedications.length ? <strong>Active medication</strong> : null}
{activeMedications.map((medication) => renderMedicationCard(medication, options))}
{pastMedications.length ? <strong>Past medication</strong> : null}
{pastMedications.map((medication) => renderMedicationCard(medication, options))}
</>
) : (
<article className="vet-visit-card empty-card">
<strong>No medication configured yet</strong>
<small>Add medication here to make it visible on the flock member care page.</small>
</article>
);
return (
<main className="app-shell">
@@ -3975,158 +4128,17 @@ function App() {
</form>
</section>
<section className="panel inset-panel">
<div className="panel-header">
<div>
<p className="eyebrow">Medication</p>
<h2>Per-bird medication log</h2>
{medications.length ? (
<section className="panel inset-panel">
<div className="panel-header">
<div>
<p className="eyebrow">Medication</p>
<h2>Medication schedule</h2>
</div>
</div>
</div>
<form className="form-panel inline-form" onSubmit={handleMedicationSubmit}>
<label>
Medication
<input
value={medicationForm.name}
onChange={(event) => setMedicationForm({ ...medicationForm, name: event.target.value })}
placeholder="Meloxicam"
required
/>
</label>
<label>
Dosage
<input
value={medicationForm.dosage}
onChange={(event) => setMedicationForm({ ...medicationForm, dosage: event.target.value })}
placeholder="0.05 mL"
required
/>
</label>
<label>
Frequency
<input
value={medicationForm.frequency}
onChange={(event) => setMedicationForm({ ...medicationForm, frequency: event.target.value })}
placeholder="Every 12 hours"
required
/>
</label>
<label>
Route
<input
value={medicationForm.route}
onChange={(event) => setMedicationForm({ ...medicationForm, route: event.target.value })}
placeholder="Oral"
/>
</label>
<label>
Start date
<input
type="date"
value={medicationForm.startDate}
onChange={(event) => setMedicationForm({ ...medicationForm, startDate: event.target.value })}
required
/>
</label>
<label>
End date
<input
type="date"
value={medicationForm.endDate}
onChange={(event) => setMedicationForm({ ...medicationForm, endDate: event.target.value })}
/>
</label>
<label className="wide-field">
Notes
<textarea
rows={3}
value={medicationForm.notes}
onChange={(event) => setMedicationForm({ ...medicationForm, notes: event.target.value })}
placeholder="Instructions, response, side effects, or prescribing vet"
/>
</label>
<div className="button-row wide-field">
<button className="primary-button" type="submit">
{editingMedicationId ? 'Save medication changes' : 'Save medication'}
</button>
{editingMedicationId ? (
<button className="secondary-button" onClick={handleCancelMedicationEdit} type="button">
Cancel edit
</button>
) : null}
</div>
</form>
<div className="recent-list">
{medications.length ? (
<>
{activeMedications.length ? <strong>Active medication</strong> : null}
{activeMedications.map((medication) => (
<article key={medication.id} className="vet-visit-card">
<strong>{medication.name}</strong>
<span>
{medication.dosage} {medication.frequency}
{medication.route ? `${medication.route}` : ''}
</span>
<small>
{formatDate(medication.startDate)} to {formatDate(medication.endDate)}
</small>
<small>{medication.notes || 'No notes recorded.'}</small>
<div className="button-row">
<button className="secondary-button" onClick={() => handleEditMedication(medication)} type="button">
Edit
</button>
{editingMedicationId === medication.id ? (
<button
className="secondary-button"
onClick={() => handleDeleteMedication(medication.id)}
type="button"
disabled={deletingMedicationId === medication.id}
>
{deletingMedicationId === medication.id ? 'Deleting...' : 'Delete'}
</button>
) : null}
</div>
</article>
))}
{pastMedications.length ? <strong>Past medication</strong> : null}
{pastMedications.map((medication) => (
<article key={medication.id} className="vet-visit-card">
<strong>{medication.name}</strong>
<span>
{medication.dosage} {medication.frequency}
{medication.route ? `${medication.route}` : ''}
</span>
<small>
{formatDate(medication.startDate)} to {formatDate(medication.endDate)}
</small>
<small>{medication.notes || 'No notes recorded.'}</small>
<div className="button-row">
<button className="secondary-button" onClick={() => handleEditMedication(medication)} type="button">
Edit
</button>
{editingMedicationId === medication.id ? (
<button
className="secondary-button"
onClick={() => handleDeleteMedication(medication.id)}
type="button"
disabled={deletingMedicationId === medication.id}
>
{deletingMedicationId === medication.id ? 'Deleting...' : 'Delete'}
</button>
) : null}
</div>
</article>
))}
</>
) : (
<article className="vet-visit-card empty-card">
<strong>No medication logged yet</strong>
<small>Add medication above to track dosage, frequency, dates, and notes for this bird.</small>
</article>
)}
</div>
</section>
<div className="recent-list">{renderMedicationList({ showAdministrationControls: true })}</div>
</section>
) : null}
<section className="panel inset-panel">
<div className="panel-header">
@@ -4136,7 +4148,7 @@ function App() {
</div>
</div>
<form className="form-panel inline-form" onSubmit={handleVetVisitSubmit}>
<form className="form-panel inline-form care-entry-form" onSubmit={handleVetVisitSubmit}>
<label>
Visit date
<input
@@ -4171,7 +4183,7 @@ function App() {
placeholder="Exam notes, medications, follow-ups, or restrictions"
/>
</label>
<div className="button-row wide-field">
<div className="button-row care-form-actions">
<button className="primary-button" type="submit">
{editingVetVisitId ? 'Save vet visit changes' : 'Save vet visit'}
</button>
@@ -4776,101 +4788,116 @@ function App() {
))}
</div>
<form className="form-panel" onSubmit={handleBirdSubmit}>
<label>
Bird name
<input value={birdForm.name} onChange={(event) => setBirdForm({ ...birdForm, name: event.target.value })} required />
</label>
<label>
Band ID
<input value={birdForm.tagId} onChange={(event) => setBirdForm({ ...birdForm, tagId: event.target.value })} required />
</label>
<label className="species-picker-field">
Species
<div className="species-picker">
<input
value={birdForm.species}
onChange={(event) => {
setBirdForm({ ...birdForm, species: event.target.value });
setSpeciesPickerOpen(true);
}}
onFocus={() => setSpeciesPickerOpen(true)}
onBlur={() => {
window.setTimeout(() => {
setSpeciesPickerOpen(false);
}, 120);
}}
placeholder="Start typing a species"
autoComplete="off"
required
/>
{speciesPickerOpen ? (
<div className="species-picker-menu">
{filteredSpeciesOptions.length ? (
filteredSpeciesOptions.map((speciesOption) => (
<button
key={speciesOption}
className={`species-picker-option ${birdForm.species === speciesOption ? 'active' : ''}`}
onMouseDown={(event) => {
event.preventDefault();
setBirdForm({ ...birdForm, species: speciesOption });
setSpeciesPickerOpen(false);
}}
type="button"
>
{speciesOption}
</button>
))
) : (
<div className="species-picker-empty">No matching species yet. Keep typing to add a custom entry.</div>
)}
<form className="form-panel settings-nested-stack" onSubmit={handleBirdSubmit}>
<section className="settings-nested-card">
<div className="settings-nested-header">
<p className="eyebrow">Identity</p>
<h3>Basic profile</h3>
</div>
<div className="settings-nested-grid">
<label>
Bird name
<input value={birdForm.name} onChange={(event) => setBirdForm({ ...birdForm, name: event.target.value })} required />
</label>
<label>
Band ID
<input value={birdForm.tagId} onChange={(event) => setBirdForm({ ...birdForm, tagId: event.target.value })} required />
</label>
<label className="species-picker-field wide-field">
Species
<div className="species-picker">
<input
value={birdForm.species}
onChange={(event) => {
setBirdForm({ ...birdForm, species: event.target.value });
setSpeciesPickerOpen(true);
}}
onFocus={() => setSpeciesPickerOpen(true)}
onBlur={() => {
window.setTimeout(() => {
setSpeciesPickerOpen(false);
}, 120);
}}
placeholder="Start typing a species"
autoComplete="off"
required
/>
{speciesPickerOpen ? (
<div className="species-picker-menu">
{filteredSpeciesOptions.length ? (
filteredSpeciesOptions.map((speciesOption) => (
<button
key={speciesOption}
className={`species-picker-option ${birdForm.species === speciesOption ? 'active' : ''}`}
onMouseDown={(event) => {
event.preventDefault();
setBirdForm({ ...birdForm, species: speciesOption });
setSpeciesPickerOpen(false);
}}
type="button"
>
{speciesOption}
</button>
))
) : (
<div className="species-picker-empty">No matching species yet. Keep typing to add a custom entry.</div>
)}
</div>
) : null}
</div>
) : null}
<small className="muted">Search or select a species so alerts and chart references stay consistent.</small>
</label>
<div className="segmented-field wide-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>
</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>
</section>
<section className="settings-nested-card">
<div className="settings-nested-header">
<p className="eyebrow">Dates</p>
<h3>Milestones and reminders</h3>
</div>
<small className="muted">Shown on the bird profile card as a symbol.</small>
</div>
<div className="settings-nested-grid">
<label>
DOB
<input
@@ -4905,6 +4932,15 @@ function App() {
/>
<small className="muted">Send a reminder on this bird's gotcha day anniversary.</small>
</label>
</div>
</section>
<section className="settings-nested-card">
<div className="settings-nested-header">
<p className="eyebrow">Display</p>
<h3>Chart color and photo</h3>
</div>
<div className="settings-nested-grid">
<label>
Graph color
<input type="color" value={birdForm.chartColor} onChange={(event) => setBirdForm({ ...birdForm, chartColor: event.target.value })} />
@@ -4914,7 +4950,7 @@ function App() {
<p className="muted">This color will follow this bird across the overview graph and its individual weight trend.</p>
</div>
<div className="photo-editor">
<div className="photo-editor wide-field">
<div className="photo-preview-shell">
{photoCrop ? (
<div
@@ -4988,11 +5024,112 @@ function App() {
) : null}
</div>
</div>
</div>
</section>
<button className="primary-button" type="submit" disabled={savingBird}>
{savingBird ? 'Saving...' : editingBird ? 'Save profile changes' : 'Save bird profile'}
</button>
<div className="settings-save-row">
<button className="primary-button" type="submit" disabled={savingBird}>
{savingBird ? 'Saving...' : editingBird ? 'Save profile changes' : 'Save bird profile'}
</button>
</div>
</form>
<section className="settings-subsection settings-nested-card">
<div className="panel-header">
<div>
<p className="eyebrow">Medication</p>
<h3>Medication configuration</h3>
<p className="muted">
Add per-bird medications here. The medication section only appears on the flock member page after medication is configured.
</p>
</div>
</div>
{selectedBird ? (
<>
<form className="form-panel inline-form care-entry-form" onSubmit={handleMedicationSubmit}>
<label>
Medication
<input
value={medicationForm.name}
onChange={(event) => setMedicationForm({ ...medicationForm, name: event.target.value })}
placeholder="Meloxicam"
required
/>
</label>
<label>
Dosage
<input
value={medicationForm.dosage}
onChange={(event) => setMedicationForm({ ...medicationForm, dosage: event.target.value })}
placeholder="0.05 mL"
required
/>
</label>
<label>
Frequency
<input
value={medicationForm.frequency}
onChange={(event) => setMedicationForm({ ...medicationForm, frequency: event.target.value })}
placeholder="Every 12 hours"
required
/>
</label>
<label>
Route
<input
value={medicationForm.route}
onChange={(event) => setMedicationForm({ ...medicationForm, route: event.target.value })}
placeholder="Oral"
/>
</label>
<label>
Start date
<input
type="date"
value={medicationForm.startDate}
onChange={(event) => setMedicationForm({ ...medicationForm, startDate: event.target.value })}
required
/>
</label>
<label>
End date
<input
type="date"
value={medicationForm.endDate}
onChange={(event) => setMedicationForm({ ...medicationForm, endDate: event.target.value })}
/>
</label>
<label className="wide-field">
Notes
<textarea
rows={3}
value={medicationForm.notes}
onChange={(event) => setMedicationForm({ ...medicationForm, notes: event.target.value })}
placeholder="Instructions, response, side effects, or prescribing vet"
/>
</label>
<div className="button-row care-form-actions">
<button className="primary-button" type="submit">
{editingMedicationId ? 'Save medication changes' : 'Save medication'}
</button>
{editingMedicationId ? (
<button className="secondary-button" onClick={handleCancelMedicationEdit} type="button">
Cancel edit
</button>
) : null}
</div>
</form>
<div className="recent-list">{renderMedicationList({ showActions: true })}</div>
</>
) : (
<article className="vet-visit-card empty-card">
<strong>Select a bird profile first</strong>
<small>Choose an existing bird above before adding medication configuration.</small>
</article>
)}
</section>
</>
) : null}
</article>
+56 -1
View File
@@ -533,6 +533,45 @@ textarea {
order: 2;
}
.settings-subsection {
display: grid;
gap: 1rem;
}
.settings-nested-stack {
gap: 1rem;
}
.settings-nested-card {
display: grid;
gap: 1rem;
padding: 1rem;
border: 1px solid rgba(53, 129, 98, 0.16);
border-radius: 22px;
background: rgba(255, 252, 246, 0.62);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7);
}
.settings-nested-header {
display: grid;
gap: 0.15rem;
}
.settings-nested-header h3 {
margin: 0;
}
.settings-nested-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 1rem;
}
.settings-save-row {
display: flex;
justify-content: flex-start;
}
.flock-member-panel,
.flock-member-sections {
display: grid;
@@ -1008,6 +1047,15 @@ textarea {
grid-column: 1 / -1;
}
.care-form-actions {
align-self: start;
margin-top: 0;
}
.care-entry-form {
margin-bottom: 1rem;
}
.legend-card {
grid-template-columns: auto 1fr;
align-items: center;
@@ -1142,6 +1190,12 @@ textarea {
opacity: 0.82;
}
.medication-admin-actions {
display: grid;
gap: 0.65rem;
padding-top: 0.35rem;
}
.form-panel {
display: grid;
gap: 1rem;
@@ -1471,7 +1525,8 @@ label {
.chart-footer,
.inline-form,
.profile-hero,
.photo-editor {
.photo-editor,
.settings-nested-grid {
grid-template-columns: 1fr;
}