203 lines
5.7 KiB
React
203 lines
5.7 KiB
React
|
|
import { useState, useEffect } from 'react';
|
|||
|
|
import ModalErrorDisplay from '../common/ModalErrorDisplay';
|
|||
|
|
import './PlannableForm.css';
|
|||
|
|
|
|||
|
|
const PlannableForm = ({ item, tripId, calendarSlots, onSubmit, onCancel }) => {
|
|||
|
|
const [formData, setFormData] = useState({
|
|||
|
|
name: '',
|
|||
|
|
type: 'attraction',
|
|||
|
|
address: '',
|
|||
|
|
notes: '',
|
|||
|
|
calendar_slot_id: null
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const [errors, setErrors] = useState({});
|
|||
|
|
const [submitting, setSubmitting] = useState(false);
|
|||
|
|
const [submitError, setSubmitError] = useState(null);
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
if (item) {
|
|||
|
|
setFormData({
|
|||
|
|
name: item.name || '',
|
|||
|
|
type: item.type || 'attraction',
|
|||
|
|
address: item.address || '',
|
|||
|
|
notes: item.notes || '',
|
|||
|
|
calendar_slot_id: item.calendar_slot_id || null
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}, [item]);
|
|||
|
|
|
|||
|
|
const handleChange = (e) => {
|
|||
|
|
const { name, value } = e.target;
|
|||
|
|
setFormData(prev => ({
|
|||
|
|
...prev,
|
|||
|
|
[name]: value === '' ? null : value
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
// Clear error for this field
|
|||
|
|
if (errors[name]) {
|
|||
|
|
setErrors(prev => ({
|
|||
|
|
...prev,
|
|||
|
|
[name]: null
|
|||
|
|
}));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Clear submit error when user starts typing
|
|||
|
|
if (submitError) {
|
|||
|
|
setSubmitError(null);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const validate = () => {
|
|||
|
|
const newErrors = {};
|
|||
|
|
|
|||
|
|
if (!formData.name || formData.name.trim() === '') {
|
|||
|
|
newErrors.name = 'Name is required';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!formData.type) {
|
|||
|
|
newErrors.type = 'Type is required';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return newErrors;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleSubmit = async (e) => {
|
|||
|
|
e.preventDefault();
|
|||
|
|
|
|||
|
|
const newErrors = validate();
|
|||
|
|
if (Object.keys(newErrors).length > 0) {
|
|||
|
|
setErrors(newErrors);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
setSubmitting(true);
|
|||
|
|
setSubmitError(null);
|
|||
|
|
try {
|
|||
|
|
await onSubmit(formData);
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error('Form submission error:', err);
|
|||
|
|
setSubmitError(err.message || 'Failed to save item. Please try again.');
|
|||
|
|
} finally {
|
|||
|
|
setSubmitting(false);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="plannable-form-overlay">
|
|||
|
|
<div className="plannable-form-modal">
|
|||
|
|
<div className="form-header">
|
|||
|
|
<h2>{item ? 'Edit Item' : 'Add New Item'}</h2>
|
|||
|
|
<button className="btn-close" onClick={onCancel}>×</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<form onSubmit={handleSubmit} className="plannable-form">
|
|||
|
|
<ModalErrorDisplay
|
|||
|
|
error={submitError}
|
|||
|
|
onDismiss={() => setSubmitError(null)}
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
<div className="form-group">
|
|||
|
|
<label>Name *</label>
|
|||
|
|
<input
|
|||
|
|
type="text"
|
|||
|
|
name="name"
|
|||
|
|
value={formData.name}
|
|||
|
|
onChange={handleChange}
|
|||
|
|
className={`form-control ${errors.name ? 'error' : ''}`}
|
|||
|
|
placeholder="Enter item name"
|
|||
|
|
autoFocus
|
|||
|
|
/>
|
|||
|
|
{errors.name && <span className="error-message">{errors.name}</span>}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="form-group">
|
|||
|
|
<label>Type *</label>
|
|||
|
|
<select
|
|||
|
|
name="type"
|
|||
|
|
value={formData.type}
|
|||
|
|
onChange={handleChange}
|
|||
|
|
className={`form-control ${errors.type ? 'error' : ''}`}
|
|||
|
|
>
|
|||
|
|
<option value="hotel">🏨 Hotel</option>
|
|||
|
|
<option value="restaurant">🍽️ Restaurant</option>
|
|||
|
|
<option value="attraction">🎯 Attraction</option>
|
|||
|
|
<option value="transport">✈️ Transport</option>
|
|||
|
|
<option value="activity">🎭 Activity</option>
|
|||
|
|
</select>
|
|||
|
|
{errors.type && <span className="error-message">{errors.type}</span>}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="form-group">
|
|||
|
|
<label>Address</label>
|
|||
|
|
<input
|
|||
|
|
type="text"
|
|||
|
|
name="address"
|
|||
|
|
value={formData.address}
|
|||
|
|
onChange={handleChange}
|
|||
|
|
className="form-control"
|
|||
|
|
placeholder="Enter address (optional)"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="form-group">
|
|||
|
|
<label>Assign to Day</label>
|
|||
|
|
<select
|
|||
|
|
name="calendar_slot_id"
|
|||
|
|
value={formData.calendar_slot_id || ''}
|
|||
|
|
onChange={handleChange}
|
|||
|
|
className="form-control"
|
|||
|
|
>
|
|||
|
|
<option value="">Unplanned</option>
|
|||
|
|
{calendarSlots.map(slot => {
|
|||
|
|
const slotDate = new Date(slot.slot_date);
|
|||
|
|
const dateStr = slotDate.toLocaleDateString('en-US', {
|
|||
|
|
weekday: 'short',
|
|||
|
|
month: 'short',
|
|||
|
|
day: 'numeric'
|
|||
|
|
});
|
|||
|
|
return (
|
|||
|
|
<option key={slot.id} value={slot.id}>
|
|||
|
|
{slot.name} - {dateStr}
|
|||
|
|
</option>
|
|||
|
|
);
|
|||
|
|
})}
|
|||
|
|
</select>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="form-group">
|
|||
|
|
<label>Notes</label>
|
|||
|
|
<textarea
|
|||
|
|
name="notes"
|
|||
|
|
value={formData.notes}
|
|||
|
|
onChange={handleChange}
|
|||
|
|
className="form-control"
|
|||
|
|
rows="3"
|
|||
|
|
placeholder="Add any additional notes (optional)"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="form-actions">
|
|||
|
|
<button
|
|||
|
|
type="button"
|
|||
|
|
className="btn-secondary"
|
|||
|
|
onClick={onCancel}
|
|||
|
|
disabled={submitting}
|
|||
|
|
>
|
|||
|
|
Cancel
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
type="submit"
|
|||
|
|
className="btn-primary"
|
|||
|
|
disabled={submitting}
|
|||
|
|
>
|
|||
|
|
{submitting ? 'Saving...' : (item ? 'Update' : 'Add')} Item
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</form>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export default PlannableForm;
|