Spaces:
Sleeping
Sleeping
| import { useState, useEffect } from 'react'; | |
| import { Upload, Download, Check, AlertCircle, Loader2, X } from 'lucide-react'; | |
| import api from '../api/api'; | |
| const DeliveryPanel = ({ bookingId, photographerId, role = 'photographer' }) => { | |
| const [delivery, setDelivery] = useState(null); | |
| const [loading, setLoading] = useState(true); | |
| const [uploading, setUploading] = useState(false); | |
| const [showUploadForm, setShowUploadForm] = useState(false); | |
| useEffect(() => { | |
| fetchDelivery(); | |
| }, [bookingId]); | |
| const fetchDelivery = async () => { | |
| try { | |
| const res = await api.get(`/delivery/booking/${bookingId}`); | |
| setDelivery(res.data); | |
| } catch (err) { | |
| // Delivery record might not exist yet | |
| console.log('Delivery record not yet created'); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| const handlePhotoUpload = async (e) => { | |
| const files = e.target.files; | |
| if (!files || files.length === 0) return; | |
| setUploading(true); | |
| try { | |
| const photoUrls = Array.from(files).map(file => URL.createObjectURL(file)); | |
| const res = await api.post(`/delivery/${delivery._id}/upload`, { | |
| photoUrls, | |
| }); | |
| setDelivery(res.data); | |
| alert(`${files.length} photos uploaded!`); | |
| setShowUploadForm(false); | |
| } catch (err) { | |
| alert('Failed to upload photos'); | |
| } finally { | |
| setUploading(false); | |
| } | |
| }; | |
| const handleCompleteDelivery = async () => { | |
| try { | |
| const res = await api.post(`/delivery/${delivery._id}/complete`, { | |
| downloadLinkExpiryDays: 7, | |
| }); | |
| setDelivery(res.data); | |
| alert('Delivery marked as complete!'); | |
| } catch (err) { | |
| alert('Failed to complete delivery'); | |
| } | |
| }; | |
| if (loading) { | |
| return ( | |
| <div className="flex items-center justify-center py-8"> | |
| <Loader2 className="animate-spin text-blue-600" size={28} /> | |
| </div> | |
| ); | |
| } | |
| if (!delivery) { | |
| return ( | |
| <div className="bg-amber-50 border border-amber-200 rounded-lg p-4"> | |
| <p className="text-amber-700">Delivery not yet created for this booking</p> | |
| </div> | |
| ); | |
| } | |
| const statusColor = { | |
| pending: 'gray', | |
| partially_delivered: 'blue', | |
| delivered: 'green', | |
| revision_requested: 'orange', | |
| }; | |
| return ( | |
| <div className="space-y-4"> | |
| <div className="flex items-center justify-between"> | |
| <h3 className="text-lg font-bold">📦 Photo Delivery</h3> | |
| <span | |
| className={`px-3 py-1 rounded-full text-xs font-bold text-${statusColor[delivery.status]}-700 bg-${statusColor[delivery.status]}-100`} | |
| > | |
| {delivery.status.replace('_', ' ').toUpperCase()} | |
| </span> | |
| </div> | |
| {/* Photos Grid */} | |
| {delivery.photos && delivery.photos.length > 0 && ( | |
| <div> | |
| <p className="text-sm font-semibold text-gray-700 mb-3"> | |
| {delivery.photos.length} Photos Uploaded | |
| </p> | |
| <div className="grid grid-cols-2 md:grid-cols-4 gap-3 mb-4 max-h-[300px] overflow-y-auto"> | |
| {delivery.photos.map((photo, idx) => ( | |
| <div key={idx} className="aspect-square bg-gray-200 rounded-lg overflow-hidden relative group"> | |
| <img | |
| src={photo.url} | |
| alt={`Photo ${idx + 1}`} | |
| className="w-full h-full object-cover group-hover:opacity-75" | |
| /> | |
| {photo.watermarked && ( | |
| <div className="absolute bottom-1 right-1 bg-black/50 text-white text-xs px-2 py-1 rounded"> | |
| Watermarked | |
| </div> | |
| )} | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| )} | |
| {/* Upload Form */} | |
| {role === 'photographer' && delivery.status !== 'delivered' && ( | |
| <div> | |
| {!showUploadForm ? ( | |
| <button | |
| onClick={() => setShowUploadForm(true)} | |
| className="w-full flex items-center justify-center gap-2 bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700 font-medium" | |
| > | |
| <Upload size={18} /> Add More Photos | |
| </button> | |
| ) : ( | |
| <div className="border-2 border-dashed border-gray-300 rounded-lg p-6"> | |
| <label className="flex flex-col items-center justify-center cursor-pointer"> | |
| <Upload className="text-gray-400 mb-2" size={32} /> | |
| <p className="font-semibold text-gray-700">Click to upload photos</p> | |
| <p className="text-xs text-gray-500">or drag and drop</p> | |
| <input | |
| type="file" | |
| onChange={handlePhotoUpload} | |
| disabled={uploading} | |
| multiple | |
| className="hidden" | |
| accept="image/*" | |
| /> | |
| </label> | |
| {uploading && <p className="text-sm text-blue-600 mt-2">Uploading...</p>} | |
| <button | |
| onClick={() => setShowUploadForm(false)} | |
| className="mt-2 text-sm text-gray-600 hover:text-gray-900" | |
| > | |
| Cancel | |
| </button> | |
| </div> | |
| )} | |
| </div> | |
| )} | |
| {/* Action Buttons */} | |
| {role === 'photographer' && delivery.status === 'partially_delivered' && ( | |
| <button | |
| onClick={handleCompleteDelivery} | |
| className="w-full flex items-center justify-center gap-2 bg-green-600 text-white py-2 rounded-lg hover:bg-green-700 font-medium" | |
| > | |
| <Check size={18} /> Mark as Delivered | |
| </button> | |
| )} | |
| {/* Customer Download Section */} | |
| {role === 'customer' && delivery.status === 'delivered' && delivery.downloadLink && ( | |
| <div className="bg-green-50 border border-green-200 rounded-lg p-4"> | |
| <div className="flex items-center gap-2 text-green-700 font-semibold mb-3"> | |
| <Check size={20} /> | |
| Photos Delivered! | |
| </div> | |
| <p className="text-sm text-green-600 mb-3"> | |
| Download link expires on{' '} | |
| {new Date(delivery.linkExpiresAt).toLocaleDateString()} | |
| </p> | |
| <a | |
| href={delivery.downloadLink} | |
| className="inline-flex items-center gap-2 bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 font-medium" | |
| > | |
| <Download size={18} /> Download Photos | |
| </a> | |
| </div> | |
| )} | |
| {/* Revision Request */} | |
| {delivery.status === 'revision_requested' && ( | |
| <div className="bg-amber-50 border border-amber-200 rounded-lg p-4"> | |
| <div className="flex items-center gap-2 text-amber-700 font-semibold mb-2"> | |
| <AlertCircle size={20} /> | |
| Revision Requested | |
| </div> | |
| <p className="text-sm text-amber-700 mb-3">{delivery.revisionNotes}</p> | |
| {role === 'photographer' && ( | |
| <p className="text-xs text-amber-600"> | |
| Please review and upload revised photos | |
| </p> | |
| )} | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| }; | |
| export default DeliveryPanel; | |