diff --git a/client/src/pages/DogDetail.jsx b/client/src/pages/DogDetail.jsx
index 2ffd296..cfdc17f 100644
--- a/client/src/pages/DogDetail.jsx
+++ b/client/src/pages/DogDetail.jsx
@@ -8,6 +8,8 @@ import ClearanceSummaryCard from '../components/ClearanceSummaryCard'
import HealthRecordForm from '../components/HealthRecordForm'
import GeneticPanelCard from '../components/GeneticPanelCard'
import { ShieldCheck } from 'lucide-react'
+import { useToast } from '../components/Toast'
+import { useConfirm } from '../components/ConfirmDialog'
function DogDetail() {
const { id } = useParams()
@@ -18,6 +20,8 @@ function DogDetail() {
const [uploading, setUploading] = useState(false)
const [selectedPhoto, setSelectedPhoto] = useState(0)
const fileInputRef = useRef(null)
+ const toast = useToast()
+ const confirm = useConfirm()
// Health records state
const [healthRecords, setHealthRecords] = useState([])
@@ -57,7 +61,7 @@ function DogDetail() {
fetchDog()
} catch (error) {
console.error('Error uploading photo:', error)
- alert('Failed to upload photo')
+ toast.error(error.response?.data?.error || 'Failed to upload photo')
} finally {
setUploading(false)
if (fileInputRef.current) fileInputRef.current.value = ''
@@ -65,7 +69,7 @@ function DogDetail() {
}
const handleDeletePhoto = async (photoIndex) => {
- if (!confirm('Delete this photo?')) return
+ if (!(await confirm({ title: 'Delete photo?', message: 'This photo will be permanently removed.' }))) return
try {
await axios.delete(`/api/dogs/${id}/photos/${photoIndex}`)
fetchDog()
@@ -74,7 +78,7 @@ function DogDetail() {
}
} catch (error) {
console.error('Error deleting photo:', error)
- alert('Failed to delete photo')
+ toast.error(error.response?.data?.error || 'Failed to delete photo')
}
}
@@ -99,13 +103,17 @@ function DogDetail() {
const handleHealthSaved = () => { closeHealthForm(); fetchHealth() }
const handleDeleteHealth = async (rec) => {
const label = rec.test_name || (rec.test_type ? rec.test_type.replace(/_/g, ' ') : rec.record_type)
- if (!confirm(`Delete health record "${label}"?`)) return
+ if (!(await confirm({
+ title: 'Delete health record?',
+ message: `"${label}"${rec.test_date ? ` from ${rec.test_date}` : ''} will be permanently removed.`,
+ }))) return
try {
await axios.delete(`/api/health/${rec.id}`)
+ toast.success('Health record deleted')
fetchHealth()
} catch (error) {
console.error('Error deleting health record:', error)
- alert('Failed to delete health record')
+ toast.error(error.response?.data?.error || 'Failed to delete health record')
}
}
diff --git a/client/src/pages/DogList.jsx b/client/src/pages/DogList.jsx
index a9910ce..2480ae1 100644
--- a/client/src/pages/DogList.jsx
+++ b/client/src/pages/DogList.jsx
@@ -4,6 +4,7 @@ import { Dog, Plus, Search, Calendar, Hash, ArrowRight, Trash2 } from 'lucide-re
import axios from 'axios'
import DogForm from '../components/DogForm'
import { ChampionBadge, ChampionBloodlineBadge } from '../components/ChampionBadge'
+import { useToast } from '../components/Toast'
const LIMIT = 50
@@ -17,6 +18,7 @@ function DogList() {
const [showAddModal, setShowAddModal] = useState(false)
const [deleteTarget, setDeleteTarget] = useState(null) // { id, name }
const [deleting, setDeleting] = useState(false)
+ const toast = useToast()
const searchTimerRef = useRef(null)
useEffect(() => { fetchDogs(1, '', 'all') }, []) // eslint-disable-line
@@ -62,11 +64,12 @@ function DogList() {
setDeleting(true)
try {
await axios.delete(`/api/dogs/${deleteTarget.id}`)
+ toast.success(`${deleteTarget.name} deleted`)
setDeleteTarget(null)
fetchDogs(page, search, sexFilter)
} catch (err) {
console.error('Delete failed:', err)
- alert('Failed to delete dog. Please try again.')
+ toast.error(err.response?.data?.error || 'Failed to delete dog. Please try again.')
} finally {
setDeleting(false)
}
diff --git a/client/src/pages/ExternalDogs.jsx b/client/src/pages/ExternalDogs.jsx
index df9b8c8..2696c20 100644
--- a/client/src/pages/ExternalDogs.jsx
+++ b/client/src/pages/ExternalDogs.jsx
@@ -4,6 +4,7 @@ import { Dog, Plus, Search, Calendar, Hash, ArrowRight, Trash2, ExternalLink } f
import axios from 'axios'
import DogForm from '../components/DogForm'
import { ChampionBadge, ChampionBloodlineBadge } from '../components/ChampionBadge'
+import { useToast } from '../components/Toast'
function ExternalDogs() {
const [dogs, setDogs] = useState([])
@@ -14,6 +15,7 @@ function ExternalDogs() {
const [showAddModal, setShowAddModal] = useState(false)
const [deleteTarget, setDeleteTarget] = useState(null) // { id, name }
const [deleting, setDeleting] = useState(false)
+ const toast = useToast()
useEffect(() => { fetchDogs() }, [])
useEffect(() => { filterDogs() }, [dogs, search, sexFilter])
@@ -49,11 +51,12 @@ function ExternalDogs() {
setDeleting(true)
try {
await axios.delete(`/api/dogs/${deleteTarget.id}`)
+ toast.success(`${deleteTarget.name} deleted`)
setDogs(prev => prev.filter(d => d.id !== deleteTarget.id))
setDeleteTarget(null)
} catch (err) {
console.error('Delete failed:', err)
- alert('Failed to delete dog. Please try again.')
+ toast.error(err.response?.data?.error || 'Failed to delete dog. Please try again.')
} finally {
setDeleting(false)
}
diff --git a/client/src/pages/LitterDetail.jsx b/client/src/pages/LitterDetail.jsx
index 50da907..a912080 100644
--- a/client/src/pages/LitterDetail.jsx
+++ b/client/src/pages/LitterDetail.jsx
@@ -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
Loading litter...
diff --git a/client/src/pages/LitterList.jsx b/client/src/pages/LitterList.jsx
index ec6f324..b5845eb 100644
--- a/client/src/pages/LitterList.jsx
+++ b/client/src/pages/LitterList.jsx
@@ -3,6 +3,8 @@ import { Activity, Plus, Edit2, Trash2, ChevronRight } from 'lucide-react'
import { useNavigate } from 'react-router-dom'
import axios from 'axios'
import LitterForm from '../components/LitterForm'
+import { useToast } from '../components/Toast'
+import { useConfirm } from '../components/ConfirmDialog'
const LIMIT = 50
@@ -15,6 +17,8 @@ function LitterList() {
const [editingLitter, setEditingLitter] = useState(null)
const [prefill, setPrefill] = useState(null)
const navigate = useNavigate()
+ const toast = useToast()
+ const confirm = useConfirm()
useEffect(() => {
fetchLitters(1)
@@ -61,12 +65,17 @@ function LitterList() {
const handleDelete = async (e, id) => {
e.stopPropagation()
- if (!window.confirm('Delete this litter record? Puppies will be unlinked but not deleted.')) return
+ if (!(await confirm({
+ title: 'Delete litter record?',
+ message: 'Puppies will be unlinked but their dog records are kept.',
+ }))) return
try {
await axios.delete(`/api/litters/${id}`)
+ toast.success('Litter deleted')
fetchLitters(page)
} catch (error) {
console.error('Error deleting litter:', error)
+ toast.error(error.response?.data?.error || 'Failed to delete litter')
}
}