freshyo/apps/admin-ui/app/(drawer)/delivery-sequences/index.tsx
2026-01-24 00:13:15 +05:30

978 lines
34 KiB
TypeScript

import React, { useState, useEffect, useMemo } from "react";
import {
View,
TouchableOpacity,
Alert,
ActivityIndicator,
Linking,
} from "react-native";
import DraggableFlatList, {
RenderItemParams,
ScaleDecorator,
} from "react-native-draggable-flatlist";
import {
AppContainer,
MyText,
tw,
useManualRefresh,
useMarkDataFetchers,
BottomDialog,
Checkbox,
BottomDropdown,
} from "common-ui";
import { useQueryClient } from "@tanstack/react-query";
import dayjs from "dayjs";
import { useLocalSearchParams, useRouter } from "expo-router";
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";
// Define types outside
interface OrderWithSequence {
sequenceId: number;
readableId: number;
isPackaged: boolean;
totalAmount: number;
address: string;
latitude: number | null;
longitude: number | null;
id: number;
isDelivered: boolean;
addressId: number;
adminNotes?: string | null;
customerName?: string | null;
assignedUserName?: string | null;
}
interface SlotProgressProps {
slotId: number;
orders: any[];
}
const SlotProgress: React.FC<SlotProgressProps> = ({ slotId, orders }) => {
const delivered = orders.filter((o) => o.isDelivered).length;
const undelivered = orders.filter((o) => !o.isDelivered).length;
const packaged = orders.filter((o) => o.isPackaged).length;
const notPackaged = orders.filter((o) => !o.isPackaged).length;
const stats = [
{ label: "Delivered", value: delivered },
{ label: "Undelivered", value: undelivered },
{ label: "Packaged", value: packaged },
{ label: "Not Packaged", value: notPackaged },
];
return (
<View style={tw`bg-white px-4 py-3 border-b border-gray-200 mb-2`}>
<MyText style={tw`text-gray-700 text-sm mb-3`}>
Slot {slotId} Statistics
</MyText>
<View style={tw`flex-row flex-wrap`}>
{stats.map((stat, index) => (
<View key={index} style={tw`w-1/2 p-2`}>
<View style={tw`bg-gray-50 p-3 rounded-lg border border-gray-200`}>
<MyText style={tw`text-gray-900 font-bold text-lg`}>
{stat.value}
</MyText>
<MyText style={tw`text-gray-500 text-xs`}>{stat.label}</MyText>
</View>
</View>
))}
</View>
</View>
);
};
interface OrderItemProps {
item: OrderWithSequence;
drag: () => void;
isActive: boolean;
isPending: boolean;
setSelectedOrder: (order: OrderWithSequence | null) => void;
setShowOrderMenu: (show: boolean) => void;
selectedOrderIds: number[];
onToggleOrder: (id: number) => void;
assignedUserName?: string | null;
staffData?: { staff?: Array<{ id: number; name: string }> };
setSelectedUserId?: (id: number) => void;
}
const OrderItem: React.FC<OrderItemProps> = ({
item,
drag,
isActive,
isPending,
setSelectedOrder,
setShowOrderMenu,
selectedOrderIds,
onToggleOrder,
assignedUserName,
staffData,
setSelectedUserId,
}) => {
const orderItem = item;
return (
<ScaleDecorator>
<TouchableOpacity
onLongPress={drag}
disabled={isPending}
activeOpacity={1}
style={tw`mx-4 my-2`}
>
<View
style={[
tw`bg-white p-4 rounded-xl border`,
isActive
? tw`shadow-xl border-blue-500 z-50`
: tw`shadow-sm border-gray-100`,
]}
>
<View style={tw`flex-row items-center`}>
{/* Checkbox and Drag Handle */}
<View style={tw`mr-4 flex-col items-center`}>
<Checkbox
checked={selectedOrderIds.includes(orderItem.id)}
onPress={() => onToggleOrder(orderItem.id)}
size={20}
fillColor="#3b82f6"
checkColor="#FFFFFF"
/>
<View style={tw`mt-1`}>
<MaterialIcons
name="drag-indicator"
size={24}
color={isActive ? "#3b82f6" : "#9ca3af"}
/>
</View>
</View>
{/* Content */}
<View style={tw`flex-1`}>
<View style={tw`flex-row justify-between items-start mb-2`}>
<View style={tw`flex-row items-center gap-2`}>
<MyText style={tw`font-bold text-gray-800 text-lg`}>
#{orderItem.readableId}
</MyText>
<View style={tw`flex-row items-center gap-1`}>
<MyText style={tw`text-xs font-medium text-gray-600`}>
Pkg
</MyText>
<Checkbox
checked={orderItem.isPackaged}
size={16}
fillColor="#6B7280"
checkColor="#FFFFFF"
/>
</View>
{/* Optional: Add time if available, or just keep ID */}
</View>
<View style={tw`flex-row items-center`}>
<View
style={tw`bg-green-50 px-2 py-1 rounded-lg border border-green-100 mr-2`}
>
<MyText style={tw`font-bold text-green-700 text-sm`}>
{orderItem.totalAmount}
</MyText>
</View>
<TouchableOpacity
onPress={() => {
setSelectedOrder(orderItem);
setShowOrderMenu(true);
}}
style={tw`p-2`}
>
<Entypo
name="dots-three-vertical"
size={20}
color="#6b7280"
/>
</TouchableOpacity>
</View>
</View>
<View style={tw`flex-row items-start`}>
<MaterialIcons
name="location-on"
size={14}
color="#6b7280"
style={tw`mr-1 mt-0.5`}
/>
<MyText
style={tw`text-gray-500 text-xs flex-1`}
numberOfLines={2}
>
{orderItem.address}
</MyText>
{orderItem.latitude && orderItem.longitude && (
<TouchableOpacity
onPress={() => {
Linking.openURL(
`https://www.google.com/maps?q=${orderItem.latitude},${orderItem.longitude}`
);
}}
style={tw`ml-2`}
>
<MaterialIcons name="map" size={16} color="#3b82f6" />
</TouchableOpacity>
)}
</View>
{assignedUserName && (
<View style={tw`mt-2 flex-row items-center`}>
<MyText style={tw`text-xs text-gray-500`}>
Assigned to{" "}
</MyText>
<TouchableOpacity
onPress={() => {
const assignedUserId = staffData?.staff?.find(
(s: { id: number; name: string }) => s.name === assignedUserName
)?.id;
if (assignedUserId && setSelectedUserId) {
setSelectedUserId(assignedUserId);
}
}}
>
<MyText style={tw`text-xs text-blue-600 underline`}>
{assignedUserName}
</MyText>
</TouchableOpacity>
</View>
)}
</View>
</View>
{/* Admin Notes */}
{orderItem.adminNotes && (
<View
style={tw`bg-yellow-50 p-2 rounded-lg border border-yellow-100 mt-2`}
>
<MyText style={tw`text-xs text-yellow-900 leading-4`}>
{orderItem.adminNotes}
</MyText>
</View>
)}
</View>
</TouchableOpacity>
</ScaleDecorator>
);
};
export default function DeliverySequences() {
const { slotId } = useLocalSearchParams();
const selectedSlotId = slotId ? Number(slotId) : null;
const [localOrderedOrders, setLocalOrderedOrders] = useState<
OrderWithSequence[]
>([]);
const [showOrderMenu, setShowOrderMenu] = useState(false);
const [selectedOrder, setSelectedOrder] = useState<OrderWithSequence | null>(
null
);
const [selectedUserId, setSelectedUserId] = useState<number>(-1);
const [selectedOrderIds, setSelectedOrderIds] = useState<number[]>([]);
const [hasSequenceChanged, setHasSequenceChanged] = useState(false);
const router = useRouter();
const { data: slotsData, refetch: refetchSlots } =
trpc.admin.slots.getAll.useQuery();
const {
data: ordersData,
isLoading: ordersLoading,
refetch: refetchOrders,
} = trpc.admin.order.getSlotOrders.useQuery(
{ slotId: String(selectedSlotId) },
{
enabled: !!selectedSlotId,
}
);
const { data: sequenceData, refetch: refetchSequence } =
trpc.admin.slots.getDeliverySequence.useQuery(
{ id: String(selectedSlotId) },
{
enabled: !!selectedSlotId,
}
);
const { data: staffData } = trpc.admin.staffUser.getStaff.useQuery();
// Auto-select first slot if no slotId provided
useEffect(() => {
if (!slotId && slotsData?.slots && slotsData.slots.length > 0) {
router.replace(`/delivery-sequences?slotId=${slotsData.slots[0].id}`);
}
}, [slotId, slotsData, router]);
const updateSequenceMutation =
trpc.admin.slots.updateDeliverySequence.useMutation();
const updatePackagedMutation = trpc.admin.order.updatePackaged.useMutation();
const updateDeliveredMutation =
trpc.admin.order.updateDelivered.useMutation();
const updateAddressCoordsMutation =
trpc.admin.order.updateAddressCoords.useMutation();
// Manual refresh functionality
useManualRefresh(() => {
refetchSlots();
refetchOrders();
refetchSequence();
});
useMarkDataFetchers(() => {
refetchSlots();
refetchOrders();
refetchSequence();
});
const slots = slotsData?.slots || [];
const orders = ordersData?.data || [];
const deliverySequence = sequenceData?.deliverySequence || [];
const slotOptions =
slots?.map((slot) => ({
label: dayjs(slot.deliveryTime).format("ddd DD MMM, h:mm a"),
value: slot.id,
})) || [];
const userOptions = [
{ label: "Unassigned", value: -1 },
{ label: "All Users", value: -2 },
...(staffData?.staff?.map((staff) => ({
label: staff.name,
value: staff.id,
})) || []),
];
// Create ordered orders based on delivery sequence
const computedOrderedOrders = useMemo(() => {
if (orders.length > 0) {
const userSequence =
selectedUserId !== -1
? (deliverySequence as any)?.[String(selectedUserId)] || []
: null;
if (selectedUserId !== -1 && userSequence && userSequence.length > 0) {
// Sort orders according to user's sequence
const sequenceMap = new Map(
userSequence.map((id: number, index: number) => [id, index])
);
let ordered = orders
.filter((order) => userSequence.includes(order.id))
.map((order) => ({ ...order, sequenceId: order.id }))
.sort((a, b) => {
const aIndex = sequenceMap.get(a.sequenceId) ?? Infinity;
const bIndex = sequenceMap.get(b.sequenceId) ?? Infinity;
return aIndex < bIndex ? -1 : aIndex > bIndex ? 1 : 0;
});
return ordered;
} else if (selectedUserId === -1) {
// Show unassigned orders (not in any user's sequence)
const assignedIds = new Set(
Object.values(deliverySequence as any).flat()
);
let ordered = orders
.filter((order) => !assignedIds.has(order.id))
.map((order) => ({ ...order, sequenceId: order.id }))
.sort((a, b) =>
a.sequenceId < b.sequenceId
? -1
: a.sequenceId > b.sequenceId
? 1
: 0
);
return ordered;
} else if (selectedUserId === -2) {
// Show all orders with assignment info
const orderToUserMap = new Map<number, number>();
Object.entries(deliverySequence as any).forEach(([userId, orderIds]) => {
(orderIds as number[]).forEach((orderId) => {
orderToUserMap.set(orderId, parseInt(userId));
});
});
let ordered = orders
.map((order) => {
const assignedUserId = orderToUserMap.get(order.id);
const assignedUser = staffData?.staff?.find(
(s) => s.id === assignedUserId
);
return {
...order,
sequenceId: order.id,
assignedUserName: assignedUser?.name || null,
};
})
.sort((a, b) =>
a.sequenceId < b.sequenceId
? -1
: a.sequenceId > b.sequenceId
? 1
: 0
);
return ordered;
} else {
// No sequence for user, show empty
return [];
}
} else {
return [];
}
}, [ordersData, sequenceData, selectedUserId]);
// Sync local state with computed orders
useEffect(() => {
setLocalOrderedOrders(computedOrderedOrders);
}, [computedOrderedOrders]);
const handleDragEnd = ({ data }: { data: OrderWithSequence[] }) => {
if (selectedUserId !== -1 && selectedUserId !== -2) {
setLocalOrderedOrders(data);
setHasSequenceChanged(true);
}
};
const handleSaveSequence = () => {
if (!selectedSlotId || selectedUserId === -1) return;
const newSequence = { ...(deliverySequence as any) };
newSequence[String(selectedUserId)] = localOrderedOrders.map(
(order) => order.sequenceId
);
updateSequenceMutation.mutate(
{
id: selectedSlotId,
deliverySequence: newSequence,
},
{
onSuccess: () => {
setHasSequenceChanged(false);
Alert.alert("Success", "Delivery sequence updated successfully");
},
onError: (error: any) => {
Alert.alert(
"Error",
`Failed to update delivery sequence: ${
error.message || "Unknown error"
}`
);
},
}
);
};
if (!slotsData) {
return (
<View style={tw`flex-1 justify-center items-center bg-gray-50 pt-6`}>
<ActivityIndicator size="large" color="#3b82f6" />
<MyText style={tw`text-gray-500 mt-4`}>Loading slots...</MyText>
</View>
);
}
if (slotsData.slots.length === 0) {
return (
<View style={tw`flex-1 justify-center items-center p-8 pt-6`}>
<MaterialIcons name="event-busy" size={64} color="#e5e7eb" />
<MyText style={tw`text-gray-500 mt-4 text-center text-lg`}>
No slots available
</MyText>
</View>
);
}
return (
<View style={tw`flex-1 bg-gray-50 pt-6`}>
{selectedSlotId ? (
<>
{/* Header Section */}
<View
style={tw`bg-white px-4 py-4 border-b border-gray-200 flex-row items-center shadow-sm z-10`}
>
<TouchableOpacity
onPress={() => router.back()}
style={tw`p-2 -ml-4`}
accessibilityLabel="Go back"
>
<MaterialIcons name="chevron-left" size={24} color="#374151" />
</TouchableOpacity>
<View style={tw`flex-2 mx-2`}>
<BottomDropdown
label="Select Slot"
options={slotOptions}
value={selectedSlotId || ""}
onValueChange={(val) => {
if (val) {
router.replace(`/delivery-sequences?slotId=${val}`);
}
}}
placeholder="Select slot"
/>
</View>
<View style={tw`flex-1 mx-2`}>
<BottomDropdown
label="Select User"
options={userOptions}
value={selectedUserId}
onValueChange={(val) => setSelectedUserId(val as number)}
placeholder="Select user"
/>
</View>
</View>
{/* Content Section */}
{ordersLoading ? (
<View style={tw`flex-1 justify-center items-center`}>
<ActivityIndicator size="large" color="#3b82f6" />
<MyText style={tw`text-gray-500 mt-4`}>Loading orders...</MyText>
</View>
) : localOrderedOrders.length === 0 ? (
<View style={tw`flex-1 justify-center items-center p-8`}>
<MaterialIcons name="assignment-late" size={64} color="#e5e7eb" />
<MyText style={tw`text-gray-500 mt-4 text-center text-lg`}>
No orders found for this slot
</MyText>
</View>
) : (
<View style={tw`flex-1`}>
{/* <View style={tw`bg-blue-50 px-4 py-2 mb-2`}>
<MyText style={tw`text-blue-700 text-xs text-center`}>
Long press an item to drag and reorder
</MyText>
</View> */}
<DraggableFlatList
data={localOrderedOrders}
renderItem={({ item, drag, isActive }) => (
<OrderItem
item={item}
drag={drag}
isActive={isActive}
isPending={updateSequenceMutation.isPending}
setSelectedOrder={setSelectedOrder}
setShowOrderMenu={setShowOrderMenu}
selectedOrderIds={selectedOrderIds}
onToggleOrder={(id) => {
setSelectedOrderIds((prev) =>
prev.includes(id)
? prev.filter((i) => i !== id)
: [...prev, id]
);
}}
assignedUserName={selectedUserId === -2 ? item.assignedUserName : undefined}
staffData={staffData}
setSelectedUserId={setSelectedUserId}
/>
)}
keyExtractor={(item) => item.sequenceId.toString()}
onDragEnd={handleDragEnd}
showsVerticalScrollIndicator={false}
contentContainerStyle={tw`pb-8`}
ListHeaderComponent={
<SlotProgress slotId={selectedSlotId!} orders={orders} />
}
/>
</View>
)}
{/* FAB for Assignment */}
{selectedOrderIds.length > 0 && (
<View style={tw`absolute bottom-4 right-4`}>
<BottomDropdown
label="Assign To"
options={[
...userOptions.filter((opt) => opt.value !== -1),
{ label: "None", value: -3 },
]}
value={-1}
onValueChange={(val) => {
if (val !== -1) {
if (val === -3) {
// Unassign selected orders
const newSequence = { ...(deliverySequence as any) };
Object.keys(newSequence).forEach((userId) => {
newSequence[userId] = newSequence[userId].filter(
(id: number) => !selectedOrderIds.includes(id)
);
});
// Save
updateSequenceMutation.mutate(
{
id: selectedSlotId,
deliverySequence: newSequence,
},
{
onSuccess: () => {
setSelectedOrderIds([]);
refetchSequence();
Alert.alert(
"Success",
"Orders unassigned successfully"
);
},
onError: (error: any) => {
Alert.alert(
"Error",
`Failed to unassign orders: ${
error.message || "Unknown error"
}`
);
},
}
);
} else {
// Assign selected orders to the user
// Update deliverySequence map
const newSequence = { ...(deliverySequence as any) };
// Remove from all other users
Object.keys(newSequence).forEach((userId) => {
newSequence[userId] = newSequence[userId].filter(
(id: number) => !selectedOrderIds.includes(id)
);
});
// Add to selected user
if (!newSequence[String(val)])
newSequence[String(val)] = [];
newSequence[String(val)] = [
...new Set([
...newSequence[String(val)],
...selectedOrderIds,
]),
];
// Save
updateSequenceMutation.mutate(
{
id: selectedSlotId,
deliverySequence: newSequence,
},
{
onSuccess: () => {
setSelectedOrderIds([]);
refetchSequence();
Alert.alert(
"Success",
"Orders assigned successfully"
);
},
onError: (error: any) => {
Alert.alert(
"Error",
`Failed to assign orders: ${
error.message || "Unknown error"
}`
);
},
}
);
}
}
}}
triggerComponent={({ onPress }) => (
<TouchableOpacity
onPress={onPress}
style={tw`bg-blue-600 p-4 rounded-full shadow-lg`}
>
<MaterialIcons name="person-add" size={24} color="white" />
</TouchableOpacity>
)}
/>
</View>
)}
{/* FAB for Save */}
{hasSequenceChanged && (
<View style={tw`absolute bottom-4 left-4`}>
<TouchableOpacity
onPress={handleSaveSequence}
style={tw`bg-blue-600 p-4 rounded-full shadow-lg`}
disabled={updateSequenceMutation.isPending}
accessibilityLabel="Save sequence"
>
<MaterialIcons name="save" size={24} color="white" />
</TouchableOpacity>
</View>
)}
</>
) : (
<View style={tw`flex-1 justify-center items-center p-8`}>
<MaterialIcons name="event-busy" size={64} color="#e5e7eb" />
<MyText style={tw`text-gray-500 mt-4 text-center text-lg`}>
No slot selected. Please select a slot to view delivery sequence.
</MyText>
<TouchableOpacity
style={tw`mt-6 bg-blue-600 px-6 py-3 rounded-full`}
onPress={() => router.back()}
>
<MyText style={tw`text-white font-bold`}>Go Back</MyText>
</TouchableOpacity>
</View>
)}
{/* Order Menu Dialog */}
<BottomDialog
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,
});
refetchOrders();
refetchSequence();
setShowOrderMenu(false);
} catch (error) {
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,
});
refetchOrders();
refetchSequence();
setShowOrderMenu(false);
} catch (error) {
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>
<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>
<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>
</View>
);
}