Client UX: toast system, confirm dialogs, load-error banners, nav fix, 404 page
- New ToastProvider/useToast (self-built, no dep): success/error toasts replace
all five alert() calls, with server error messages surfaced
- New ConfirmProvider/useConfirm promise-based dialog replaces all six
window.confirm sites, showing what is being deleted; Escape/overlay cancels
- Dashboard + BreedingCalendar loaders use Promise.allSettled with a
danger banner + Retry instead of silently blanking on fetch failure
- Settings load failure now surfaces a toast instead of vanishing
- Fix BreedingCalendar females list: read paginated {data} shape (was .dogs,
so 'Start Heat Cycle' never listed any females)
- Nav links highlight on detail routes (/dogs/123, /pedigree/5)
- Catch-all 404 route with link back to Dashboard
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,8 @@ import { useParams, useNavigate } from 'react-router-dom'
|
||||
import { ArrowLeft, Plus, X, ExternalLink, Dog, Weight, ChevronDown, ChevronUp, Trash2 } from 'lucide-react'
|
||||
import axios from 'axios'
|
||||
import LitterForm from '../components/LitterForm'
|
||||
import { useToast } from '../components/Toast'
|
||||
import { useConfirm } from '../components/ConfirmDialog'
|
||||
|
||||
// ─── Puppy Log Panel ────────────────────────────────────────────────────────────
|
||||
function PuppyLogPanel({ litterId, puppy, whelpingDate }) {
|
||||
@@ -18,6 +20,8 @@ function PuppyLogPanel({ litterId, puppy, whelpingDate }) {
|
||||
record_type: 'weight_log'
|
||||
})
|
||||
const [saving, setSaving] = useState(false)
|
||||
const toast = useToast()
|
||||
const confirm = useConfirm()
|
||||
|
||||
useEffect(() => { if (open) fetchLogs() }, [open])
|
||||
|
||||
@@ -41,16 +45,22 @@ function PuppyLogPanel({ litterId, puppy, whelpingDate }) {
|
||||
setShowAdd(false)
|
||||
setForm(f => ({ ...f, weight_oz: '', weight_lbs: '', notes: '' }))
|
||||
fetchLogs()
|
||||
} catch (e) { console.error(e) }
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.error(e.response?.data?.error || 'Failed to save log entry')
|
||||
}
|
||||
finally { setSaving(false) }
|
||||
}
|
||||
|
||||
const handleDelete = async (logId) => {
|
||||
if (!window.confirm('Delete this log entry?')) return
|
||||
if (!(await confirm({ title: 'Delete log entry?', message: `This log entry for ${puppy.name} will be permanently removed.` }))) return
|
||||
try {
|
||||
await axios.delete(`/api/litters/${litterId}/puppies/${puppy.id}/logs/${logId}`)
|
||||
fetchLogs()
|
||||
} catch (e) { console.error(e) }
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.error('Failed to delete log entry')
|
||||
}
|
||||
}
|
||||
|
||||
const TYPES = [
|
||||
@@ -262,6 +272,8 @@ function LitterDetail() {
|
||||
const [addMode, setAddMode] = useState('existing')
|
||||
const [error, setError] = useState('')
|
||||
const [saving, setSaving] = useState(false)
|
||||
const toast = useToast()
|
||||
const confirm = useConfirm()
|
||||
|
||||
useEffect(() => { fetchLitter(); fetchAllDogs() }, [id])
|
||||
|
||||
@@ -320,11 +332,18 @@ function LitterDetail() {
|
||||
}
|
||||
|
||||
const handleUnlinkPuppy = async (puppyId) => {
|
||||
if (!window.confirm('Remove this puppy from the litter? The dog record will not be deleted.')) return
|
||||
if (!(await confirm({
|
||||
title: 'Remove puppy from litter?',
|
||||
message: 'The dog record itself will not be deleted.',
|
||||
confirmLabel: 'Remove',
|
||||
}))) return
|
||||
try {
|
||||
await axios.delete(`/api/litters/${id}/puppies/${puppyId}`)
|
||||
fetchLitter()
|
||||
} catch (err) { console.error('Error unlinking puppy:', err) }
|
||||
} catch (err) {
|
||||
console.error('Error unlinking puppy:', err)
|
||||
toast.error(err.response?.data?.error || 'Failed to remove puppy from litter')
|
||||
}
|
||||
}
|
||||
|
||||
if (loading) return <div className="container loading">Loading litter...</div>
|
||||
|
||||
Reference in New Issue
Block a user