freshyo/apps/admin-ui/components/OrderOptionsMenu.tsx
2026-03-09 22:02:26 +05:30

341 lines
No EOL
12 KiB
TypeScript

import React from 'react';
import { View, TouchableOpacity, Linking, Alert, TextInput, ScrollView, Dimensions } from 'react-native';
import * as Location from 'expo-location';
const { height: SCREEN_HEIGHT } = Dimensions.get('window');
import {
MyText,
tw,
BottomDialog,
} from 'common-ui';
import { trpc } from '@/src/trpc-client';
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
import Ionicons from '@expo/vector-icons/Ionicons';
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;
}
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 { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
Alert.alert(
'Permission Denied',
'Location permission is required to attach coordinates.'
);
return;
}
const location = await Location.getCurrentPositionAsync({
accuracy: Location.Accuracy.High,
});
const { latitude, longitude } = location.coords;
await updateAddressCoordsMutation.mutateAsync({
addressId: order.addressId,
latitude,
longitude,
});
Alert.alert('Success', 'Location attached to address successfully.');
onAttachLocation();
} catch (error) {
Alert.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) {
Linking.openURL(`whatsapp://send?phone=+91${phone}`);
} else {
Alert.alert('No phone number found');
}
};
const handleDial = () => {
const phone = extractPhone(order.address);
if (phone) {
Linking.openURL(`tel:${phone}`);
} else {
Alert.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}`;
Linking.openURL(url);
} else {
Alert.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}>
<View style={{ maxHeight: SCREEN_HEIGHT * 0.7 }}>
<ScrollView showsVerticalScrollIndicator={false} contentContainerStyle={{ flexGrow: 1 }}>
<View style={tw`pb-8 pt-2 px-4`}>
<View style={tw`items-center mb-6`}>
<View style={tw`w-12 h-1.5 bg-gray-200 rounded-full mb-4`} />
<MyText style={tw`text-lg font-bold text-gray-900`}>
Order #{order.readableId}
</MyText>
<MyText style={tw`text-sm text-gray-500`}>
Select an action to perform
</MyText>
</View>
<TouchableOpacity
style={tw`flex-row items-center p-4 bg-white border border-gray-100 rounded-xl mb-3 shadow-sm`}
onPress={() => {
onViewDetails();
onClose();
}}
>
<View style={tw`w-10 h-10 rounded-full bg-purple-50 items-center justify-center mr-4`}>
<MaterialIcons name="visibility" size={20} color="#9333ea" />
</View>
<View>
<MyText style={tw`font-semibold text-gray-800 text-base`}>
View Details
</MyText>
<MyText style={tw`text-gray-500 text-xs`}>
See full order information
</MyText>
</View>
<MaterialIcons name="chevron-right" size={24} color="#9ca3af" style={tw`ml-auto`} />
</TouchableOpacity>
<View style={tw`flex-row items-center p-4 bg-white border border-gray-100 rounded-xl mb-3 shadow-sm`}>
<View
style={tw`p-1`}
hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }}
onStartShouldSetResponder={() => true}
onResponderEnd={(e) => {
e.stopPropagation();
onTogglePackaged();
}}
>
<Ionicons
name={order.isPackaged ? 'checkbox' : 'square-outline'}
size={24}
color={order.isPackaged ? '#10B981' : '#1570EF'}
/>
</View>
<View style={tw`ml-3 flex-1`}>
<MyText style={tw`font-semibold text-gray-800 text-base`}>
Packaged
</MyText>
<MyText style={tw`text-gray-500 text-xs`}>
{order.isPackaged ? 'Mark as not packaged' : 'Mark as packaged'}
</MyText>
</View>
</View>
<View style={tw`flex-row items-center p-4 bg-white border border-gray-100 rounded-xl mb-3 shadow-sm`}>
<View
style={tw`p-1`}
hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }}
onStartShouldSetResponder={() => true}
onResponderEnd={(e) => {
e.stopPropagation();
onToggleDelivered();
}}
>
<Ionicons
name={order.isDelivered ? 'checkbox' : 'square-outline'}
size={24}
color={order.isDelivered ? '#10B981' : '#1570EF'}
/>
</View>
<View style={tw`ml-3 flex-1`}>
<MyText style={tw`font-semibold text-gray-800 text-base`}>
Delivered
</MyText>
<MyText style={tw`text-gray-500 text-xs`}>
{order.isDelivered ? 'Mark as not delivered' : 'Mark as delivered'}
</MyText>
</View>
</View>
<TouchableOpacity
style={tw`flex-row items-center p-4 bg-white border border-gray-100 rounded-xl mb-3 shadow-sm`}
onPress={() => {
onOpenAdminNotes();
onClose();
}}
>
<View style={tw`w-10 h-10 rounded-full bg-yellow-50 items-center justify-center mr-4`}>
<MaterialIcons name="edit" size={20} color="#ca8a04" />
</View>
<View>
<MyText style={tw`font-semibold text-gray-800 text-base`}>
Admin Notes
</MyText>
<MyText style={tw`text-gray-500 text-xs`}>
{order.adminNotes ? 'Edit existing notes' : 'Add admin notes'}
</MyText>
</View>
<MaterialIcons name="chevron-right" size={24} color="#9ca3af" style={tw`ml-auto`} />
</TouchableOpacity>
{order.status !== 'cancelled' && (
<TouchableOpacity
style={tw`flex-row items-center p-4 bg-white border border-gray-100 rounded-xl mb-3 shadow-sm`}
onPress={() => {
onCancelOrder();
onClose();
}}
>
<View style={tw`w-10 h-10 rounded-full bg-red-50 items-center justify-center mr-4`}>
<MaterialIcons name="cancel" size={20} color="#dc2626" />
</View>
<View>
<MyText style={tw`font-semibold text-red-700 text-base`}>
Cancel Order
</MyText>
<MyText style={tw`text-red-500 text-xs`}>
Cancel and provide reason
</MyText>
</View>
<MaterialIcons name="chevron-right" size={24} color="#9ca3af" style={tw`ml-auto`} />
</TouchableOpacity>
)}
<TouchableOpacity
style={tw`flex-row items-center p-4 bg-white border border-gray-100 rounded-xl mb-3 shadow-sm`}
onPress={() => {
handleAttachLocation();
onClose();
}}
disabled={updateAddressCoordsMutation.isPending}
>
<View style={tw`w-10 h-10 rounded-full bg-orange-50 items-center justify-center mr-4`}>
<MaterialIcons name="add-location-alt" size={20} color="#ea580c" />
</View>
<View>
<MyText style={tw`font-semibold text-gray-800 text-base`}>
Attach Location
</MyText>
<MyText style={tw`text-gray-500 text-xs`}>
Save GPS coordinates to address
</MyText>
</View>
<MaterialIcons name="chevron-right" size={24} color="#9ca3af" style={tw`ml-auto`} />
</TouchableOpacity>
{hasCoordinates && (
<TouchableOpacity
style={tw`flex-row items-center p-4 bg-white border border-gray-100 rounded-xl mb-3 shadow-sm`}
onPress={() => {
handleOpenInMaps();
onClose();
}}
>
<View style={tw`w-10 h-10 rounded-full bg-blue-50 items-center justify-center mr-4`}>
<MaterialIcons name="map" size={20} color="#2563eb" />
</View>
<View>
<MyText style={tw`font-semibold text-gray-800 text-base`}>
Open in Maps
</MyText>
<MyText style={tw`text-gray-500 text-xs`}>
View delivery location on Google Maps
</MyText>
</View>
<MaterialIcons name="chevron-right" size={24} color="#9ca3af" style={tw`ml-auto`} />
</TouchableOpacity>
)}
<TouchableOpacity
style={tw`flex-row items-center p-4 bg-white border border-gray-100 rounded-xl mb-3 shadow-sm`}
onPress={() => {
handleWhatsApp();
onClose();
}}
>
<View style={tw`w-10 h-10 rounded-full bg-green-50 items-center justify-center mr-4`}>
<MaterialIcons name="message" size={20} color="#16a34a" />
</View>
<View>
<MyText style={tw`font-semibold text-gray-800 text-base`}>
Message On WhatsApp
</MyText>
<MyText style={tw`text-gray-500 text-xs`}>
Send message via WhatsApp
</MyText>
</View>
<MaterialIcons name="chevron-right" size={24} color="#9ca3af" style={tw`ml-auto`} />
</TouchableOpacity>
<TouchableOpacity
style={tw`flex-row items-center p-4 bg-white border border-gray-100 rounded-xl mb-3 shadow-sm`}
onPress={() => {
handleDial();
onClose();
}}
>
<View style={tw`w-10 h-10 rounded-full bg-green-50 items-center justify-center mr-4`}>
<MaterialIcons name="phone" size={20} color="#16a34a" />
</View>
<View>
<MyText style={tw`font-semibold text-gray-800 text-base`}>
Dial Mobile Number
</MyText>
<MyText style={tw`text-gray-500 text-xs`}>
Call customer directly
</MyText>
</View>
<MaterialIcons name="chevron-right" size={24} color="#9ca3af" style={tw`ml-auto`} />
</TouchableOpacity>
</View>
</ScrollView>
</View>
</BottomDialog>
);
}