'use client' import { useState, useEffect } from 'react' import { useRouter } from 'next/navigation' interface Drop { id: number item: string size: number fill: number unit: string ppu: number image_url: string | null images?: string[] created_at: string start_time: string | null } interface Sale { id: number drop_id: number buyer_id: number size: number payment_id: string | null created_at: string buyer_username?: string buyer_email?: string buyer_fullname?: string buyer_address?: string buyer_phone?: string } export default function DropsManagementPage() { const router = useRouter() const [drops, setDrops] = useState([]) const [loading, setLoading] = useState(true) const [authenticated, setAuthenticated] = useState(false) const [editingDrop, setEditingDrop] = useState(null) const [creatingDrop, setCreatingDrop] = useState(false) const [showSalesPopup, setShowSalesPopup] = useState(false) const [salesForDrop, setSalesForDrop] = useState([]) const [selectedDropId, setSelectedDropId] = useState(null) const [formData, setFormData] = useState({ item: '', size: '', unit: 'g', ppu: '', imageUrl: '', startTime: '', }) const [imageFiles, setImageFiles] = useState([]) const [imagePreviews, setImagePreviews] = useState([]) const [existingImages, setExistingImages] = useState([]) const [uploadingImage, setUploadingImage] = useState(false) useEffect(() => { // Check authentication fetch('/api/admin/check') .then((res) => res.json()) .then((data) => { if (data.authenticated) { setAuthenticated(true) fetchDrops() } else { router.push('/admin/login') } }) .catch(() => { router.push('/admin/login') }) }, [router]) const fetchDrops = async () => { try { const response = await fetch('/api/drops') if (response.ok) { const data = await response.json() setDrops(Array.isArray(data) ? data : []) } } catch (error) { console.error('Error fetching drops:', error) } finally { setLoading(false) } } const handleEdit = async (drop: Drop) => { setEditingDrop(drop) setFormData({ item: drop.item, size: drop.size.toString(), unit: drop.unit, ppu: drop.ppu.toString(), imageUrl: drop.image_url || '', startTime: drop.start_time ? new Date(drop.start_time).toISOString().slice(0, 16) : '', }) // Fetch existing images for this drop try { const response = await fetch(`/api/drops/images?drop_id=${drop.id}`) if (response.ok) { const data = await response.json() const imageUrls = data.map((img: any) => img.image_url) setExistingImages(imageUrls) } else { setExistingImages(drop.image_url ? [drop.image_url] : []) } } catch (error) { console.error('Error fetching drop images:', error) setExistingImages(drop.image_url ? [drop.image_url] : []) } // Clear file selections setImageFiles([]) setImagePreviews([]) } const handleSave = async () => { if (!editingDrop) return try { setUploadingImage(true) const imageUrls: string[] = [...existingImages] // Upload new image files for (const file of imageFiles) { const uploadFormData = new FormData() uploadFormData.append('file', file) const uploadResponse = await fetch('/api/upload', { method: 'POST', body: uploadFormData, }) if (uploadResponse.ok) { const uploadData = await uploadResponse.json() imageUrls.push(uploadData.url) } else { const error = await uploadResponse.json() alert(`Image upload failed: ${error.error}`) setUploadingImage(false) return } } // Add URL if provided if (formData.imageUrl) { imageUrls.push(formData.imageUrl) } // Limit to 4 images total const finalImageUrls = imageUrls.slice(0, 4) // Update drop basic info const response = await fetch(`/api/drops/${editingDrop.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ item: formData.item, size: parseInt(formData.size), unit: formData.unit, ppu: parseInt(formData.ppu), imageUrl: finalImageUrls[0] || null, // Keep first image for legacy support startTime: formData.startTime || null, }), }) if (!response.ok) { const error = await response.json() alert(`Error: ${error.error}`) setUploadingImage(false) return } // Save images (always save, even if empty array to clear all images) const imagesResponse = await fetch('/api/drops/images', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ drop_id: editingDrop.id, image_urls: finalImageUrls, }), }) if (!imagesResponse.ok) { const error = await imagesResponse.json() alert(`Error saving images: ${error.error}`) setUploadingImage(false) return } alert('Drop updated successfully') setEditingDrop(null) setImageFiles([]) setImagePreviews([]) setExistingImages([]) // Clean up preview URLs imagePreviews.forEach(url => URL.revokeObjectURL(url)) fetchDrops() } catch (error) { console.error('Error updating drop:', error) alert('Failed to update drop') } finally { setUploadingImage(false) } } const handleDelete = async (id: number) => { if (!confirm('Are you sure you want to delete this drop? This will also delete all related sales.')) { return } try { const response = await fetch(`/api/drops/${id}`, { method: 'DELETE', }) if (response.ok) { alert('Drop deleted successfully') fetchDrops() } else { const error = await response.json() alert(`Error: ${error.error}`) } } catch (error) { console.error('Error deleting drop:', error) alert('Failed to delete drop') } } const handleImageChange = (e: React.ChangeEvent) => { const files = Array.from(e.target.files || []) if (files.length > 0) { // Calculate how many more images we can add (max 4 total) const currentTotal = existingImages.length + imagePreviews.length const remainingSlots = Math.max(0, 4 - currentTotal) if (remainingSlots === 0) { alert('Maximum of 4 images allowed. Please remove some images first.') // Clear the file input e.target.value = '' return } // Limit to remaining slots const limitedFiles = files.slice(0, remainingSlots) setImageFiles([...imageFiles, ...limitedFiles]) // Create previews for new files const newPreviews = limitedFiles.map(file => URL.createObjectURL(file)) setImagePreviews([...imagePreviews, ...newPreviews]) // Clear the file input e.target.value = '' } } const removeImage = (index: number) => { const newFiles = imageFiles.filter((_, i) => i !== index) const newPreviews = imagePreviews.filter((_, i) => i !== index) setImageFiles(newFiles) setImagePreviews(newPreviews) // Revoke the URL to free memory URL.revokeObjectURL(imagePreviews[index]) } const removeExistingImage = (index: number) => { const newImages = existingImages.filter((_, i) => i !== index) setExistingImages(newImages) } const handleCreate = async (e: React.FormEvent) => { e.preventDefault() try { setUploadingImage(true) const imageUrls: string[] = [] // Add URL if provided if (formData.imageUrl) { imageUrls.push(formData.imageUrl) } // Upload image files for (const file of imageFiles) { const uploadFormData = new FormData() uploadFormData.append('file', file) const uploadResponse = await fetch('/api/upload', { method: 'POST', body: uploadFormData, }) if (!uploadResponse.ok) { const error = await uploadResponse.json() alert(`Image upload failed: ${error.error}`) setUploadingImage(false) return } const uploadData = await uploadResponse.json() imageUrls.push(uploadData.url) } // Limit to 4 images const finalImageUrls = imageUrls.slice(0, 4) // Create drop const response = await fetch('/api/drops', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ item: formData.item, size: parseInt(formData.size), unit: formData.unit, ppu: parseInt(formData.ppu), imageUrl: finalImageUrls[0] || null, // Keep first image for legacy support startTime: formData.startTime || null, }), }) if (!response.ok) { const error = await response.json() alert(`Error: ${error.error}`) setUploadingImage(false) return } const dropData = await response.json() const dropId = dropData.id // Save multiple images if we have more than one if (finalImageUrls.length > 0) { const imagesResponse = await fetch('/api/drops/images', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ drop_id: dropId, image_urls: finalImageUrls, }), }) if (!imagesResponse.ok) { const error = await imagesResponse.json() alert(`Drop created but failed to save images: ${error.error}`) } } alert('Drop created successfully') setFormData({ item: '', size: '', unit: 'g', ppu: '', imageUrl: '', startTime: '', }) setImageFiles([]) setImagePreviews([]) // Clean up preview URLs imagePreviews.forEach(url => URL.revokeObjectURL(url)) // Clear file input const fileInput = document.getElementById('imageFiles') as HTMLInputElement if (fileInput) fileInput.value = '' setCreatingDrop(false) fetchDrops() } catch (error) { console.error('Error creating drop:', error) alert('Failed to create drop') } finally { setUploadingImage(false) } } const handleViewSales = async (dropId: number) => { try { const response = await fetch(`/api/sales/drop/${dropId}`) if (response.ok) { const data = await response.json() setSalesForDrop(Array.isArray(data) ? data : []) setSelectedDropId(dropId) setShowSalesPopup(true) } else { alert('Failed to fetch sales') } } catch (error) { console.error('Error fetching sales:', error) alert('Failed to fetch sales') } } const getProgressPercentage = (fill: number, size: number) => { return Math.min((fill / size) * 100, 100) } if (loading) { return (

Loading...

) } if (!authenticated) { return null } return (

Drops Management

{creatingDrop && (

Create New Drop

setFormData({ ...formData, item: e.target.value })} required style={{ width: '100%', padding: '8px', borderRadius: '8px', border: '1px solid var(--border)', background: 'var(--bg-soft)', color: 'var(--text)' }} />
setFormData({ ...formData, size: e.target.value })} required min="1" style={{ width: '100%', padding: '8px', borderRadius: '8px', border: '1px solid var(--border)', background: 'var(--bg-soft)', color: 'var(--text)' }} />
setFormData({ ...formData, unit: e.target.value })} required maxLength={12} placeholder="g, kg, ml, etc." style={{ width: '100%', padding: '8px', borderRadius: '8px', border: '1px solid var(--border)', background: 'var(--bg-soft)', color: 'var(--text)' }} />
setFormData({ ...formData, ppu: e.target.value })} required min="1" placeholder="2500 = 2.50 EUR" style={{ width: '100%', padding: '8px', borderRadius: '8px', border: '1px solid var(--border)', background: 'var(--bg-soft)', color: 'var(--text)' }} />
{(imagePreviews.length > 0 || existingImages.length > 0) && (
{existingImages.map((imgUrl, index) => (
{`Existing
))} {imagePreviews.map((preview, index) => (
{`Preview
))}
)}

Max 4 images. Max file size: 5MB each. Allowed formats: JPEG, PNG, WebP

Or enter an image URL (will be added to uploaded images):

{ setFormData({ ...formData, imageUrl: e.target.value }) }} placeholder="https://example.com/image.jpg" style={{ width: '100%', padding: '8px', borderRadius: '8px', border: '1px solid var(--border)', background: 'var(--bg-soft)', color: 'var(--text)', marginTop: '8px' }} />
setFormData({ ...formData, startTime: e.target.value })} style={{ width: '100%', padding: '8px', borderRadius: '8px', border: '1px solid var(--border)', background: 'var(--bg-soft)', color: 'var(--text)' }} />
)} {drops.length === 0 ? (

No drops found

) : (
{drops.map((drop) => (
{editingDrop?.id === drop.id ? (
setFormData({ ...formData, item: e.target.value })} style={{ width: '100%', padding: '8px', borderRadius: '8px', border: '1px solid var(--border)', background: 'var(--bg-soft)', color: 'var(--text)' }} />
setFormData({ ...formData, size: e.target.value })} style={{ width: '100%', padding: '8px', borderRadius: '8px', border: '1px solid var(--border)', background: 'var(--bg-soft)', color: 'var(--text)' }} />
setFormData({ ...formData, unit: e.target.value })} maxLength={12} style={{ width: '100%', padding: '8px', borderRadius: '8px', border: '1px solid var(--border)', background: 'var(--bg-soft)', color: 'var(--text)' }} />
setFormData({ ...formData, ppu: e.target.value })} style={{ width: '100%', padding: '8px', borderRadius: '8px', border: '1px solid var(--border)', background: 'var(--bg-soft)', color: 'var(--text)' }} />
setFormData({ ...formData, startTime: e.target.value })} style={{ width: '100%', padding: '8px', borderRadius: '8px', border: '1px solid var(--border)', background: 'var(--bg-soft)', color: 'var(--text)' }} />
{(imagePreviews.length > 0 || existingImages.length > 0) && (
{existingImages.map((imgUrl, index) => (
{`Existing
))} {imagePreviews.map((preview, index) => (
{`Preview
))}
)}

Max 4 images. Max file size: 5MB each. Allowed formats: JPEG, PNG, WebP

Or enter an image URL (will be added to uploaded images):

{ setFormData({ ...formData, imageUrl: e.target.value }) }} placeholder="https://example.com/image.jpg" style={{ width: '100%', padding: '8px', borderRadius: '8px', border: '1px solid var(--border)', background: 'var(--bg-soft)', color: 'var(--text)', marginTop: '8px' }} />
) : (

{drop.item}

ID: {drop.id} · Size: {drop.size}{drop.unit} · Price: {(drop.ppu / 1000).toFixed(2)} EUR / {drop.unit}

Created: {new Date(drop.created_at).toLocaleString()} {drop.start_time && ` · Start: ${new Date(drop.start_time).toLocaleString()}`}

{/* Fill Bar */}

{drop.unit === 'kg' ? drop.fill.toFixed(2) : Math.round(drop.fill)}{drop.unit} of {drop.size}{drop.unit} reserved

{/* Display all images */} {(() => { const images = drop.images && drop.images.length > 0 ? drop.images : (drop.image_url ? [drop.image_url] : []) if (images.length === 0) return null return (
1 ? 'repeat(auto-fit, minmax(150px, 1fr))' : '1fr', gap: '8px', maxWidth: images.length === 1 ? '200px' : '100%' }}> {images.slice(0, 4).map((imgUrl, index) => ( {`${drop.item} ))}
{images.length > 4 && (

+{images.length - 4} more image{images.length - 4 > 1 ? 's' : ''}

)}
) })()}
)}
))}
)} {/* Sales Popup */} {showSalesPopup && (
setShowSalesPopup(false)} >
e.stopPropagation()} >

Sales for Drop #{selectedDropId}

{salesForDrop.length === 0 ? (

No sales found for this drop

) : (
{salesForDrop.map((sale) => (

Sale #{sale.id}

Buyer: {sale.buyer_username || `#${sale.buyer_id}`} ({sale.buyer_email || 'N/A'})

Size: {sale.size}g

{(sale.buyer_fullname || sale.buyer_address || sale.buyer_phone) && (

Delivery Information:

{sale.buyer_fullname && (

Name: {sale.buyer_fullname}

)} {sale.buyer_address && (

Address: {sale.buyer_address}

)} {sale.buyer_phone && (

Phone: {sale.buyer_phone}

)}
)}

{new Date(sale.created_at).toLocaleString()} {sale.payment_id && ` · Payment ID: ${sale.payment_id}`}

))}
)}
)}
) }