Add equipment management to events
- Add equipmentProvided and requiredItems fields to Event model - Update event creation form with equipment section - Allow organizers to specify if equipment is provided or BYO - Display required items list in event details - Update database schema and run migration
This commit is contained in:
@@ -25,6 +25,8 @@ interface EventFormData {
|
||||
scheduledDate: string;
|
||||
scheduledTime: string;
|
||||
duration: number;
|
||||
equipmentProvided: boolean;
|
||||
requiredItems: string[];
|
||||
}
|
||||
|
||||
interface EventFormProps {
|
||||
@@ -44,8 +46,12 @@ export function EventForm({ onSubmit, onCancel, initialData, isSubmitting = fals
|
||||
scheduledDate: initialData?.scheduledDate || '',
|
||||
scheduledTime: initialData?.scheduledTime || '',
|
||||
duration: initialData?.duration || 120,
|
||||
equipmentProvided: initialData?.equipmentProvided || false,
|
||||
requiredItems: initialData?.requiredItems || [],
|
||||
});
|
||||
|
||||
const [newItem, setNewItem] = useState('');
|
||||
|
||||
const [selectedLocation, setSelectedLocation] = useState<Coordinates | null>(
|
||||
initialData?.latitude && initialData?.longitude
|
||||
? { latitude: initialData.latitude, longitude: initialData.longitude }
|
||||
@@ -328,6 +334,130 @@ export function EventForm({ onSubmit, onCancel, initialData, isSubmitting = fals
|
||||
{errors.duration && <p className="mt-1 text-sm text-red-600">{errors.duration}</p>}
|
||||
</div>
|
||||
|
||||
{/* Equipment Section */}
|
||||
<div className="border border-gray-200 rounded-lg p-4 bg-gray-50">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">Equipment</h3>
|
||||
|
||||
{/* Equipment Provided Toggle */}
|
||||
<div className="mb-4">
|
||||
<label className="flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.equipmentProvided}
|
||||
onChange={(e) => {
|
||||
setFormData(prev => ({ ...prev, equipmentProvided: e.target.checked }));
|
||||
// Clear required items if equipment is provided
|
||||
if (e.target.checked) {
|
||||
setFormData(prev => ({ ...prev, requiredItems: [] }));
|
||||
}
|
||||
}}
|
||||
className="w-5 h-5 text-blue-600 border-gray-300 rounded focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="ml-3 text-sm font-medium text-gray-700">
|
||||
Equipment will be provided by organizer
|
||||
</span>
|
||||
</label>
|
||||
<p className="mt-1 ml-8 text-sm text-gray-500">
|
||||
Check this if you'll supply all necessary cleanup equipment
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Required Items List (only shown if equipment not provided) */}
|
||||
{!formData.equipmentProvided && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Items Participants Need to Bring
|
||||
</label>
|
||||
<p className="text-sm text-gray-500 mb-3">
|
||||
Add items that participants should bring (e.g., gloves, litter picker, bags, coat)
|
||||
</p>
|
||||
|
||||
{/* Add Item Input */}
|
||||
<div className="flex gap-2 mb-3">
|
||||
<input
|
||||
type="text"
|
||||
value={newItem}
|
||||
onChange={(e) => setNewItem(e.target.value)}
|
||||
onKeyPress={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
if (newItem.trim() && !formData.requiredItems.includes(newItem.trim())) {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
requiredItems: [...prev.requiredItems, newItem.trim()]
|
||||
}));
|
||||
setNewItem('');
|
||||
}
|
||||
}
|
||||
}}
|
||||
placeholder="e.g., Gloves, Litter picker, Bags"
|
||||
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if (newItem.trim() && !formData.requiredItems.includes(newItem.trim())) {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
requiredItems: [...prev.requiredItems, newItem.trim()]
|
||||
}));
|
||||
setNewItem('');
|
||||
}
|
||||
}}
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors text-sm font-medium"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Required Items List */}
|
||||
{formData.requiredItems.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium text-gray-700">Required Items:</p>
|
||||
<ul className="space-y-1">
|
||||
{formData.requiredItems.map((item, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className="flex items-center justify-between bg-white px-3 py-2 rounded-lg border border-gray-200"
|
||||
>
|
||||
<span className="text-sm text-gray-700">• {item}</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
requiredItems: prev.requiredItems.filter((_, i) => i !== index)
|
||||
}));
|
||||
}}
|
||||
className="text-red-600 hover:text-red-800 text-sm font-medium"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{formData.requiredItems.length === 0 && (
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-3">
|
||||
<p className="text-sm text-blue-700">
|
||||
💡 Tip: Add common items like gloves, litter pickers, bags, or appropriate clothing
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{formData.equipmentProvided && (
|
||||
<div className="bg-green-50 border border-green-200 rounded-lg p-3">
|
||||
<p className="text-sm text-green-700">
|
||||
✓ Participants will be informed that all equipment will be provided
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Location Name */}
|
||||
<div>
|
||||
<label htmlFor="locationName" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
|
||||
Reference in New Issue
Block a user