327 lines
11 KiB
TypeScript
327 lines
11 KiB
TypeScript
import React from 'react'
|
|
import { p as P, BottomDialog, Checkbox } from 'web-components'
|
|
import { trpc } from '@/lib/trpc-client'
|
|
import { ChevronRight, Eye, Pencil, XCircle, MapPin, Map, MessageCircle, Phone } from 'lucide-react'
|
|
|
|
interface OrderOptionsMenuProps {
|
|
open: boolean
|
|
onClose: () => void
|
|
order: {
|
|
id: number
|
|
readableId: number
|
|
isPackaged: boolean
|
|
isDelivered: boolean
|
|
isFlashDelivery?: boolean
|
|
address: string
|
|
addressId: number
|
|
adminNotes?: string | null
|
|
userNotes?: string | null
|
|
latitude?: number | null
|
|
longitude?: number | null
|
|
status?: string
|
|
}
|
|
onViewDetails: () => void
|
|
onTogglePackaged: () => void
|
|
onToggleDelivered: () => void
|
|
onOpenAdminNotes: () => void
|
|
onCancelOrder: () => void
|
|
onAttachLocation: () => void
|
|
onWhatsApp: () => void
|
|
onDial: () => void
|
|
}
|
|
|
|
function getCurrentPosition(): Promise<GeolocationPosition> {
|
|
return new Promise((resolve, reject) => {
|
|
if (!navigator.geolocation) {
|
|
reject(new Error('Geolocation is not supported by this browser'))
|
|
return
|
|
}
|
|
navigator.geolocation.getCurrentPosition(resolve, reject, { enableHighAccuracy: true })
|
|
})
|
|
}
|
|
|
|
export function OrderOptionsMenu({
|
|
open,
|
|
onClose,
|
|
order,
|
|
onViewDetails,
|
|
onTogglePackaged,
|
|
onToggleDelivered,
|
|
onOpenAdminNotes,
|
|
onCancelOrder,
|
|
onAttachLocation,
|
|
onWhatsApp,
|
|
onDial,
|
|
}: OrderOptionsMenuProps) {
|
|
const updateAddressCoordsMutation = trpc.admin.order.updateAddressCoords.useMutation()
|
|
|
|
const handleAttachLocation = async () => {
|
|
try {
|
|
const location = await getCurrentPosition()
|
|
const { latitude, longitude } = location.coords
|
|
await updateAddressCoordsMutation.mutateAsync({
|
|
addressId: order.addressId,
|
|
latitude,
|
|
longitude,
|
|
})
|
|
window.alert('Success: Location attached to address successfully.')
|
|
onAttachLocation()
|
|
} catch (error: any) {
|
|
if (error?.code === 1) {
|
|
window.alert('Permission Denied: Location permission is required to attach coordinates.')
|
|
return
|
|
}
|
|
window.alert('Error: Failed to attach location. Please try again.')
|
|
}
|
|
}
|
|
|
|
const extractPhone = (address: string) => {
|
|
const phoneMatch = address.match(/Phone: (\d+)/)
|
|
return phoneMatch ? phoneMatch[1] : null
|
|
}
|
|
|
|
const handleWhatsApp = () => {
|
|
const phone = extractPhone(order.address)
|
|
if (phone) {
|
|
window.open(`https://wa.me/91${phone}`, '_blank')
|
|
} else {
|
|
window.alert('No phone number found')
|
|
}
|
|
}
|
|
|
|
const handleDial = () => {
|
|
const phone = extractPhone(order.address)
|
|
if (phone) {
|
|
window.open(`tel:${phone}`, '_blank')
|
|
} else {
|
|
window.alert('No phone number found')
|
|
}
|
|
}
|
|
|
|
const handleOpenInMaps = () => {
|
|
if (order.latitude && order.longitude) {
|
|
const url = `https://www.google.com/maps/search/?api=1&query=${order.latitude},${order.longitude}`
|
|
window.open(url, '_blank')
|
|
} else {
|
|
window.alert('No location coordinates available')
|
|
}
|
|
}
|
|
|
|
const hasCoordinates = order.latitude !== null && order.latitude !== undefined &&
|
|
order.longitude !== null && order.longitude !== undefined
|
|
|
|
return (
|
|
<BottomDialog open={open} onClose={onClose}>
|
|
<div style={{ maxHeight: '70vh' }} className="overflow-hidden flex flex-col">
|
|
<div className="overflow-auto grow">
|
|
<div className="pb-8 pt-2 px-4">
|
|
<div className="items-center mb-6 flex flex-col">
|
|
<div className="w-12 h-1.5 bg-gray-200 rounded-full mb-4" />
|
|
<P className="text-lg font-bold text-gray-900">
|
|
Order #{order.readableId}
|
|
</P>
|
|
<P className="text-sm text-gray-500">
|
|
Select an action to perform
|
|
</P>
|
|
</div>
|
|
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
onViewDetails()
|
|
onClose()
|
|
}}
|
|
className="flex flex-row items-center p-4 bg-white border border-gray-100 rounded-xl mb-3 shadow-sm w-full text-left"
|
|
>
|
|
<div className="w-10 h-10 rounded-full bg-purple-50 items-center justify-center mr-4 flex shrink-0">
|
|
<Eye className="h-5 w-5 text-purple-600" />
|
|
</div>
|
|
<div>
|
|
<P className="font-semibold text-gray-800 text-base block">
|
|
View Details
|
|
</P>
|
|
<P className="text-gray-500 text-xs block">
|
|
See full order information
|
|
</P>
|
|
</div>
|
|
<ChevronRight className="h-6 w-6 text-gray-400 ml-auto" />
|
|
</button>
|
|
|
|
<div className="flex flex-row items-center p-4 bg-white border border-gray-100 rounded-xl mb-3 shadow-sm">
|
|
<button type="button" onClick={onTogglePackaged} className="p-1" aria-label="Toggle packaged">
|
|
<Checkbox
|
|
checked={order.isPackaged}
|
|
onPress={onTogglePackaged}
|
|
fillColor={order.isPackaged ? '#10B981' : '#1570EF'}
|
|
/>
|
|
</button>
|
|
<button type="button" onClick={onTogglePackaged} className="ml-3 flex-1 text-left">
|
|
<P className="font-semibold text-gray-800 text-base block">
|
|
Packaged
|
|
</P>
|
|
<P className="text-gray-500 text-xs block">
|
|
{order.isPackaged ? 'Mark as not packaged' : 'Mark as packaged'}
|
|
</P>
|
|
</button>
|
|
</div>
|
|
|
|
<div className="flex flex-row items-center p-4 bg-white border border-gray-100 rounded-xl mb-3 shadow-sm">
|
|
<button type="button" onClick={onToggleDelivered} className="p-1" aria-label="Toggle delivered">
|
|
<Checkbox
|
|
checked={order.isDelivered}
|
|
onPress={onToggleDelivered}
|
|
fillColor={order.isDelivered ? '#10B981' : '#1570EF'}
|
|
/>
|
|
</button>
|
|
<button type="button" onClick={onToggleDelivered} className="ml-3 flex-1 text-left">
|
|
<P className="font-semibold text-gray-800 text-base block">
|
|
Delivered
|
|
</P>
|
|
<P className="text-gray-500 text-xs block">
|
|
{order.isDelivered ? 'Mark as not delivered' : 'Mark as delivered'}
|
|
</P>
|
|
</button>
|
|
</div>
|
|
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
onOpenAdminNotes()
|
|
onClose()
|
|
}}
|
|
className="flex flex-row items-center p-4 bg-white border border-gray-100 rounded-xl mb-3 shadow-sm w-full text-left"
|
|
>
|
|
<div className="w-10 h-10 rounded-full bg-yellow-50 items-center justify-center mr-4 flex shrink-0">
|
|
<Pencil className="h-5 w-5 text-yellow-600" />
|
|
</div>
|
|
<div>
|
|
<P className="font-semibold text-gray-800 text-base block">
|
|
Admin Notes
|
|
</P>
|
|
<P className="text-gray-500 text-xs block">
|
|
{order.adminNotes ? 'Edit existing notes' : 'Add admin notes'}
|
|
</P>
|
|
</div>
|
|
<ChevronRight className="h-6 w-6 text-gray-400 ml-auto" />
|
|
</button>
|
|
|
|
{order.status !== 'cancelled' && (
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
onCancelOrder()
|
|
onClose()
|
|
}}
|
|
className="flex flex-row items-center p-4 bg-white border border-gray-100 rounded-xl mb-3 shadow-sm w-full text-left"
|
|
>
|
|
<div className="w-10 h-10 rounded-full bg-red-50 items-center justify-center mr-4 flex shrink-0">
|
|
<XCircle className="h-5 w-5 text-red-600" />
|
|
</div>
|
|
<div>
|
|
<P className="font-semibold text-red-700 text-base block">
|
|
Cancel Order
|
|
</P>
|
|
<P className="text-red-500 text-xs block">
|
|
Cancel and provide reason
|
|
</P>
|
|
</div>
|
|
<ChevronRight className="h-6 w-6 text-gray-400 ml-auto" />
|
|
</button>
|
|
)}
|
|
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
handleAttachLocation()
|
|
onClose()
|
|
}}
|
|
disabled={updateAddressCoordsMutation.isPending}
|
|
className="flex flex-row items-center p-4 bg-white border border-gray-100 rounded-xl mb-3 shadow-sm w-full text-left disabled:opacity-50"
|
|
>
|
|
<div className="w-10 h-10 rounded-full bg-orange-50 items-center justify-center mr-4 flex shrink-0">
|
|
<MapPin className="h-5 w-5 text-orange-600" />
|
|
</div>
|
|
<div>
|
|
<P className="font-semibold text-gray-800 text-base block">
|
|
Attach Location
|
|
</P>
|
|
<P className="text-gray-500 text-xs block">
|
|
Save GPS coordinates to address
|
|
</P>
|
|
</div>
|
|
<ChevronRight className="h-6 w-6 text-gray-400 ml-auto" />
|
|
</button>
|
|
|
|
{hasCoordinates && (
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
handleOpenInMaps()
|
|
onClose()
|
|
}}
|
|
className="flex flex-row items-center p-4 bg-white border border-gray-100 rounded-xl mb-3 shadow-sm w-full text-left"
|
|
>
|
|
<div className="w-10 h-10 rounded-full bg-blue-50 items-center justify-center mr-4 flex shrink-0">
|
|
<Map className="h-5 w-5 text-blue-600" />
|
|
</div>
|
|
<div>
|
|
<P className="font-semibold text-gray-800 text-base block">
|
|
Open in Maps
|
|
</P>
|
|
<P className="text-gray-500 text-xs block">
|
|
View delivery location on Google Maps
|
|
</P>
|
|
</div>
|
|
<ChevronRight className="h-6 w-6 text-gray-400 ml-auto" />
|
|
</button>
|
|
)}
|
|
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
handleWhatsApp()
|
|
onClose()
|
|
}}
|
|
className="flex flex-row items-center p-4 bg-white border border-gray-100 rounded-xl mb-3 shadow-sm w-full text-left"
|
|
>
|
|
<div className="w-10 h-10 rounded-full bg-green-50 items-center justify-center mr-4 flex shrink-0">
|
|
<MessageCircle className="h-5 w-5 text-green-600" />
|
|
</div>
|
|
<div>
|
|
<P className="font-semibold text-gray-800 text-base block">
|
|
Message On WhatsApp
|
|
</P>
|
|
<P className="text-gray-500 text-xs block">
|
|
Send message via WhatsApp
|
|
</P>
|
|
</div>
|
|
<ChevronRight className="h-6 w-6 text-gray-400 ml-auto" />
|
|
</button>
|
|
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
handleDial()
|
|
onClose()
|
|
}}
|
|
className="flex flex-row items-center p-4 bg-white border border-gray-100 rounded-xl mb-3 shadow-sm w-full text-left"
|
|
>
|
|
<div className="w-10 h-10 rounded-full bg-green-50 items-center justify-center mr-4 flex shrink-0">
|
|
<Phone className="h-5 w-5 text-green-600" />
|
|
</div>
|
|
<div>
|
|
<P className="font-semibold text-gray-800 text-base block">
|
|
Dial Mobile Number
|
|
</P>
|
|
<P className="text-gray-500 text-xs block">
|
|
Call customer directly
|
|
</P>
|
|
</div>
|
|
<ChevronRight className="h-6 w-6 text-gray-400 ml-auto" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</BottomDialog>
|
|
)
|
|
}
|