Polish: live OFA age warning, puppy sorting, responsive fixes, form hints

- HealthRecordForm shows a live amber warning under Test Date when the dog is
  under the OFA minimum certification age (submit-time block unchanged)
- LitterDetail puppies sortable by name / sex / birth date
- Responsive at 375px: DogDetail photos+info grid stacks, header wraps;
  puppy grid uses min(220px,100%); navbar wraps with scrollable icon row;
  calendar uses minmax(0,1fr) columns so cells shrink instead of overflowing
- LitterForm explains why sire/dam selects are locked when editing
- Add .claude/launch.json for the vite dev server

Verified in-browser at 375px and desktop: no horizontal overflow on any page.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jason Stedwell
2026-07-06 15:06:32 -05:00
parent 55d636b512
commit f3fc01f31c
7 changed files with 72 additions and 14 deletions
+29 -7
View File
@@ -272,6 +272,7 @@ function LitterDetail() {
const [addMode, setAddMode] = useState('existing')
const [error, setError] = useState('')
const [saving, setSaving] = useState(false)
const [puppySort, setPuppySort] = useState('name')
const toast = useToast()
const confirm = useConfirm()
@@ -351,6 +352,12 @@ function LitterDetail() {
const puppyCount = litter.puppies?.length ?? 0
const sortedPuppies = [...(litter.puppies || [])].sort((a, b) => {
if (puppySort === 'sex') return (a.sex || '').localeCompare(b.sex || '') || (a.name || '').localeCompare(b.name || '')
if (puppySort === 'dob') return (a.date_of_birth || '').localeCompare(b.date_of_birth || '') || (a.name || '').localeCompare(b.name || '')
return (a.name || '').localeCompare(b.name || '')
})
return (
<div className="container">
{/* Header */}
@@ -405,12 +412,27 @@ function LitterDetail() {
)}
{/* Puppies section */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem', flexWrap: 'wrap', gap: '0.75rem' }}>
<h2 style={{ margin: 0 }}>Puppies</h2>
<button className="btn btn-primary" onClick={() => { setShowAddPuppy(true); setError('') }}>
<Plus size={16} style={{ marginRight: '0.4rem' }} />
Add Puppy
</button>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
{puppyCount > 1 && (
<select
value={puppySort}
onChange={e => setPuppySort(e.target.value)}
className="input"
style={{ width: 'auto', fontSize: '0.85rem', padding: '0.4rem 0.6rem' }}
aria-label="Sort puppies"
>
<option value="name">Sort: Name</option>
<option value="sex">Sort: Sex</option>
<option value="dob">Sort: Birth Date</option>
</select>
)}
<button className="btn btn-primary" onClick={() => { setShowAddPuppy(true); setError('') }}>
<Plus size={16} style={{ marginRight: '0.4rem' }} />
Add Puppy
</button>
</div>
</div>
{puppyCount === 0 ? (
@@ -419,8 +441,8 @@ function LitterDetail() {
<p style={{ color: 'var(--text-secondary)' }}>No puppies linked yet. Add puppies to this litter.</p>
</div>
) : (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(240px, 1fr))', gap: '1rem' }}>
{litter.puppies.map(puppy => (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(min(220px, 100%), 1fr))', gap: '1rem' }}>
{sortedPuppies.map(puppy => (
<div key={puppy.id} className="card" style={{ position: 'relative' }}>
<button
className="btn-icon"