409 lines
9.9 KiB
Markdown
409 lines
9.9 KiB
Markdown
# 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.
|