enh
This commit is contained in:
parent
e96ed2334e
commit
d658022a51
9 changed files with 4056 additions and 346 deletions
|
|
@ -27,6 +27,9 @@ import { trpc } from "@/src/trpc-client";
|
|||
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
|
||||
import Entypo from "@expo/vector-icons/Entypo";
|
||||
import * as Location from "expo-location";
|
||||
import { OrderOptionsMenu } from "@/components/OrderOptionsMenu";
|
||||
import CancelOrderDialog from "@/components/CancelOrderDialog";
|
||||
import { OrderNotesForm } from "@/components/OrderNotesForm";
|
||||
|
||||
// Define types outside
|
||||
interface OrderWithSequence {
|
||||
|
|
@ -270,6 +273,8 @@ export default function DeliverySequences() {
|
|||
const [selectedUserId, setSelectedUserId] = useState<number>(-1);
|
||||
const [selectedOrderIds, setSelectedOrderIds] = useState<number[]>([]);
|
||||
const [hasSequenceChanged, setHasSequenceChanged] = useState(false);
|
||||
const [cancelDialogOpen, setCancelDialogOpen] = useState(false);
|
||||
const [notesDialogOpen, setNotesDialogOpen] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
const { data: slotsData, refetch: refetchSlots } =
|
||||
|
|
@ -705,273 +710,90 @@ export default function DeliverySequences() {
|
|||
)}
|
||||
|
||||
{/* Order Menu Dialog */}
|
||||
<BottomDialog
|
||||
<OrderOptionsMenu
|
||||
open={showOrderMenu}
|
||||
onClose={() => setShowOrderMenu(false)}
|
||||
>
|
||||
<View style={tw`pb-8 pt-2 px-4`}>
|
||||
{/* Handle Bar */}
|
||||
<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 #{selectedOrder?.readableId}
|
||||
</MyText>
|
||||
<MyText style={tw`text-sm text-gray-500`}>
|
||||
Select an action to perform
|
||||
</MyText>
|
||||
</View>
|
||||
|
||||
{/* Actions */}
|
||||
<TouchableOpacity
|
||||
style={tw`flex-row items-center p-4 bg-white border border-gray-100 rounded-xl mb-3 shadow-sm`}
|
||||
onPress={() => {
|
||||
router.push(`/order-details/${selectedOrder?.id}`);
|
||||
setShowOrderMenu(false);
|
||||
}}
|
||||
disabled={updateAddressCoordsMutation.isPending}
|
||||
>
|
||||
<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>
|
||||
|
||||
<TouchableOpacity
|
||||
style={tw`flex-row items-center p-4 bg-white border border-gray-100 rounded-xl mb-3 shadow-sm ${
|
||||
updatePackagedMutation.isPending ? "opacity-50" : ""
|
||||
}`}
|
||||
onPress={async () => {
|
||||
if (!selectedOrder) return;
|
||||
try {
|
||||
await updatePackagedMutation.mutateAsync({
|
||||
orderId: selectedOrder.id.toString(),
|
||||
isPackaged: !selectedOrder.isPackaged,
|
||||
});
|
||||
order={{
|
||||
id: selectedOrder?.id || 0,
|
||||
readableId: selectedOrder?.readableId || 0,
|
||||
isPackaged: selectedOrder?.isPackaged || false,
|
||||
isDelivered: selectedOrder?.isDelivered || false,
|
||||
address: selectedOrder?.address || '',
|
||||
addressId: selectedOrder?.addressId || 0,
|
||||
adminNotes: selectedOrder?.adminNotes || null,
|
||||
latitude: selectedOrder?.latitude || null,
|
||||
longitude: selectedOrder?.longitude || null,
|
||||
}}
|
||||
onViewDetails={() => {
|
||||
if (selectedOrder) {
|
||||
router.push(`/order-details/${selectedOrder.id}`);
|
||||
}
|
||||
setShowOrderMenu(false);
|
||||
}}
|
||||
onTogglePackaged={() => {
|
||||
if (!selectedOrder) return;
|
||||
updatePackagedMutation.mutate(
|
||||
{ orderId: selectedOrder.id.toString(), isPackaged: !selectedOrder.isPackaged },
|
||||
{
|
||||
onSuccess: () => {
|
||||
refetchOrders();
|
||||
refetchSequence();
|
||||
setShowOrderMenu(false);
|
||||
} catch (error) {
|
||||
},
|
||||
onError: () => {
|
||||
Alert.alert("Error", "Failed to update packaged status");
|
||||
}
|
||||
}}
|
||||
disabled={updatePackagedMutation.isPending}
|
||||
>
|
||||
<View
|
||||
style={tw`w-10 h-10 rounded-full bg-blue-50 items-center justify-center mr-4`}
|
||||
>
|
||||
<MaterialIcons name="inventory" size={20} color="#2563eb" />
|
||||
</View>
|
||||
<View>
|
||||
<MyText style={tw`font-semibold text-gray-800 text-base`}>
|
||||
{selectedOrder?.isPackaged
|
||||
? "Unmark Packaged"
|
||||
: "Mark Packaged"}
|
||||
</MyText>
|
||||
<MyText style={tw`text-gray-500 text-xs`}>
|
||||
{selectedOrder?.isPackaged
|
||||
? "Revert to not packaged"
|
||||
: "Update status to packaged"}
|
||||
</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 ${
|
||||
updateDeliveredMutation.isPending ? "opacity-50" : ""
|
||||
}`}
|
||||
onPress={async () => {
|
||||
if (!selectedOrder) return;
|
||||
try {
|
||||
await updateDeliveredMutation.mutateAsync({
|
||||
orderId: selectedOrder.id.toString(),
|
||||
isDelivered: !selectedOrder.isDelivered,
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
}}
|
||||
onToggleDelivered={() => {
|
||||
if (!selectedOrder) return;
|
||||
updateDeliveredMutation.mutate(
|
||||
{ orderId: selectedOrder.id.toString(), isDelivered: !selectedOrder.isDelivered },
|
||||
{
|
||||
onSuccess: () => {
|
||||
refetchOrders();
|
||||
refetchSequence();
|
||||
setShowOrderMenu(false);
|
||||
} catch (error) {
|
||||
},
|
||||
onError: () => {
|
||||
Alert.alert("Error", "Failed to update delivered status");
|
||||
}
|
||||
}}
|
||||
disabled={updateDeliveredMutation.isPending}
|
||||
>
|
||||
<View
|
||||
style={tw`w-10 h-10 rounded-full bg-green-50 items-center justify-center mr-4`}
|
||||
>
|
||||
<MaterialIcons name="local-shipping" size={20} color="#16a34a" />
|
||||
</View>
|
||||
<View>
|
||||
<MyText style={tw`font-semibold text-gray-800 text-base`}>
|
||||
{selectedOrder?.isDelivered
|
||||
? "Unmark Delivered"
|
||||
: "Mark Delivered"}
|
||||
</MyText>
|
||||
<MyText style={tw`text-gray-500 text-xs`}>
|
||||
{selectedOrder?.isDelivered
|
||||
? "Revert delivery status"
|
||||
: "Complete the delivery"}
|
||||
</MyText>
|
||||
</View>
|
||||
<MaterialIcons
|
||||
name="chevron-right"
|
||||
size={24}
|
||||
color="#9ca3af"
|
||||
style={tw`ml-auto`}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
},
|
||||
}
|
||||
);
|
||||
}}
|
||||
onOpenAdminNotes={() => {
|
||||
setShowOrderMenu(false);
|
||||
setNotesDialogOpen(true);
|
||||
}}
|
||||
onCancelOrder={() => {
|
||||
setShowOrderMenu(false);
|
||||
setCancelDialogOpen(true);
|
||||
}}
|
||||
onAttachLocation={() => {}}
|
||||
onWhatsApp={() => {}}
|
||||
onDial={() => {}}
|
||||
/>
|
||||
|
||||
<TouchableOpacity
|
||||
style={tw`flex-row items-center p-4 bg-white border border-gray-100 rounded-xl mb-3 shadow-sm ${
|
||||
updateAddressCoordsMutation.isPending ? "opacity-50" : ""
|
||||
}`}
|
||||
onPress={async () => {
|
||||
if (!selectedOrder) return;
|
||||
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: selectedOrder.addressId,
|
||||
latitude,
|
||||
longitude,
|
||||
});
|
||||
Alert.alert(
|
||||
"Success",
|
||||
"Location attached to address successfully."
|
||||
);
|
||||
} catch (error) {
|
||||
Alert.alert(
|
||||
"Error",
|
||||
"Failed to attach location. Please try again."
|
||||
);
|
||||
}
|
||||
setShowOrderMenu(false);
|
||||
}}
|
||||
>
|
||||
<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 coordinates to address
|
||||
</MyText>
|
||||
</View>
|
||||
<MaterialIcons
|
||||
name="chevron-right"
|
||||
size={24}
|
||||
color="#9ca3af"
|
||||
style={tw`ml-auto`}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<CancelOrderDialog
|
||||
orderId={selectedOrder?.id || 0}
|
||||
open={cancelDialogOpen}
|
||||
onClose={() => setCancelDialogOpen(false)}
|
||||
onSuccess={() => {
|
||||
refetchOrders();
|
||||
refetchSequence();
|
||||
}}
|
||||
/>
|
||||
|
||||
<TouchableOpacity
|
||||
style={tw`flex-row items-center p-4 bg-white border border-gray-100 rounded-xl mb-3 shadow-sm`}
|
||||
onPress={() => {
|
||||
const phoneMatch = selectedOrder?.address.match(/Phone: (\d+)/);
|
||||
const phone = phoneMatch ? phoneMatch[1] : null;
|
||||
if (phone) {
|
||||
Linking.openURL(`whatsapp://send?phone=+91${phone}`);
|
||||
} else {
|
||||
Alert.alert("No phone number found");
|
||||
}
|
||||
setShowOrderMenu(false);
|
||||
}}
|
||||
>
|
||||
<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={() => {
|
||||
const phoneMatch = selectedOrder?.address.match(/Phone: (\d+)/);
|
||||
const phone = phoneMatch ? phoneMatch[1] : null;
|
||||
if (phone) {
|
||||
Linking.openURL(`tel:${phone}`);
|
||||
} else {
|
||||
Alert.alert("No phone number found");
|
||||
}
|
||||
setShowOrderMenu(false);
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
<BottomDialog open={notesDialogOpen} onClose={() => setNotesDialogOpen(false)}>
|
||||
<OrderNotesForm
|
||||
orderId={selectedOrder?.id || 0}
|
||||
initialNotes={selectedOrder?.adminNotes || ''}
|
||||
onSuccess={() => {
|
||||
refetchOrders();
|
||||
refetchSequence();
|
||||
setNotesDialogOpen(false);
|
||||
}}
|
||||
onCancel={() => setNotesDialogOpen(false)}
|
||||
/>
|
||||
</BottomDialog>
|
||||
</View>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useState , useEffect } from 'react';
|
||||
import { View, TouchableOpacity, Alert, TextInput, ActivityIndicator } from 'react-native';
|
||||
import { View, TouchableOpacity, Alert, TextInput, ActivityIndicator, Linking } from 'react-native';
|
||||
import { AppContainer, MyText, tw, MyFlatList, BottomDialog, BottomDropdown, Checkbox, theme, MyTextInput } from 'common-ui';
|
||||
import { trpc } from '../../../src/trpc-client';
|
||||
import { useRouter, useLocalSearchParams } from 'expo-router';
|
||||
|
|
@ -7,6 +7,8 @@ import dayjs from 'dayjs';
|
|||
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
||||
import { Entypo } from '@expo/vector-icons';
|
||||
import CancelOrderDialog from '@/components/CancelOrderDialog';
|
||||
import { OrderOptionsMenu } from '@/components/OrderOptionsMenu';
|
||||
import * as Location from 'expo-location';
|
||||
|
||||
const AdminNotesForm = ({ orderId, existingNotes, onClose, refetch }: { orderId: string; existingNotes?: string | null; onClose: () => void; refetch: () => void }) => {
|
||||
const [notesText, setNotesText] = useState(existingNotes || '');
|
||||
|
|
@ -111,7 +113,6 @@ const OrderItem = ({ order, refetch }: { order: OrderType; refetch: () => void }
|
|||
{ orderId: order.orderId.toString(), isPackaged },
|
||||
{
|
||||
onSuccess: () => {
|
||||
setMenuOpen(false);
|
||||
refetch();
|
||||
},
|
||||
}
|
||||
|
|
@ -123,7 +124,6 @@ const OrderItem = ({ order, refetch }: { order: OrderType; refetch: () => void }
|
|||
{ orderId: order.orderId.toString(), isDelivered },
|
||||
{
|
||||
onSuccess: () => {
|
||||
setMenuOpen(false);
|
||||
refetch();
|
||||
},
|
||||
}
|
||||
|
|
@ -344,81 +344,40 @@ const OrderItem = ({ order, refetch }: { order: OrderType; refetch: () => void }
|
|||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
<BottomDialog open={menuOpen} onClose={() => setMenuOpen(false)}>
|
||||
<View style={tw`p-6`}>
|
||||
<MyText style={tw`text-lg font-bold text-gray-800 mb-4`}>
|
||||
Order Options
|
||||
</MyText>
|
||||
{order.isFlashDelivery && (
|
||||
<View style={tw`bg-amber-50 border border-amber-200 rounded-lg p-3 mb-4 flex-row items-center`}>
|
||||
<MaterialIcons name="bolt" size={20} color="#D97706" />
|
||||
<View style={tw`ml-3 flex-1`}>
|
||||
<MyText style={tw`text-sm font-bold text-amber-900`}>Flash Delivery Order</MyText>
|
||||
<MyText style={tw`text-xs text-amber-700`}>
|
||||
Deliver within 30 minutes • High Priority
|
||||
</MyText>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
<TouchableOpacity
|
||||
style={tw`flex-row items-center p-4 bg-gray-50 rounded-lg mb-3`}
|
||||
onPress={() => handleMarkPackaged(!order.isPackaged)}
|
||||
>
|
||||
<Entypo name="box" size={20} color="#6B7280" />
|
||||
<MyText style={tw`text-gray-800 font-medium ml-3`}>
|
||||
{order.isPackaged ? 'Mark Not Packaged' : 'Mark Packaged'}
|
||||
</MyText>
|
||||
</TouchableOpacity>
|
||||
{order.isPackaged && (
|
||||
<TouchableOpacity
|
||||
style={tw`flex-row items-center p-4 bg-gray-50 rounded-lg mb-3`}
|
||||
onPress={() => handleMarkDelivered(!order.isDelivered)}
|
||||
>
|
||||
<Entypo name="location" size={20} color="#6B7280" />
|
||||
<MyText style={tw`text-gray-800 font-medium ml-3`}>
|
||||
{order.isDelivered ? 'Mark Not Delivered' : 'Mark Delivered'}
|
||||
</MyText>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
<TouchableOpacity
|
||||
style={tw`flex-row items-center p-4 bg-gray-50 rounded-lg mb-3`}
|
||||
onPress={handleMenuOption}
|
||||
>
|
||||
<Entypo name="info-with-circle" size={20} color="#6B7280" />
|
||||
<MyText style={tw`text-gray-800 font-medium ml-3`}>
|
||||
Order Details
|
||||
</MyText>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={tw`flex-row items-center p-4 bg-gray-50 rounded-lg mb-3`}
|
||||
onPress={() => {
|
||||
setMenuOpen(false);
|
||||
setNotesDialogOpen(true);
|
||||
}}
|
||||
>
|
||||
<Entypo name="edit" size={20} color="#6B7280" />
|
||||
<MyText style={tw`text-gray-800 font-medium ml-3`}>
|
||||
Admin Notes
|
||||
</MyText>
|
||||
</TouchableOpacity>
|
||||
{order.status !== 'cancelled' && (
|
||||
<TouchableOpacity
|
||||
style={tw`flex-row items-center p-4 bg-red-50 rounded-lg`}
|
||||
onPress={() => {
|
||||
setMenuOpen(false);
|
||||
setCancelDialogOpen(true);
|
||||
}}
|
||||
>
|
||||
<MaterialIcons name="cancel" size={20} color="#DC2626" />
|
||||
<MyText style={tw`text-red-700 font-medium ml-3`}>
|
||||
Cancel Order
|
||||
</MyText>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
</BottomDialog>
|
||||
<OrderOptionsMenu
|
||||
open={menuOpen}
|
||||
onClose={() => setMenuOpen(false)}
|
||||
order={{
|
||||
id: order.id,
|
||||
readableId: order.readableId,
|
||||
isPackaged: order.isPackaged,
|
||||
isDelivered: order.isDelivered,
|
||||
isFlashDelivery: order.isFlashDelivery,
|
||||
address: order.address,
|
||||
addressId: 0,
|
||||
adminNotes: order.adminNotes,
|
||||
userNotes: order.userNotes,
|
||||
latitude: null,
|
||||
longitude: null,
|
||||
status: order.status,
|
||||
}}
|
||||
onViewDetails={handleMenuOption}
|
||||
onTogglePackaged={() => handleMarkPackaged(!order.isPackaged)}
|
||||
onToggleDelivered={() => handleMarkDelivered(!order.isDelivered)}
|
||||
onOpenAdminNotes={() => {
|
||||
setMenuOpen(false);
|
||||
setNotesDialogOpen(true);
|
||||
}}
|
||||
onCancelOrder={() => {
|
||||
setMenuOpen(false);
|
||||
setCancelDialogOpen(true);
|
||||
}}
|
||||
onAttachLocation={() => {}}
|
||||
onWhatsApp={() => {}}
|
||||
onDial={() => {}}
|
||||
/>
|
||||
|
||||
<BottomDialog open={itemsDialogOpen} onClose={() => setItemsDialogOpen(false)}>
|
||||
<BottomDialog open={itemsDialogOpen} onClose={() => setItemsDialogOpen(false)}>
|
||||
<View style={tw`py-6`}>
|
||||
<View style={tw`flex-row items-center justify-between mb-4`}>
|
||||
<MyText style={tw`text-lg font-bold text-gray-800`}>
|
||||
|
|
|
|||
306
apps/admin-ui/components/OrderOptionsMenu.tsx
Normal file
306
apps/admin-ui/components/OrderOptionsMenu.tsx
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
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');
|
||||
}
|
||||
};
|
||||
|
||||
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>
|
||||
|
||||
<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>
|
||||
);
|
||||
}
|
||||
2
apps/backend/drizzle/0068_colossal_magma.sql
Normal file
2
apps/backend/drizzle/0068_colossal_magma.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE "mf"."addresses" ADD COLUMN "admin_latitude" real;--> statement-breakpoint
|
||||
ALTER TABLE "mf"."addresses" ADD COLUMN "admin_longitude" real;
|
||||
3612
apps/backend/drizzle/meta/0068_snapshot.json
Normal file
3612
apps/backend/drizzle/meta/0068_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -477,6 +477,13 @@
|
|||
"when": 1769280779210,
|
||||
"tag": "0067_messy_earthquake",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 68,
|
||||
"version": "7",
|
||||
"when": 1769709890336,
|
||||
"tag": "0068_colossal_magma",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -48,6 +48,8 @@ export const addresses = mf.table('addresses', {
|
|||
isDefault: boolean('is_default').notNull().default(false),
|
||||
latitude: real('latitude'),
|
||||
longitude: real('longitude'),
|
||||
adminLatitude: real('admin_latitude'),
|
||||
adminLongitude: real('admin_longitude'),
|
||||
zoneId: integer('zone_id').references(() => addressZones.id),
|
||||
createdAt: timestamp('created_at').notNull().defaultNow(),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -519,9 +519,9 @@ export const orderRouter = router({
|
|||
}, ${order.address.city}, ${order.address.state} - ${
|
||||
order.address.pincode
|
||||
}, Phone: ${order.address.phone}`,
|
||||
addressId: order.addressId,
|
||||
latitude: order.address.latitude,
|
||||
longitude: order.address.longitude,
|
||||
addressId: order.addressId,
|
||||
latitude: order.address.adminLatitude ?? order.address.latitude,
|
||||
longitude: order.address.adminLongitude ?? order.address.longitude,
|
||||
totalAmount: parseFloat(order.totalAmount),
|
||||
items,
|
||||
deliveryTime: order.slot?.deliveryTime.toISOString() || null,
|
||||
|
|
@ -646,8 +646,8 @@ export const orderRouter = router({
|
|||
const result = await db
|
||||
.update(addresses)
|
||||
.set({
|
||||
latitude,
|
||||
longitude,
|
||||
adminLatitude: latitude,
|
||||
adminLongitude: longitude,
|
||||
})
|
||||
.where(eq(addresses.id, addressId))
|
||||
.returning();
|
||||
|
|
@ -793,9 +793,9 @@ export const orderRouter = router({
|
|||
}, ${order.address.city}, ${order.address.state} - ${
|
||||
order.address.pincode
|
||||
}, Phone: ${order.address.phone}`,
|
||||
addressId: order.addressId,
|
||||
latitude: order.address.latitude,
|
||||
longitude: order.address.longitude,
|
||||
addressId: order.addressId,
|
||||
latitude: order.address.adminLatitude ?? order.address.latitude,
|
||||
longitude: order.address.adminLongitude ?? order.address.longitude,
|
||||
totalAmount: parseFloat(order.totalAmount),
|
||||
deliveryCharge: parseFloat(order.deliveryCharge || "0"),
|
||||
items,
|
||||
|
|
|
|||
|
|
@ -63,8 +63,8 @@ const isDevMode = Constants.executionEnvironment !== "standalone";
|
|||
// const BASE_API_URL = API_URL;
|
||||
// const BASE_API_URL = 'http://10.0.2.2:4000';
|
||||
// const BASE_API_URL = 'http://192.168.100.101:4000';
|
||||
// const BASE_API_URL = 'http://192.168.1.14:4000';
|
||||
let BASE_API_URL = "https://mf.freshyo.in";
|
||||
const BASE_API_URL = 'http://192.168.1.14:4000';
|
||||
// let BASE_API_URL = "https://mf.freshyo.in";
|
||||
// let BASE_API_URL = 'http://192.168.100.103:4000';
|
||||
// let BASE_API_URL = 'http://192.168.29.219:4000';
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue