diff --git a/client/src/pages/BreedingCalendar.jsx b/client/src/pages/BreedingCalendar.jsx
index e413033..1846a35 100644
--- a/client/src/pages/BreedingCalendar.jsx
+++ b/client/src/pages/BreedingCalendar.jsx
@@ -14,6 +14,21 @@ const addDays = (dateStr, n) => {
const fmt = str => str ? new Date(str + 'T00:00:00').toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) : '–'
const today = toISO(new Date())
+// ─── Canine gestation constants (days from breeding date) ─────────────────────
+const GESTATION_EARLIEST = 58
+const GESTATION_EXPECTED = 63
+const GESTATION_LATEST = 65
+
+/** Returns { earliest, expected, latest } ISO date strings, or null if no breeding_date */
+function getWhelpDates(cycle) {
+ if (!cycle?.breeding_date) return null
+ return {
+ earliest: addDays(cycle.breeding_date, GESTATION_EARLIEST),
+ expected: addDays(cycle.breeding_date, GESTATION_EXPECTED),
+ latest: addDays(cycle.breeding_date, GESTATION_LATEST),
+ }
+}
+
// ─── Cycle window classifier ─────────────────────────────────────────────────
function getWindowForDate(cycle, dateStr) {
if (!cycle?.start_date) return null
@@ -34,6 +49,14 @@ const WINDOW_STYLES = {
diestrus: { bg: 'rgba(148,163,184,0.12)', border: '#64748b', label: 'Diestrus', dot: '#64748b' },
}
+// Whelp window style (used in legend + calendar marker)
+const WHELP_STYLE = {
+ bg: 'rgba(99,102,241,0.15)',
+ border: '#6366f1',
+ label: 'Projected Whelp',
+ dot: '#6366f1',
+}
+
// ─── Start Heat Cycle Modal ───────────────────────────────────────────────────
function StartCycleModal({ females, onClose, onSaved }) {
const [dogId, setDogId] = useState('')
@@ -150,6 +173,9 @@ function CycleDetailModal({ cycle, onClose, onDeleted, onRecordLitter }) {
const whelp = suggestions?.whelping
const hasBreedingDate = !!(breedingDate && breedingDate === cycle.breeding_date)
+ // Client-side projected whelp dates (immediate, before API suggestions load)
+ const projectedWhelp = getWhelpDates({ breeding_date: breedingDate })
+
return (
e.target === e.currentTarget && onClose()}>
@@ -220,9 +246,30 @@ function CycleDetailModal({ cycle, onClose, onDeleted, onRecordLitter }) {
{savingBreed ? 'Saving…' : 'Save'}
+ {/* Live projected whelp preview — shown as soon as a breeding date is entered */}
+ {projectedWhelp && (
+
+
+ Projected Whelp:
+
+ {fmt(projectedWhelp.earliest)} – {fmt(projectedWhelp.latest)}
+ (expected {fmt(projectedWhelp.expected)})
+
+
+ )}
- {/* Whelping estimate */}
+ {/* Whelping estimate (from API suggestions) */}
{whelp && (
@@ -342,6 +389,12 @@ export default function BreedingCalendar() {
}
}, [navigate])
+ // ── Navigate to a specific year/month ──
+ function goToMonth(y, m) {
+ setYear(y)
+ setMonth(m)
+ }
+
// ── Build calendar grid ──
const firstDay = new Date(year, month, 1)
const lastDay = new Date(year, month + 1, 0)
@@ -370,6 +423,23 @@ export default function BreedingCalendar() {
})
}
+ /** Returns array of cycles whose projected whelp expected date is this dateStr */
+ function whelpingCyclesForDate(dateStr) {
+ return cycles.filter(c => {
+ const wd = getWhelpDates(c)
+ if (!wd) return false
+ return dateStr >= wd.earliest && dateStr <= wd.latest
+ })
+ }
+
+ /** Returns true if this dateStr is the exact expected whelp date for any cycle */
+ function isExpectedWhelpDate(dateStr) {
+ return cycles.some(c => {
+ const wd = getWhelpDates(c)
+ return wd?.expected === dateStr
+ })
+ }
+
function handleDayClick(dateStr, dayCycles) {
setSelectedDay(dateStr)
if (dayCycles.length === 1) {
@@ -389,6 +459,15 @@ export default function BreedingCalendar() {
return s <= mEnd && end >= mStart
})
+ // Cycles that have a whelp window overlapping current month view
+ const whelpingThisMonth = cycles.filter(c => {
+ const wd = getWhelpDates(c)
+ if (!wd) return false
+ const mStart = toISO(new Date(year, month, 1))
+ const mEnd = toISO(new Date(year, month + 1, 0))
+ return wd.earliest <= mEnd && wd.latest >= mStart
+ })
+
return (
{/* Header */}
@@ -399,7 +478,7 @@ export default function BreedingCalendar() {
Heat Cycle Calendar
-
Track heat cycles and optimal breeding windows
+
Track heat cycles, optimal breeding windows, and projected whelping dates