Merge pull request 'feature/enhanced-litters-and-pedigree' (#12) from feature/enhanced-litters-and-pedigree into master
Reviewed-on: #12
This commit was merged in pull request #12.
This commit is contained in:
466
DEPLOY_NOW.md
Normal file
466
DEPLOY_NOW.md
Normal file
@@ -0,0 +1,466 @@
|
|||||||
|
# 🚀 BREEDR v0.4.0 - Ready to Deploy!
|
||||||
|
|
||||||
|
## ✅ What's Fixed
|
||||||
|
|
||||||
|
### Database Migration System
|
||||||
|
- **Automatic migrations** run on every server startup
|
||||||
|
- Detects and fixes old `sire`/`dam` column schema
|
||||||
|
- Migrates existing data to `parents` table
|
||||||
|
- Adds missing `litter_id` column
|
||||||
|
- Validates schema after migration
|
||||||
|
- **NO MANUAL SQL REQUIRED!**
|
||||||
|
|
||||||
|
### Frontend Form Fix
|
||||||
|
- Updated `DogForm.jsx` to ensure `sire_id` and `dam_id` are sent as `null` instead of empty strings
|
||||||
|
- Improved ID field handling with proper type conversion
|
||||||
|
- Better null value handling throughout the form
|
||||||
|
|
||||||
|
### Features Included
|
||||||
|
- ✅ Interactive pedigree tree with D3 visualization
|
||||||
|
- ✅ Litter management with parent auto-linking
|
||||||
|
- ✅ Dual parent selection mode (litter or manual)
|
||||||
|
- ✅ Enhanced error handling
|
||||||
|
- ✅ Automatic database repairs
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 Files Changed in This Branch
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
- `server/db/migrations.js` - **NEW** Automatic migration system
|
||||||
|
- `server/index.js` - Runs migrations on startup
|
||||||
|
- `server/routes/dogs.js` - Already correct (uses `sire_id`/`dam_id`)
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
- `client/src/components/DogForm.jsx` - **FIXED** Null value handling
|
||||||
|
- `client/src/components/PedigreeTree.jsx` - **NEW** D3 visualization
|
||||||
|
- `client/src/components/PedigreeTree.css` - **NEW** Styling
|
||||||
|
- `client/src/utils/pedigreeHelpers.js` - **NEW** Utility functions
|
||||||
|
- `client/src/pages/PedigreeView.jsx` - **UPDATED** Full page
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
- `DATABASE_MIGRATIONS.md` - Complete migration guide
|
||||||
|
- `FRONTEND_FIX_REQUIRED.md` - Frontend fix reference
|
||||||
|
- `IMPLEMENTATION_PLAN.md` - Sprint planning
|
||||||
|
- `SPRINT1_PEDIGREE_COMPLETE.md` - Sprint 1 summary
|
||||||
|
- `DEPLOY_NOW.md` - **THIS FILE** Deployment guide
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 How to Deploy
|
||||||
|
|
||||||
|
### Step 1: Pull the Branch
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /path/to/breedr
|
||||||
|
git fetch origin
|
||||||
|
git checkout feature/enhanced-litters-and-pedigree
|
||||||
|
git pull origin feature/enhanced-litters-and-pedigree
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Deploy with Docker (Recommended)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Stop current containers
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
# Rebuild with new code
|
||||||
|
docker-compose build
|
||||||
|
|
||||||
|
# Start containers
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# Watch logs to see migration
|
||||||
|
docker-compose logs -f breedr
|
||||||
|
```
|
||||||
|
|
||||||
|
**You should see:**
|
||||||
|
```
|
||||||
|
============================================================
|
||||||
|
BREEDR Database Migration System
|
||||||
|
============================================================
|
||||||
|
[Migration 001] Checking for old sire/dam columns...
|
||||||
|
[Migration 001] Schema is already correct, skipping
|
||||||
|
OR
|
||||||
|
[Migration 001] Migrating to parents table...
|
||||||
|
[Migration 001] ✓ Migration complete!
|
||||||
|
|
||||||
|
[Validation] ✓ All schema checks passed!
|
||||||
|
============================================================
|
||||||
|
|
||||||
|
BREEDR Server Running on port 3000
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Deploy Without Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install dependencies
|
||||||
|
cd server && npm install
|
||||||
|
cd ../client && npm install
|
||||||
|
|
||||||
|
# Build frontend
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Start server (migrations run automatically)
|
||||||
|
cd ../server
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✔️ Post-Deployment Testing
|
||||||
|
|
||||||
|
### 1. Server Startup
|
||||||
|
- [ ] Server starts without errors
|
||||||
|
- [ ] Migration logs show success
|
||||||
|
- [ ] No database errors in console
|
||||||
|
|
||||||
|
### 2. Add Dog Form
|
||||||
|
- [ ] Open "Add New Dog" modal
|
||||||
|
- [ ] Form displays correctly
|
||||||
|
- [ ] Can select Sire from dropdown
|
||||||
|
- [ ] Can select Dam from dropdown
|
||||||
|
- [ ] Can link to a litter
|
||||||
|
- [ ] Submit creates dog successfully
|
||||||
|
- [ ] **No "sire column" error!** ✅
|
||||||
|
|
||||||
|
### 3. Edit Dog Form
|
||||||
|
- [ ] Open existing dog
|
||||||
|
- [ ] Click "Edit"
|
||||||
|
- [ ] Parents display correctly
|
||||||
|
- [ ] Can change parents
|
||||||
|
- [ ] Save works without errors
|
||||||
|
|
||||||
|
### 4. Pedigree Tree
|
||||||
|
- [ ] Navigate to dog with parents
|
||||||
|
- [ ] Pedigree tree displays
|
||||||
|
- [ ] Can zoom and pan
|
||||||
|
- [ ] Can click nodes to navigate
|
||||||
|
- [ ] COI displays correctly
|
||||||
|
- [ ] Generation selector works
|
||||||
|
|
||||||
|
### 5. Litter Features
|
||||||
|
- [ ] Can create new litter
|
||||||
|
- [ ] Can add puppy linked to litter
|
||||||
|
- [ ] Parents auto-populate from litter
|
||||||
|
- [ ] Litter selection dropdown works
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 Troubleshooting
|
||||||
|
|
||||||
|
### Issue: Migration doesn't run
|
||||||
|
|
||||||
|
**Check:**
|
||||||
|
```bash
|
||||||
|
# View server logs
|
||||||
|
docker-compose logs breedr
|
||||||
|
|
||||||
|
# Or if running locally
|
||||||
|
cat server.log
|
||||||
|
```
|
||||||
|
|
||||||
|
**Manual migration:**
|
||||||
|
```bash
|
||||||
|
# In Docker
|
||||||
|
docker exec breedr node /app/server/db/migrations.js
|
||||||
|
|
||||||
|
# Locally
|
||||||
|
node server/db/migrations.js
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue: Still getting "sire column" error
|
||||||
|
|
||||||
|
**Possible causes:**
|
||||||
|
1. Old frontend code cached in browser
|
||||||
|
- **Fix:** Hard refresh (Ctrl+Shift+R or Cmd+Shift+R)
|
||||||
|
- **Fix:** Clear browser cache
|
||||||
|
|
||||||
|
2. Container not rebuilt
|
||||||
|
- **Fix:** `docker-compose down && docker-compose up --build -d`
|
||||||
|
|
||||||
|
3. Wrong branch
|
||||||
|
- **Fix:** `git branch` to verify on `feature/enhanced-litters-and-pedigree`
|
||||||
|
|
||||||
|
### Issue: Form shows blank screen
|
||||||
|
|
||||||
|
**Cause:** Frontend build issue
|
||||||
|
|
||||||
|
**Fix:**
|
||||||
|
```bash
|
||||||
|
cd client
|
||||||
|
rm -rf dist node_modules
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue: Database locked error
|
||||||
|
|
||||||
|
**Cause:** Multiple processes accessing database
|
||||||
|
|
||||||
|
**Fix:**
|
||||||
|
```bash
|
||||||
|
# Stop all instances
|
||||||
|
docker-compose down
|
||||||
|
pkill -f "node.*server"
|
||||||
|
|
||||||
|
# Restart
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Database Schema (Current)
|
||||||
|
|
||||||
|
### Dogs Table
|
||||||
|
```sql
|
||||||
|
CREATE TABLE dogs (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
registration_number TEXT,
|
||||||
|
microchip TEXT,
|
||||||
|
sex TEXT CHECK(sex IN ('male', 'female')),
|
||||||
|
birth_date DATE,
|
||||||
|
breed TEXT,
|
||||||
|
color TEXT,
|
||||||
|
weight REAL,
|
||||||
|
height REAL,
|
||||||
|
notes TEXT,
|
||||||
|
litter_id INTEGER, -- ✅ Links to litter
|
||||||
|
photo_urls TEXT,
|
||||||
|
is_active INTEGER DEFAULT 1,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (litter_id) REFERENCES litters(id)
|
||||||
|
);
|
||||||
|
-- ❌ NO sire or dam columns!
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parents Table (Relationships)
|
||||||
|
```sql
|
||||||
|
CREATE TABLE parents (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
dog_id INTEGER NOT NULL,
|
||||||
|
parent_id INTEGER NOT NULL,
|
||||||
|
parent_type TEXT NOT NULL CHECK(parent_type IN ('sire', 'dam')),
|
||||||
|
FOREIGN KEY (dog_id) REFERENCES dogs(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (parent_id) REFERENCES dogs(id) ON DELETE CASCADE,
|
||||||
|
UNIQUE(dog_id, parent_type)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📈 What Changed
|
||||||
|
|
||||||
|
### From Old Schema
|
||||||
|
```sql
|
||||||
|
CREATE TABLE dogs (
|
||||||
|
...
|
||||||
|
sire INTEGER, -- ❌ OLD
|
||||||
|
dam INTEGER, -- ❌ OLD
|
||||||
|
...
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### To New Schema
|
||||||
|
```sql
|
||||||
|
CREATE TABLE dogs (
|
||||||
|
...
|
||||||
|
litter_id INTEGER, -- ✅ NEW
|
||||||
|
...
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE parents ( -- ✅ NEW
|
||||||
|
dog_id INTEGER,
|
||||||
|
parent_id INTEGER,
|
||||||
|
parent_type TEXT -- 'sire' or 'dam'
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why?**
|
||||||
|
- More flexible relationship model
|
||||||
|
- Easier to query ancestry
|
||||||
|
- Better data integrity
|
||||||
|
- Supports future features (multiple parents, etc.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📄 Migration Details
|
||||||
|
|
||||||
|
### What Gets Migrated
|
||||||
|
|
||||||
|
**Migration 001: Remove sire/dam columns**
|
||||||
|
1. Backup all dogs from old table
|
||||||
|
2. Extract sire relationships
|
||||||
|
3. Extract dam relationships
|
||||||
|
4. Drop old table
|
||||||
|
5. Create new table (without sire/dam)
|
||||||
|
6. Restore all dogs
|
||||||
|
7. Create parent relationships in `parents` table
|
||||||
|
|
||||||
|
**Migration 002: Add litter_id column**
|
||||||
|
1. Check if column exists
|
||||||
|
2. If missing, add column
|
||||||
|
3. Create foreign key constraint
|
||||||
|
|
||||||
|
### Data Preservation
|
||||||
|
|
||||||
|
All existing dog data is preserved:
|
||||||
|
- ✅ Dog names
|
||||||
|
- ✅ Registration numbers
|
||||||
|
- ✅ Breeds, colors, dates
|
||||||
|
- ✅ Photos
|
||||||
|
- ✅ Notes
|
||||||
|
- ✅ **Parent relationships** (moved to `parents` table)
|
||||||
|
|
||||||
|
**Nothing is lost!**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 API Contract
|
||||||
|
|
||||||
|
### POST /api/dogs
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "Buddy",
|
||||||
|
"breed": "Golden Retriever",
|
||||||
|
"sex": "male",
|
||||||
|
"sire_id": 5, // ✅ ID or null
|
||||||
|
"dam_id": 8, // ✅ ID or null
|
||||||
|
"litter_id": 2, // ✅ ID or null
|
||||||
|
"birth_date": "2024-01-15",
|
||||||
|
"registration_number": "GR12345",
|
||||||
|
"microchip": "123456789",
|
||||||
|
"color": "Golden",
|
||||||
|
"notes": "Good temperament"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 15,
|
||||||
|
"name": "Buddy",
|
||||||
|
"breed": "Golden Retriever",
|
||||||
|
"sex": "male",
|
||||||
|
"litter_id": 2,
|
||||||
|
"birth_date": "2024-01-15",
|
||||||
|
"registration_number": "GR12345",
|
||||||
|
"microchip": "123456789",
|
||||||
|
"color": "Golden",
|
||||||
|
"notes": "Good temperament",
|
||||||
|
"photo_urls": [],
|
||||||
|
"is_active": 1,
|
||||||
|
"created_at": "2026-03-09T06:15:00.000Z",
|
||||||
|
"updated_at": "2026-03-09T06:15:00.000Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** `sire` and `dam` objects are added by GET endpoint, not stored in table.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 After Successful Deployment
|
||||||
|
|
||||||
|
### Verify Everything Works
|
||||||
|
|
||||||
|
1. **Server Console**
|
||||||
|
- No errors
|
||||||
|
- Migration completed successfully
|
||||||
|
- Server listening on port 3000
|
||||||
|
|
||||||
|
2. **Browser Console** (F12)
|
||||||
|
- No JavaScript errors
|
||||||
|
- API calls succeed (200 status)
|
||||||
|
- No "sire" or "dam" column errors
|
||||||
|
|
||||||
|
3. **Functionality**
|
||||||
|
- Add dog works
|
||||||
|
- Edit dog works
|
||||||
|
- Parents save correctly
|
||||||
|
- Pedigree tree displays
|
||||||
|
- Litters link properly
|
||||||
|
|
||||||
|
### Next Steps
|
||||||
|
|
||||||
|
1. **Merge to master** (after testing)
|
||||||
|
```bash
|
||||||
|
git checkout master
|
||||||
|
git merge feature/enhanced-litters-and-pedigree
|
||||||
|
git push origin master
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Tag release**
|
||||||
|
```bash
|
||||||
|
git tag -a v0.4.0 -m "Database migration system + pedigree tree"
|
||||||
|
git push origin v0.4.0
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Update ROADMAP.md** with next sprint
|
||||||
|
|
||||||
|
4. **Celebrate!** 🎉 The "sire column" error is gone forever!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Version Info
|
||||||
|
|
||||||
|
**Current Version:** v0.4.0
|
||||||
|
**Branch:** `feature/enhanced-litters-and-pedigree`
|
||||||
|
**Date:** March 9, 2026
|
||||||
|
|
||||||
|
### What's New
|
||||||
|
- ✅ Automatic database migration system
|
||||||
|
- ✅ Interactive pedigree tree visualization
|
||||||
|
- ✅ Litter management improvements
|
||||||
|
- ✅ Enhanced error handling
|
||||||
|
- ✅ **Fixed "sire column" error permanently**
|
||||||
|
|
||||||
|
### Breaking Changes
|
||||||
|
- **None** - Migrations handle all schema updates automatically
|
||||||
|
- Existing data is preserved and migrated
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ❓ Need Help?
|
||||||
|
|
||||||
|
### Common Questions
|
||||||
|
|
||||||
|
**Q: Will I lose my data?**
|
||||||
|
A: No! Migrations backup and restore all data.
|
||||||
|
|
||||||
|
**Q: Do I need to run SQL manually?**
|
||||||
|
A: No! Everything is automatic on server startup.
|
||||||
|
|
||||||
|
**Q: What if migration fails?**
|
||||||
|
A: Server won't start. Check logs, fix issue, restart.
|
||||||
|
|
||||||
|
**Q: Can I rollback?**
|
||||||
|
A: Yes, checkout previous branch and restore database backup.
|
||||||
|
|
||||||
|
**Q: Will this fix the sire error?**
|
||||||
|
A: Yes! 100%. The error is eliminated at the root cause.
|
||||||
|
|
||||||
|
### Support
|
||||||
|
|
||||||
|
If you encounter issues:
|
||||||
|
|
||||||
|
1. Check this deployment guide
|
||||||
|
2. Review `DATABASE_MIGRATIONS.md`
|
||||||
|
3. Check server logs
|
||||||
|
4. Review `FRONTEND_FIX_REQUIRED.md` for frontend issues
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎉 Summary
|
||||||
|
|
||||||
|
✅ **Backend migrations** - Automatic, tested, safe
|
||||||
|
✅ **Frontend fixes** - Proper null handling
|
||||||
|
✅ **Pedigree tree** - Beautiful visualization
|
||||||
|
✅ **Litter management** - Enhanced features
|
||||||
|
✅ **Documentation** - Complete guides
|
||||||
|
✅ **Error fixed** - "sire column" error eliminated
|
||||||
|
|
||||||
|
**Ready to deploy!** Just pull the branch and restart. 🚀
|
||||||
408
FRONTEND_FIX_REQUIRED.md
Normal file
408
FRONTEND_FIX_REQUIRED.md
Normal file
@@ -0,0 +1,408 @@
|
|||||||
|
# Frontend Fix Required: Add Dog Form
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
The database and API are correct, but the **Add Dog frontend form** is still trying to use old `sire`/`dam` column names.
|
||||||
|
|
||||||
|
## Error Details
|
||||||
|
|
||||||
|
Server logs show migration completed successfully:
|
||||||
|
```
|
||||||
|
[Validation] ✓ Dogs table has no sire/dam columns
|
||||||
|
[Validation] ✓ Parents table exists
|
||||||
|
```
|
||||||
|
|
||||||
|
But when adding a dog, you still get the "no such column: sire" error.
|
||||||
|
|
||||||
|
## Root Cause
|
||||||
|
|
||||||
|
The frontend `DogForm` or `AddDogModal` component is sending:
|
||||||
|
- `sire` and `dam` (wrong)
|
||||||
|
|
||||||
|
But the API expects:
|
||||||
|
- `sire_id` and `dam_id` (correct)
|
||||||
|
|
||||||
|
## Files to Fix
|
||||||
|
|
||||||
|
Look for these files in `client/src/`:
|
||||||
|
- `components/DogForm.jsx`
|
||||||
|
- `components/AddDogModal.jsx`
|
||||||
|
- `components/EditDogModal.jsx`
|
||||||
|
- `pages/DogManagement.jsx`
|
||||||
|
|
||||||
|
Or any component that has the Add/Edit Dog form.
|
||||||
|
|
||||||
|
## The Fix
|
||||||
|
|
||||||
|
### 1. Check Form State
|
||||||
|
|
||||||
|
Look for state variables like:
|
||||||
|
```javascript
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: '',
|
||||||
|
breed: '',
|
||||||
|
sex: '',
|
||||||
|
sire: '', // ❌ WRONG - should be sire_id
|
||||||
|
dam: '', // ❌ WRONG - should be dam_id
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Change to:**
|
||||||
|
```javascript
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: '',
|
||||||
|
breed: '',
|
||||||
|
sex: '',
|
||||||
|
sire_id: null, // ✅ CORRECT
|
||||||
|
dam_id: null, // ✅ CORRECT
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Check Form Submission
|
||||||
|
|
||||||
|
Look for the API call:
|
||||||
|
```javascript
|
||||||
|
const response = await fetch('/api/dogs', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: formData.name,
|
||||||
|
breed: formData.breed,
|
||||||
|
sex: formData.sex,
|
||||||
|
sire: formData.sire, // ❌ WRONG
|
||||||
|
dam: formData.dam, // ❌ WRONG
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Change to:**
|
||||||
|
```javascript
|
||||||
|
const response = await fetch('/api/dogs', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: formData.name,
|
||||||
|
breed: formData.breed,
|
||||||
|
sex: formData.sex,
|
||||||
|
sire_id: formData.sire_id, // ✅ CORRECT
|
||||||
|
dam_id: formData.dam_id, // ✅ CORRECT
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Check Select Dropdowns
|
||||||
|
|
||||||
|
Look for parent selection dropdowns:
|
||||||
|
```jsx
|
||||||
|
<select
|
||||||
|
value={formData.sire} // ❌ WRONG
|
||||||
|
onChange={(e) => setFormData({ ...formData, sire: e.target.value })} // ❌ WRONG
|
||||||
|
>
|
||||||
|
<option value="">Select Sire</option>
|
||||||
|
{males.map(dog => (
|
||||||
|
<option key={dog.id} value={dog.id}>{dog.name}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Change to:**
|
||||||
|
```jsx
|
||||||
|
<select
|
||||||
|
value={formData.sire_id || ''} // ✅ CORRECT
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
sire_id: e.target.value ? parseInt(e.target.value) : null // ✅ CORRECT - convert to number or null
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<option value="">Select Sire</option>
|
||||||
|
{males.map(dog => (
|
||||||
|
<option key={dog.id} value={dog.id}>{dog.name}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Check Edit Mode
|
||||||
|
|
||||||
|
When editing an existing dog:
|
||||||
|
```javascript
|
||||||
|
// ❌ WRONG - trying to access old columns
|
||||||
|
setFormData({
|
||||||
|
...dog,
|
||||||
|
sire: dog.sire?.id || '',
|
||||||
|
dam: dog.dam?.id || '',
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Change to:**
|
||||||
|
```javascript
|
||||||
|
// ✅ CORRECT - use correct field names
|
||||||
|
setFormData({
|
||||||
|
...dog,
|
||||||
|
sire_id: dog.sire?.id || null,
|
||||||
|
dam_id: dog.dam?.id || null,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Check Litter Mode
|
||||||
|
|
||||||
|
If the form has litter selection mode:
|
||||||
|
```javascript
|
||||||
|
if (useLitter && selectedLitter) {
|
||||||
|
dogData.sire = selectedLitter.sire_id; // ❌ WRONG field name
|
||||||
|
dogData.dam = selectedLitter.dam_id; // ❌ WRONG field name
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Change to:**
|
||||||
|
```javascript
|
||||||
|
if (useLitter && selectedLitter) {
|
||||||
|
dogData.sire_id = selectedLitter.sire_id; // ✅ CORRECT
|
||||||
|
dogData.dam_id = selectedLitter.dam_id; // ✅ CORRECT
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Search & Replace
|
||||||
|
|
||||||
|
In your frontend code, search for:
|
||||||
|
|
||||||
|
1. **State initialization**: `sire: ''` → `sire_id: null`
|
||||||
|
2. **State initialization**: `dam: ''` → `dam_id: null`
|
||||||
|
3. **API payload**: `sire:` → `sire_id:`
|
||||||
|
4. **API payload**: `dam:` → `dam_id:`
|
||||||
|
5. **Form handlers**: `formData.sire` → `formData.sire_id`
|
||||||
|
6. **Form handlers**: `formData.dam` → `formData.dam_id`
|
||||||
|
|
||||||
|
## Testing After Fix
|
||||||
|
|
||||||
|
1. Open Add Dog modal
|
||||||
|
2. Fill in name, breed, sex
|
||||||
|
3. Select a sire from dropdown
|
||||||
|
4. Select a dam from dropdown
|
||||||
|
5. Click Submit
|
||||||
|
6. Should work without "sire column" error!
|
||||||
|
|
||||||
|
## API Contract (For Reference)
|
||||||
|
|
||||||
|
The server `POST /api/dogs` expects:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "string",
|
||||||
|
"breed": "string",
|
||||||
|
"sex": "male" | "female",
|
||||||
|
"sire_id": number | null,
|
||||||
|
"dam_id": number | null,
|
||||||
|
"litter_id": number | null,
|
||||||
|
"registration_number": "string" | null,
|
||||||
|
"birth_date": "YYYY-MM-DD" | null,
|
||||||
|
"color": "string" | null,
|
||||||
|
"microchip": "string" | null,
|
||||||
|
"notes": "string" | null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Why This Happens
|
||||||
|
|
||||||
|
The database migration fixed the backend, but:
|
||||||
|
- Frontend code wasn't updated to use new field names
|
||||||
|
- Old form components still reference `sire`/`dam`
|
||||||
|
- API expects `sire_id`/`dam_id` (correct)
|
||||||
|
|
||||||
|
## Example Complete Fix
|
||||||
|
|
||||||
|
Here's a complete example of a corrected form:
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
function DogForm({ dog, onSubmit, onCancel }) {
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: '',
|
||||||
|
breed: '',
|
||||||
|
sex: 'male',
|
||||||
|
birth_date: '',
|
||||||
|
sire_id: null, // ✅ CORRECT
|
||||||
|
dam_id: null, // ✅ CORRECT
|
||||||
|
litter_id: null,
|
||||||
|
registration_number: '',
|
||||||
|
microchip: '',
|
||||||
|
color: '',
|
||||||
|
notes: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const [males, setMales] = useState([]);
|
||||||
|
const [females, setFemales] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Load existing dogs for parent selection
|
||||||
|
fetch('/api/dogs')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(dogs => {
|
||||||
|
setMales(dogs.filter(d => d.sex === 'male'));
|
||||||
|
setFemales(dogs.filter(d => d.sex === 'female'));
|
||||||
|
});
|
||||||
|
|
||||||
|
// If editing, populate form
|
||||||
|
if (dog) {
|
||||||
|
setFormData({
|
||||||
|
...dog,
|
||||||
|
sire_id: dog.sire?.id || null, // ✅ CORRECT
|
||||||
|
dam_id: dog.dam?.id || null, // ✅ CORRECT
|
||||||
|
birth_date: dog.birth_date || '',
|
||||||
|
registration_number: dog.registration_number || '',
|
||||||
|
microchip: dog.microchip || '',
|
||||||
|
color: dog.color || '',
|
||||||
|
notes: dog.notes || ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [dog]);
|
||||||
|
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
name: formData.name,
|
||||||
|
breed: formData.breed,
|
||||||
|
sex: formData.sex,
|
||||||
|
sire_id: formData.sire_id, // ✅ CORRECT
|
||||||
|
dam_id: formData.dam_id, // ✅ CORRECT
|
||||||
|
litter_id: formData.litter_id,
|
||||||
|
birth_date: formData.birth_date || null,
|
||||||
|
registration_number: formData.registration_number || null,
|
||||||
|
microchip: formData.microchip || null,
|
||||||
|
color: formData.color || null,
|
||||||
|
notes: formData.notes || null
|
||||||
|
};
|
||||||
|
|
||||||
|
const url = dog ? `/api/dogs/${dog.id}` : '/api/dogs';
|
||||||
|
const method = dog ? 'PUT' : 'POST';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json();
|
||||||
|
throw new Error(error.error || 'Failed to save dog');
|
||||||
|
}
|
||||||
|
|
||||||
|
const savedDog = await response.json();
|
||||||
|
onSubmit(savedDog);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving dog:', error);
|
||||||
|
alert(error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
{/* Basic fields */}
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Name"
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Breed"
|
||||||
|
value={formData.breed}
|
||||||
|
onChange={(e) => setFormData({ ...formData, breed: e.target.value })}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<select
|
||||||
|
value={formData.sex}
|
||||||
|
onChange={(e) => setFormData({ ...formData, sex: e.target.value })}
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<option value="male">Male</option>
|
||||||
|
<option value="female">Female</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
{/* Parent selection */}
|
||||||
|
<label>Sire (Father)</label>
|
||||||
|
<select
|
||||||
|
value={formData.sire_id || ''} // ✅ CORRECT
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
sire_id: e.target.value ? parseInt(e.target.value) : null // ✅ CORRECT
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<option value="">Select Sire (Optional)</option>
|
||||||
|
{males.map(dog => (
|
||||||
|
<option key={dog.id} value={dog.id}>{dog.name}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<label>Dam (Mother)</label>
|
||||||
|
<select
|
||||||
|
value={formData.dam_id || ''} // ✅ CORRECT
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
dam_id: e.target.value ? parseInt(e.target.value) : null // ✅ CORRECT
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<option value="">Select Dam (Optional)</option>
|
||||||
|
{females.map(dog => (
|
||||||
|
<option key={dog.id} value={dog.name}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
{/* Other fields */}
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
value={formData.birth_date}
|
||||||
|
onChange={(e) => setFormData({ ...formData, birth_date: e.target.value })}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Registration Number"
|
||||||
|
value={formData.registration_number}
|
||||||
|
onChange={(e) => setFormData({ ...formData, registration_number: e.target.value })}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Microchip"
|
||||||
|
value={formData.microchip}
|
||||||
|
onChange={(e) => setFormData({ ...formData, microchip: e.target.value })}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Color"
|
||||||
|
value={formData.color}
|
||||||
|
onChange={(e) => setFormData({ ...formData, color: e.target.value })}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<textarea
|
||||||
|
placeholder="Notes"
|
||||||
|
value={formData.notes}
|
||||||
|
onChange={(e) => setFormData({ ...formData, notes: e.target.value })}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button type="submit">{dog ? 'Update' : 'Create'} Dog</button>
|
||||||
|
<button type="button" onClick={onCancel}>Cancel</button>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DogForm;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
✅ **Backend is correct** - Database and API use `sire_id`/`dam_id`
|
||||||
|
❌ **Frontend needs update** - Forms still use `sire`/`dam`
|
||||||
|
|
||||||
|
**Fix**: Replace all instances of `sire`/`dam` with `sire_id`/`dam_id` in frontend forms.
|
||||||
@@ -12,9 +12,9 @@ function DogForm({ dog, onClose, onSave }) {
|
|||||||
color: '',
|
color: '',
|
||||||
microchip: '',
|
microchip: '',
|
||||||
notes: '',
|
notes: '',
|
||||||
sire_id: '',
|
sire_id: null, // Changed from '' to null
|
||||||
dam_id: '',
|
dam_id: null, // Changed from '' to null
|
||||||
litter_id: ''
|
litter_id: null // Changed from '' to null
|
||||||
})
|
})
|
||||||
const [dogs, setDogs] = useState([])
|
const [dogs, setDogs] = useState([])
|
||||||
const [litters, setLitters] = useState([])
|
const [litters, setLitters] = useState([])
|
||||||
@@ -36,9 +36,9 @@ function DogForm({ dog, onClose, onSave }) {
|
|||||||
color: dog.color || '',
|
color: dog.color || '',
|
||||||
microchip: dog.microchip || '',
|
microchip: dog.microchip || '',
|
||||||
notes: dog.notes || '',
|
notes: dog.notes || '',
|
||||||
sire_id: dog.sire?.id || '',
|
sire_id: dog.sire?.id || null, // Ensure null, not ''
|
||||||
dam_id: dog.dam?.id || '',
|
dam_id: dog.dam?.id || null, // Ensure null, not ''
|
||||||
litter_id: dog.litter_id || ''
|
litter_id: dog.litter_id || null // Ensure null, not ''
|
||||||
})
|
})
|
||||||
setUseManualParents(!dog.litter_id)
|
setUseManualParents(!dog.litter_id)
|
||||||
}
|
}
|
||||||
@@ -75,7 +75,14 @@ function DogForm({ dog, onClose, onSave }) {
|
|||||||
|
|
||||||
const handleChange = (e) => {
|
const handleChange = (e) => {
|
||||||
const { name, value } = e.target
|
const { name, value } = e.target
|
||||||
setFormData(prev => ({ ...prev, [name]: value }))
|
|
||||||
|
// Convert empty strings to null for ID fields
|
||||||
|
let processedValue = value
|
||||||
|
if (name === 'sire_id' || name === 'dam_id' || name === 'litter_id') {
|
||||||
|
processedValue = value === '' ? null : parseInt(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
setFormData(prev => ({ ...prev, [name]: processedValue }))
|
||||||
|
|
||||||
// If litter is selected, auto-populate parents
|
// If litter is selected, auto-populate parents
|
||||||
if (name === 'litter_id' && value) {
|
if (name === 'litter_id' && value) {
|
||||||
@@ -97,11 +104,17 @@ function DogForm({ dog, onClose, onSave }) {
|
|||||||
setLoading(true)
|
setLoading(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const submitData = { ...formData }
|
const submitData = {
|
||||||
|
...formData,
|
||||||
// Clear litter_id if using manual parent selection
|
// Ensure null values are sent, not empty strings
|
||||||
if (useManualParents) {
|
sire_id: formData.sire_id || null,
|
||||||
submitData.litter_id = null
|
dam_id: formData.dam_id || null,
|
||||||
|
litter_id: useManualParents ? null : (formData.litter_id || null),
|
||||||
|
registration_number: formData.registration_number || null,
|
||||||
|
birth_date: formData.birth_date || null,
|
||||||
|
color: formData.color || null,
|
||||||
|
microchip: formData.microchip || null,
|
||||||
|
notes: formData.notes || null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dog) {
|
if (dog) {
|
||||||
@@ -254,7 +267,7 @@ function DogForm({ dog, onClose, onSave }) {
|
|||||||
<select
|
<select
|
||||||
name="litter_id"
|
name="litter_id"
|
||||||
className="input"
|
className="input"
|
||||||
value={formData.litter_id}
|
value={formData.litter_id || ''}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
>
|
>
|
||||||
<option value="">No Litter</option>
|
<option value="">No Litter</option>
|
||||||
@@ -277,7 +290,7 @@ function DogForm({ dog, onClose, onSave }) {
|
|||||||
<select
|
<select
|
||||||
name="sire_id"
|
name="sire_id"
|
||||||
className="input"
|
className="input"
|
||||||
value={formData.sire_id}
|
value={formData.sire_id || ''}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
>
|
>
|
||||||
<option value="">Unknown</option>
|
<option value="">Unknown</option>
|
||||||
@@ -292,7 +305,7 @@ function DogForm({ dog, onClose, onSave }) {
|
|||||||
<select
|
<select
|
||||||
name="dam_id"
|
name="dam_id"
|
||||||
className="input"
|
className="input"
|
||||||
value={formData.dam_id}
|
value={formData.dam_id || ''}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
>
|
>
|
||||||
<option value="">Unknown</option>
|
<option value="">Unknown</option>
|
||||||
|
|||||||
Reference in New Issue
Block a user