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:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user