diff --git a/.zenflow/tasks/6e6e64eb-cb72-459e-b943-27554a749459/investigation.md b/.zenflow/tasks/6e6e64eb-cb72-459e-b943-27554a749459/investigation.md index 66d6a8e..4c8ab6d 100644 --- a/.zenflow/tasks/6e6e64eb-cb72-459e-b943-27554a749459/investigation.md +++ b/.zenflow/tasks/6e6e64eb-cb72-459e-b943-27554a749459/investigation.md @@ -13,12 +13,17 @@ The "External Dogs" interface does not match the layout and style of the main "D ## Affected Components - `client/src/pages/ExternalDogs.jsx` -## Proposed Solution -Refactor `ExternalDogs.jsx` to mirror the structure and style of `DogList.jsx`: -1. **Standardize Imports**: Use `axios` instead of `fetch`. Import `ChampionBadge`, `ChampionBloodlineBadge`, and necessary `lucide-react` icons. -2. **Match Layout**: Update the main container and header to match `DogList.jsx` using the `container` class and consistent inline styles. -3. **Sync Filter Bar**: Use the same search and filter bar implementation as `DogList.jsx`. -4. **Implement List View**: Replace the `dog-grid` with a vertical stack of cards matching the Dogs page style. -5. **Add Delete Functionality**: Implement the `handleDelete` logic and add the `Delete Confirmation Modal`. -6. **Use Badges & Helpers**: Replace emoji badges with the standardized badge components and use `calculateAge` for birth dates. -7. **Consistent Navigation**: Use `react-router-dom`'s `Link` for navigation to dog details. +## Implementation Notes +Refactored `ExternalDogs.jsx` to match `DogList.jsx` in layout, style, and functionality. Key changes: +- Switched to `axios` for API calls. +- Adopted the vertical list layout instead of the grid. +- Used standardized `ChampionBadge` and `ChampionBloodlineBadge` components. +- Added a search/filter bar consistent with the main Dogs page. +- Implemented delete functionality with a confirmation modal. +- Standardized age calculation using the `calculateAge` helper logic. +- Added an "EXT" badge to the dog avatars to clearly identify them as external dogs while maintaining the overall style. + +## Test Results +- Verified that all components are correctly imported. +- Verified that API endpoints match the backend routes. +- Code review shows consistent use of CSS variables and classes (e.g., `container`, `card`, `btn`). diff --git a/.zenflow/tasks/new-task-6e6e/plan.md b/.zenflow/tasks/new-task-6e6e/plan.md index 7d9ae26..66fec80 100644 --- a/.zenflow/tasks/new-task-6e6e/plan.md +++ b/.zenflow/tasks/new-task-6e6e/plan.md @@ -31,7 +31,8 @@ Save findings to `{@artifacts_path}/investigation.md` with: - Affected components - Proposed solution -### [ ] Step: Implementation +### [x] Step: Implementation + Read `{@artifacts_path}/investigation.md` Implement the bug fix. diff --git a/client/src/pages/ExternalDogs.jsx b/client/src/pages/ExternalDogs.jsx index 9baa308..df9b8c8 100644 --- a/client/src/pages/ExternalDogs.jsx +++ b/client/src/pages/ExternalDogs.jsx @@ -1,110 +1,327 @@ -import { useState, useEffect } from 'react'; -import { Users, Plus, Search, ExternalLink, Award, Filter } from 'lucide-react'; -import DogForm from '../components/DogForm'; +import { useEffect, useState } from 'react' +import { Link } from 'react-router-dom' +import { Dog, Plus, Search, Calendar, Hash, ArrowRight, Trash2, ExternalLink } from 'lucide-react' +import axios from 'axios' +import DogForm from '../components/DogForm' +import { ChampionBadge, ChampionBloodlineBadge } from '../components/ChampionBadge' -export default function ExternalDogs() { - const [dogs, setDogs] = useState([]); - const [loading, setLoading] = useState(true); - const [search, setSearch] = useState(''); - const [sexFilter, setSexFilter] = useState('all'); - const [showAddModal, setShowAddModal] = useState(false); +function ExternalDogs() { + const [dogs, setDogs] = useState([]) + const [filteredDogs, setFilteredDogs] = useState([]) + const [search, setSearch] = useState('') + const [sexFilter, setSexFilter] = useState('all') + const [loading, setLoading] = useState(true) + const [showAddModal, setShowAddModal] = useState(false) + const [deleteTarget, setDeleteTarget] = useState(null) // { id, name } + const [deleting, setDeleting] = useState(false) - useEffect(() => { - fetchDogs(); - }, []); + useEffect(() => { fetchDogs() }, []) + useEffect(() => { filterDogs() }, [dogs, search, sexFilter]) - const fetchDogs = () => { - fetch('/api/dogs/external') - .then(r => r.json()) - .then(data => { setDogs(data); setLoading(false); }) - .catch(() => setLoading(false)); - }; + const fetchDogs = async () => { + try { + const res = await axios.get('/api/dogs/external') + setDogs(res.data) + setLoading(false) + } catch (error) { + console.error('Error fetching external dogs:', error) + setLoading(false) + } + } - const filtered = dogs.filter(d => { - const matchSearch = d.name.toLowerCase().includes(search.toLowerCase()) || - (d.breed || '').toLowerCase().includes(search.toLowerCase()); - const matchSex = sexFilter === 'all' || d.sex === sexFilter; - return matchSearch && matchSex; - }); + const filterDogs = () => { + let filtered = dogs + if (search) { + filtered = filtered.filter(dog => + dog.name.toLowerCase().includes(search.toLowerCase()) || + (dog.breed && dog.breed.toLowerCase().includes(search.toLowerCase())) || + (dog.registration_number && dog.registration_number.toLowerCase().includes(search.toLowerCase())) + ) + } + if (sexFilter !== 'all') { + filtered = filtered.filter(dog => dog.sex === sexFilter) + } + setFilteredDogs(filtered) + } - const sires = filtered.filter(d => d.sex === 'male'); - const dams = filtered.filter(d => d.sex === 'female'); + const handleDelete = async () => { + if (!deleteTarget) return + setDeleting(true) + try { + await axios.delete(`/api/dogs/${deleteTarget.id}`) + 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.') + } finally { + setDeleting(false) + } + } - if (loading) return
External sires, dams, and ancestors used in your breeding program
++ {filteredDogs.length} {filteredDogs.length === 1 ? 'dog' : 'dogs'} + {search || sexFilter !== 'all' ? ' matching filters' : ' total'} +