diff --git a/client/src/pages/BreedingCalendar.jsx b/client/src/pages/BreedingCalendar.jsx index 17c08bb..ccd33c0 100644 --- a/client/src/pages/BreedingCalendar.jsx +++ b/client/src/pages/BreedingCalendar.jsx @@ -1,67 +1,529 @@ -import { useEffect, useState } from 'react' -import { Heart } from 'lucide-react' -import axios from 'axios' +import { useEffect, useState, useCallback } from 'react' +import { + Heart, ChevronLeft, ChevronRight, Plus, X, + CalendarDays, FlaskConical, Baby, AlertCircle, CheckCircle2 +} from 'lucide-react' -function BreedingCalendar() { - const [heatCycles, setHeatCycles] = useState([]) - const [loading, setLoading] = useState(true) +// ─── Date helpers ──────────────────────────────────────────────────────────── +const toISO = d => d.toISOString().split('T')[0] +const addDays = (dateStr, n) => { + const d = new Date(dateStr); d.setDate(d.getDate() + n); return toISO(d) +} +const fmt = str => str ? new Date(str + 'T00:00:00').toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) : '—' +const today = toISO(new Date()) - useEffect(() => { - fetchHeatCycles() - }, []) +// ─── Cycle window classifier ───────────────────────────────────────────────── +function getWindowForDate(cycle, dateStr) { + if (!cycle?.start_date) return null + const start = new Date(cycle.start_date + 'T00:00:00') + const check = new Date(dateStr + 'T00:00:00') + const day = Math.round((check - start) / 86400000) + if (day < 0 || day > 28) return null + if (day <= 8) return 'proestrus' + if (day <= 15) return 'optimal' + if (day <= 21) return 'late' + return 'diestrus' +} - const fetchHeatCycles = async () => { +const WINDOW_STYLES = { + proestrus: { bg: 'rgba(244,114,182,0.18)', border: '#f472b6', label: 'Proestrus', dot: '#f472b6' }, + optimal: { bg: 'rgba(16,185,129,0.22)', border: '#10b981', label: 'Optimal Breeding', dot: '#10b981' }, + late: { bg: 'rgba(245,158,11,0.18)', border: '#f59e0b', label: 'Late Estrus', dot: '#f59e0b' }, + diestrus: { bg: 'rgba(148,163,184,0.12)', border: '#64748b', label: 'Diestrus', dot: '#64748b' }, +} + +// ─── Start Heat Cycle Modal ─────────────────────────────────────────────────── +function StartCycleModal({ females, onClose, onSaved }) { + const [dogId, setDogId] = useState('') + const [startDate, setStartDate] = useState(today) + const [notes, setNotes] = useState('') + const [saving, setSaving] = useState(false) + const [error, setError] = useState(null) + + async function handleSubmit(e) { + e.preventDefault() + if (!dogId || !startDate) return + setSaving(true); setError(null) try { - const res = await axios.get('/api/breeding/heat-cycles/active') - setHeatCycles(res.data) - setLoading(false) - } catch (error) { - console.error('Error fetching heat cycles:', error) - setLoading(false) + const res = await fetch('/api/breeding/heat-cycles', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ dog_id: parseInt(dogId), start_date: startDate, notes: notes || null }) + }) + if (!res.ok) { const e = await res.json(); throw new Error(e.error || 'Failed to save') } + onSaved() + } catch (err) { + setError(err.message) + setSaving(false) } } - if (loading) { - return
Loading breeding calendar...
- } - return ( -
-

Breeding Calendar

- -
-

Active Heat Cycles

- {heatCycles.length === 0 ? ( -
- -

No active heat cycles

+
e.target === e.currentTarget && onClose()}> +
+
+
+ +

Start Heat Cycle

- ) : ( -
- {heatCycles.map(cycle => ( -
-

{cycle.dog_name}

-

- Started: {new Date(cycle.start_date).toLocaleDateString()} -

- {cycle.registration_number && ( -

- Reg: {cycle.registration_number} -

- )} -
- ))} + +
+
+
+ {error &&
{error}
} +
+ + + {females.length === 0 &&

No female dogs registered.

} +
+
+ + setStartDate(e.target.value)} required /> +
+
+ +