From f3fc01f31c22c6c4f1da212082942c0155379d73 Mon Sep 17 00:00:00 2001 From: Jason Stedwell Date: Mon, 6 Jul 2026 15:06:32 -0500 Subject: [PATCH] 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 --- .claude/launch.json | 12 ++++++++ client/src/App.css | 14 +++++++-- client/src/components/HealthRecordForm.jsx | 11 +++++++ client/src/components/LitterForm.jsx | 5 +++ client/src/pages/BreedingCalendar.jsx | 4 +-- client/src/pages/DogDetail.jsx | 4 +-- client/src/pages/LitterDetail.jsx | 36 +++++++++++++++++----- 7 files changed, 72 insertions(+), 14 deletions(-) create mode 100644 .claude/launch.json diff --git a/.claude/launch.json b/.claude/launch.json new file mode 100644 index 0000000..d9818d3 --- /dev/null +++ b/.claude/launch.json @@ -0,0 +1,12 @@ +{ + "version": "0.0.1", + "configurations": [ + { + "name": "client", + "runtimeExecutable": "npm", + "runtimeArgs": ["run", "dev"], + "cwd": "client", + "port": 5173 + } + ] +} diff --git a/client/src/App.css b/client/src/App.css index 227c5b4..6d90de8 100644 --- a/client/src/App.css +++ b/client/src/App.css @@ -139,14 +139,22 @@ height: 4rem; } - .nav-links { + .navbar .container { + flex-wrap: wrap; gap: 0.25rem; } - + + .nav-links { + gap: 0.25rem; + max-width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + .nav-link span { display: none; } - + .nav-link { padding: 0.625rem; } diff --git a/client/src/components/HealthRecordForm.jsx b/client/src/components/HealthRecordForm.jsx index 670a925..12079c1 100644 --- a/client/src/components/HealthRecordForm.jsx +++ b/client/src/components/HealthRecordForm.jsx @@ -100,6 +100,14 @@ export default function HealthRecordForm({ dogId, dogBirthDate, record, onClose, test_type: v === 'ofa_clearance' ? (f.test_type || 'hip_ofa') : f.test_type, })) + // Live warning while typing — validate() still hard-blocks on submit. + const ageWarning = (() => { + if (!isOFA || !ofaTest?.minAgeMonths || !dogBirthDate || !form.test_date) return null + const months = (new Date(form.test_date) - new Date(dogBirthDate)) / (1000 * 60 * 60 * 24 * 30.44) + if (months < 0 || months >= ofaTest.minAgeMonths) return null + return `Dog was ~${Math.floor(months)} months old on this date — final ${ofaTest.label} certification requires ${ofaTest.minAgeMonths} months.` + })() + const validate = () => { if (!form.test_date) return 'A date is required.' if (form.test_date > today()) return 'Date cannot be in the future.' @@ -250,6 +258,9 @@ export default function HealthRecordForm({ dogId, dogBirthDate, record, onClose, set('test_date', e.target.value)} /> + {ageWarning && ( + {ageWarning} + )} {ofaTest?.recurs ? (
diff --git a/client/src/components/LitterForm.jsx b/client/src/components/LitterForm.jsx index 7576dd8..b1d2ff7 100644 --- a/client/src/components/LitterForm.jsx +++ b/client/src/components/LitterForm.jsx @@ -116,6 +116,11 @@ function LitterForm({ litter, prefill, onClose, onSave }) { ))} + {!!litter && ( +

+ Parents can't be changed after the litter is created. +

+ )}
diff --git a/client/src/pages/BreedingCalendar.jsx b/client/src/pages/BreedingCalendar.jsx index 5cb5066..a03fcef 100644 --- a/client/src/pages/BreedingCalendar.jsx +++ b/client/src/pages/BreedingCalendar.jsx @@ -534,7 +534,7 @@ export default function BreedingCalendar() {
{/* Day headers */} -
+
{DAY_NAMES.map(d => (
{d}
))} @@ -544,7 +544,7 @@ export default function BreedingCalendar() { {loading ? (
Loading calendar…
) : ( -
+
{Array.from({ length: rows * 7 }).map((_, idx) => { const dayNum = idx - startPad + 1 const isValid = dayNum >= 1 && dayNum <= lastDay.getDate() diff --git a/client/src/pages/DogDetail.jsx b/client/src/pages/DogDetail.jsx index cfdc17f..cffdef9 100644 --- a/client/src/pages/DogDetail.jsx +++ b/client/src/pages/DogDetail.jsx @@ -126,7 +126,7 @@ function DogDetail() { return (
{/* Header */} -
+
@@ -160,7 +160,7 @@ function DogDetail() {
-
+
{/* Photo Section */}
diff --git a/client/src/pages/LitterDetail.jsx b/client/src/pages/LitterDetail.jsx index a912080..b890095 100644 --- a/client/src/pages/LitterDetail.jsx +++ b/client/src/pages/LitterDetail.jsx @@ -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 (
{/* Header */} @@ -405,12 +412,27 @@ function LitterDetail() { )} {/* Puppies section */} -
+

Puppies

- +
+ {puppyCount > 1 && ( + + )} + +
{puppyCount === 0 ? ( @@ -419,8 +441,8 @@ function LitterDetail() {

No puppies linked yet. Add puppies to this litter.

) : ( -
- {litter.puppies.map(puppy => ( +
+ {sortedPuppies.map(puppy => (