296 lines
11 KiB
TypeScript
296 lines
11 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
||
import { Plant } from '../types';
|
||
import { X } from 'lucide-react';
|
||
import { apiService } from '../services/api';
|
||
import ImageUpload from './ImageUpload';
|
||
|
||
interface PlantFormProps {
|
||
plant?: Plant | null;
|
||
onSave: (plant: Omit<Plant, 'id' | 'createdAt' | 'updatedAt'>) => void;
|
||
onCancel: () => void;
|
||
}
|
||
|
||
const PlantForm: React.FC<PlantFormProps> = ({ plant, onSave, onCancel }) => {
|
||
const [formData, setFormData] = useState({
|
||
type: '',
|
||
variety: '',
|
||
purchaseLocation: '',
|
||
seedlingAge: '',
|
||
seedlingHeight: '',
|
||
plantingDate: '',
|
||
healthStatus: 'good' as Plant['healthStatus'],
|
||
currentHeight: '',
|
||
photoUrl: '',
|
||
notes: ''
|
||
});
|
||
const [uploading, setUploading] = useState(false);
|
||
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
||
|
||
useEffect(() => {
|
||
if (plant) {
|
||
setFormData({
|
||
type: plant.type,
|
||
variety: plant.variety,
|
||
purchaseLocation: plant.purchaseLocation,
|
||
seedlingAge: plant.seedlingAge.toString(),
|
||
seedlingHeight: plant.seedlingHeight.toString(),
|
||
plantingDate: plant.plantingDate,
|
||
healthStatus: plant.healthStatus,
|
||
currentHeight: plant.currentHeight?.toString() || '',
|
||
photoUrl: plant.photoUrl || '',
|
||
notes: plant.notes
|
||
});
|
||
}
|
||
}, [plant]);
|
||
|
||
const handleSubmit = async (e: React.FormEvent) => {
|
||
e.preventDefault();
|
||
|
||
let finalPhotoUrl = formData.photoUrl;
|
||
|
||
// Upload image if a file is selected
|
||
if (selectedFile) {
|
||
try {
|
||
setUploading(true);
|
||
const result = await apiService.uploadPhoto(selectedFile);
|
||
finalPhotoUrl = result.photoUrl;
|
||
} catch (error) {
|
||
console.error('Error uploading photo:', error);
|
||
alert('Failed to upload photo. Please try again.');
|
||
setUploading(false);
|
||
return;
|
||
} finally {
|
||
setUploading(false);
|
||
}
|
||
}
|
||
|
||
onSave({
|
||
...formData,
|
||
seedlingAge: parseInt(formData.seedlingAge),
|
||
seedlingHeight: parseFloat(formData.seedlingHeight),
|
||
currentHeight: formData.currentHeight ? parseFloat(formData.currentHeight) : undefined,
|
||
photoUrl: finalPhotoUrl || undefined
|
||
});
|
||
};
|
||
|
||
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
|
||
const { name, value } = e.target;
|
||
setFormData(prev => ({ ...prev, [name]: value }));
|
||
};
|
||
|
||
const handleFileSelect = (file: File) => {
|
||
setSelectedFile(file);
|
||
};
|
||
|
||
const handleImageChange = (imageUrl: string) => {
|
||
setFormData(prev => ({ ...prev, photoUrl: imageUrl }));
|
||
};
|
||
|
||
return (
|
||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
|
||
<div className="bg-white rounded-lg shadow-xl max-w-4xl w-full max-h-[85vh] overflow-y-auto">
|
||
<div className="flex justify-between items-center p-6 border-b border-gray-200">
|
||
<h3 className="text-lg font-semibold text-gray-900">
|
||
{plant ? 'Редактирование данных' : 'Новое растение'}
|
||
</h3>
|
||
<button
|
||
onClick={onCancel}
|
||
className="text-gray-400 hover:text-gray-600 transition-colors"
|
||
>
|
||
<X className="h-5 w-5" />
|
||
</button>
|
||
</div>
|
||
|
||
<form onSubmit={handleSubmit} className="p-6 space-y-4">
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
<div>
|
||
<label htmlFor="type" className="block text-sm font-medium text-gray-700 mb-1">
|
||
Тип растения *
|
||
</label>
|
||
<select
|
||
id="type"
|
||
name="type"
|
||
value={formData.type}
|
||
onChange={handleChange}
|
||
required
|
||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent"
|
||
>
|
||
<option value="">Выбор типа</option>
|
||
<option value="tree">Дерево</option>
|
||
<option value="shrub">Кустарник</option>
|
||
<option value="herb">Зелень</option>
|
||
<option value="vegetable">Овощ</option>
|
||
<option value="flower">Цветы</option>
|
||
<option value="grass">Трава</option>
|
||
<option value="other">Другое</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div>
|
||
<label htmlFor="variety" className="block text-sm font-medium text-gray-700 mb-1">
|
||
Вид/Сорт *
|
||
</label>
|
||
<input
|
||
type="text"
|
||
id="variety"
|
||
name="variety"
|
||
value={formData.variety}
|
||
onChange={handleChange}
|
||
placeholder="e.g., Apple - Honeycrisp"
|
||
required
|
||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
<div>
|
||
<label htmlFor="purchaseLocation" className="block text-sm font-medium text-gray-700 mb-1">
|
||
Место покупки *
|
||
</label>
|
||
<input
|
||
type="text"
|
||
id="purchaseLocation"
|
||
name="purchaseLocation"
|
||
value={formData.purchaseLocation}
|
||
onChange={handleChange}
|
||
placeholder="e.g., Local Nursery, Garden Center"
|
||
required
|
||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label htmlFor="plantingDate" className="block text-sm font-medium text-gray-700 mb-1">
|
||
Дата посадки *
|
||
</label>
|
||
<input
|
||
type="date"
|
||
id="plantingDate"
|
||
name="plantingDate"
|
||
value={formData.plantingDate}
|
||
onChange={handleChange}
|
||
required
|
||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||
<div>
|
||
<label htmlFor="seedlingAge" className="block text-sm font-medium text-gray-700 mb-1">
|
||
Возраст саженца (м-цы) *
|
||
</label>
|
||
<input
|
||
type="number"
|
||
id="seedlingAge"
|
||
name="seedlingAge"
|
||
value={formData.seedlingAge}
|
||
onChange={handleChange}
|
||
min="0"
|
||
required
|
||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label htmlFor="seedlingHeight" className="block text-sm font-medium text-gray-700 mb-1">
|
||
Высота саженца (м) *
|
||
</label>
|
||
<input
|
||
type="number"
|
||
id="seedlingHeight"
|
||
name="seedlingHeight"
|
||
value={formData.seedlingHeight}
|
||
onChange={handleChange}
|
||
step="0.1"
|
||
min="0"
|
||
required
|
||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label htmlFor="currentHeight" className="block text-sm font-medium text-gray-700 mb-1">
|
||
Текущая высота (м)
|
||
</label>
|
||
<input
|
||
type="number"
|
||
id="currentHeight"
|
||
name="currentHeight"
|
||
value={formData.currentHeight}
|
||
onChange={handleChange}
|
||
step="0.1"
|
||
min="0"
|
||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label htmlFor="healthStatus" className="block text-sm font-medium text-gray-700 mb-1">
|
||
Состояние
|
||
</label>
|
||
<select
|
||
id="healthStatus"
|
||
name="healthStatus"
|
||
value={formData.healthStatus}
|
||
onChange={handleChange}
|
||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent"
|
||
>
|
||
<option value="good">Хорошее</option>
|
||
<option value="needs-attention">Требует внимания</option>
|
||
<option value="dead">Погибло</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||
Plant Photo
|
||
</label>
|
||
<ImageUpload
|
||
currentImageUrl={formData.photoUrl}
|
||
onImageChange={handleImageChange}
|
||
onFileSelect={handleFileSelect}
|
||
uploading={uploading}
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label htmlFor="type" className="block text-sm font-medium text-gray-700 mb-1">
|
||
Заметки
|
||
</label>
|
||
<textarea
|
||
id="notes"
|
||
name="notes"
|
||
value={formData.notes}
|
||
onChange={handleChange}
|
||
placeholder="Специальные заметки об этом растении..."
|
||
rows={3}
|
||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent resize-none"
|
||
/>
|
||
</div>
|
||
|
||
<div className="flex justify-end space-x-3 pt-4 border-t border-gray-200">
|
||
<button
|
||
type="button"
|
||
onClick={onCancel}
|
||
className="px-4 py-2 text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-md transition-colors"
|
||
>
|
||
Отмена
|
||
</button>
|
||
<button
|
||
type="submit"
|
||
disabled={uploading}
|
||
className="px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||
>
|
||
{uploading ? 'Загружается...' : (plant ? 'Изменить' : 'Добавить')}
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default PlantForm; |