Compare commits
No commits in common. "1dca7a34548c9f8d386f4bf07fdd7d68b244ec03" and "10d13408d38bc569f8c7d91415fed4eca23d039a" have entirely different histories.
1dca7a3454
...
10d13408d3
31 changed files with 219 additions and 4367 deletions
|
|
@ -1,58 +1,25 @@
|
||||||
import React, { useState, useCallback, useMemo } from "react";
|
import React, { useState } from "react";
|
||||||
import { View, TouchableOpacity, Alert, ActivityIndicator } from "react-native";
|
import { View, Text, TouchableOpacity, Alert } from "react-native";
|
||||||
import { MaterialIcons } from "@expo/vector-icons";
|
import { tw, ConfirmationDialog, MyText, MyFlatList, useMarkDataFetchers, usePagination, ImageViewerURI } from "common-ui";
|
||||||
import { useRouter } from "expo-router";
|
|
||||||
import { tw, ConfirmationDialog, MyText, MyFlatList, useMarkDataFetchers, ImageViewerURI } from "common-ui";
|
|
||||||
import { trpc } from "@/src/trpc-client";
|
import { trpc } from "@/src/trpc-client";
|
||||||
|
|
||||||
export default function Complaints() {
|
export default function Complaints() {
|
||||||
const router = useRouter();
|
const { currentPage, pageSize, PaginationComponent } = usePagination(5); // 5 complaints per page for testing
|
||||||
const [dialogOpen, setDialogOpen] = useState(false);
|
const { data, isLoading, error, refetch } = trpc.admin.complaint.getAll.useQuery({
|
||||||
const [selectedComplaintId, setSelectedComplaintId] = useState<number | null>(null);
|
page: currentPage,
|
||||||
|
limit: pageSize,
|
||||||
const {
|
});
|
||||||
data,
|
const resolveComplaint = trpc.admin.complaint.resolve.useMutation();
|
||||||
isLoading,
|
|
||||||
isError,
|
|
||||||
error,
|
|
||||||
fetchNextPage,
|
|
||||||
hasNextPage,
|
|
||||||
isFetchingNextPage,
|
|
||||||
refetch,
|
|
||||||
} = trpc.admin.complaint.getAll.useInfiniteQuery(
|
|
||||||
{ limit: 20 },
|
|
||||||
{
|
|
||||||
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
useMarkDataFetchers(() => {
|
useMarkDataFetchers(() => {
|
||||||
refetch();
|
refetch();
|
||||||
});
|
});
|
||||||
|
|
||||||
const resolveComplaint = trpc.admin.complaint.resolve.useMutation();
|
const [dialogOpen, setDialogOpen] = useState(false);
|
||||||
|
const [selectedComplaintId, setSelectedComplaintId] = useState<number | null>(null);
|
||||||
|
|
||||||
const complaints = useMemo(() => {
|
const complaints = data?.complaints || [];
|
||||||
const allComplaints = data?.pages.flatMap((page) => page.complaints) || [];
|
const totalCount = data?.totalCount || 0;
|
||||||
return allComplaints.filter(
|
|
||||||
(complaint, index, self) =>
|
|
||||||
index === self.findIndex((c) => c.id === complaint.id)
|
|
||||||
);
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
const handleLoadMore = useCallback(() => {
|
|
||||||
if (hasNextPage && !isFetchingNextPage) {
|
|
||||||
fetchNextPage();
|
|
||||||
}
|
|
||||||
}, [hasNextPage, isFetchingNextPage, fetchNextPage]);
|
|
||||||
|
|
||||||
const handleUserPress = useCallback((userId: number) => {
|
|
||||||
router.push(`/(drawer)/user-management/${userId}`);
|
|
||||||
}, [router]);
|
|
||||||
|
|
||||||
const handleOrderPress = useCallback((orderId: number) => {
|
|
||||||
router.push(`/(drawer)/manage-orders/order-details/${orderId}`);
|
|
||||||
}, [router]);
|
|
||||||
|
|
||||||
const handleMarkResolved = (id: number) => {
|
const handleMarkResolved = (id: number) => {
|
||||||
setSelectedComplaintId(id);
|
setSelectedComplaintId(id);
|
||||||
|
|
@ -85,72 +52,35 @@ export default function Complaints() {
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<View style={tw`flex-1 justify-center items-center bg-gray-50`}>
|
<View style={tw`flex-1 justify-center items-center`}>
|
||||||
<ActivityIndicator size="large" color="#3B82F6" />
|
<MyText style={tw`text-gray-600`}>Loading complaints...</MyText>
|
||||||
<MyText style={tw`text-gray-500 mt-4`}>Loading complaints...</MyText>
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isError) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<View style={tw`flex-1 justify-center items-center bg-gray-50 p-6`}>
|
<View style={tw`flex-1 justify-center items-center`}>
|
||||||
<MaterialIcons name="error-outline" size={48} color="#EF4444" />
|
<MyText style={tw`text-red-600`}>Error loading complaints</MyText>
|
||||||
<MyText style={tw`text-gray-900 text-lg font-bold mt-4`}>Error</MyText>
|
|
||||||
<MyText style={tw`text-gray-500 text-center mt-2 mb-6`}>
|
|
||||||
{error?.message || "Failed to load complaints"}
|
|
||||||
</MyText>
|
|
||||||
<TouchableOpacity
|
|
||||||
onPress={() => refetch()}
|
|
||||||
style={tw`bg-blue-600 px-6 py-3 rounded-full`}
|
|
||||||
>
|
|
||||||
<MyText style={tw`text-white font-semibold`}>Retry</MyText>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={tw`flex-1 bg-gray-50`}>
|
<View style={tw`flex-1`}>
|
||||||
<MyFlatList
|
<MyFlatList
|
||||||
style={tw`flex-1`}
|
style={tw`flex-1 bg-white`}
|
||||||
contentContainerStyle={tw`px-4 py-4`}
|
contentContainerStyle={tw`px-4 pb-6`}
|
||||||
data={complaints}
|
data={complaints}
|
||||||
keyExtractor={(item) => item.id.toString()}
|
keyExtractor={(item) => item.id.toString()}
|
||||||
onEndReached={handleLoadMore}
|
|
||||||
onEndReachedThreshold={0.5}
|
|
||||||
renderItem={({ item }) => (
|
renderItem={({ item }) => (
|
||||||
<View style={tw`bg-white p-4 mb-4 rounded-2xl shadow-sm border border-gray-100`}>
|
<View style={tw`bg-white p-4 mb-4 rounded-2xl shadow-lg`}>
|
||||||
<View style={tw`flex-row justify-between items-start mb-2`}>
|
<MyText style={tw`text-lg font-bold mb-2 text-gray-800`}>Complaint #{item.id}</MyText>
|
||||||
<MyText style={tw`text-lg font-bold text-gray-900`}>
|
<MyText style={tw`text-base mb-2 text-gray-700`}>{item.text}</MyText>
|
||||||
Complaint #{item.id}
|
|
||||||
</MyText>
|
|
||||||
<View
|
|
||||||
style={tw`px-2.5 py-1 rounded-full ${
|
|
||||||
item.status === "resolved"
|
|
||||||
? "bg-green-100 border border-green-200"
|
|
||||||
: "bg-amber-100 border border-amber-200"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<MyText
|
|
||||||
style={tw`text-xs font-semibold ${
|
|
||||||
item.status === "resolved" ? "text-green-700" : "text-amber-700"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{item.status === "resolved" ? "Resolved" : "Pending"}
|
|
||||||
</MyText>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<MyText style={tw`text-base text-gray-700 mb-3 leading-5`}>
|
|
||||||
{item.text}
|
|
||||||
</MyText>
|
|
||||||
|
|
||||||
{item.images && item.images.length > 0 && (
|
{item.images && item.images.length > 0 && (
|
||||||
<View style={tw`mb-3`}>
|
<View style={tw`mt-3 mb-3`}>
|
||||||
<MyText style={tw`text-sm font-semibold text-gray-700 mb-2`}>
|
<MyText style={tw`text-sm font-semibold text-gray-700 mb-2`}>Attached Images:</MyText>
|
||||||
Attached Images:
|
|
||||||
</MyText>
|
|
||||||
<View style={tw`flex-row flex-wrap gap-2`}>
|
<View style={tw`flex-row flex-wrap gap-2`}>
|
||||||
{item.images.map((imageUri: string, index: number) => (
|
{item.images.map((imageUri: string, index: number) => (
|
||||||
<ImageViewerURI
|
<ImageViewerURI
|
||||||
|
|
@ -163,64 +93,53 @@ export default function Complaints() {
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<View style={tw`flex-row items-center gap-2 mb-3`}>
|
<View style={tw`flex-row items-center mb-2`}>
|
||||||
<MaterialIcons name="person" size={14} color="#6B7280" />
|
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => item.userId && handleUserPress(item.userId)}
|
onPress={() =>
|
||||||
|
Alert.alert("User Page", "User page coming soon")
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<MyText style={tw`text-sm text-blue-600 underline`}>
|
<MyText style={tw`text-sm text-blue-600 underline`}>
|
||||||
{item.userName || item.userMobile || "Unknown User"}
|
{item.userName}
|
||||||
</MyText>
|
</MyText>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
<MyText style={tw`text-sm text-gray-600 mx-2`}>|</MyText>
|
||||||
{item.orderId && (
|
{item.orderId && (
|
||||||
<>
|
|
||||||
<MyText style={tw`text-sm text-gray-400`}>|</MyText>
|
|
||||||
<MaterialIcons name="shopping-bag" size={14} color="#6B7280" />
|
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => item.orderId && handleOrderPress(item.orderId)}
|
onPress={() =>
|
||||||
|
Alert.alert("Order Page", "Order page coming soon")
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<MyText style={tw`text-sm text-blue-600 underline`}>
|
<MyText style={tw`text-sm text-blue-600 underline`}>
|
||||||
Order #{item.orderId}
|
Order #{item.orderId}
|
||||||
</MyText>
|
</MyText>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
<MyText
|
||||||
|
style={tw`text-sm ${
|
||||||
|
item.status === "resolved" ? "text-green-600" : "text-red-600"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Status: {item.status}
|
||||||
|
</MyText>
|
||||||
{item.status === "pending" && (
|
{item.status === "pending" && (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => handleMarkResolved(item.id)}
|
onPress={() => handleMarkResolved(item.id)}
|
||||||
style={tw`bg-blue-500 py-3 rounded-xl items-center shadow-sm mt-2`}
|
style={tw`mt-2 bg-blue-500 p-3 rounded-lg shadow-md`}
|
||||||
>
|
>
|
||||||
<MyText style={tw`text-white font-semibold`}>
|
<MyText style={tw`text-white text-center font-semibold`}>Mark as Resolved</MyText>
|
||||||
Resolve Complaint
|
|
||||||
</MyText>
|
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
ListEmptyComponent={
|
ListEmptyComponent={
|
||||||
<View style={tw`flex-1 justify-center items-center py-20`}>
|
<View style={tw`flex-1 justify-center items-center py-10`}>
|
||||||
<View style={tw`bg-white p-6 rounded-full shadow-sm mb-4`}>
|
<MyText style={tw`text-gray-500 text-center`}>No complaints found</MyText>
|
||||||
<MaterialIcons name="inbox" size={48} color="#D1D5DB" />
|
|
||||||
</View>
|
</View>
|
||||||
<MyText style={tw`text-gray-900 text-lg font-bold`}>
|
|
||||||
No complaints
|
|
||||||
</MyText>
|
|
||||||
<MyText style={tw`text-gray-500 text-center mt-2`}>
|
|
||||||
All complaints will appear here
|
|
||||||
</MyText>
|
|
||||||
</View>
|
|
||||||
}
|
|
||||||
ListFooterComponent={
|
|
||||||
isFetchingNextPage ? (
|
|
||||||
<View style={tw`py-4 items-center flex-row justify-center`}>
|
|
||||||
<ActivityIndicator size="small" color="#3B82F6" />
|
|
||||||
<MyText style={tw`text-gray-500 ml-2`}>Loading more...</MyText>
|
|
||||||
</View>
|
|
||||||
) : null
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<PaginationComponent totalCount={totalCount} />
|
||||||
<ConfirmationDialog
|
<ConfirmationDialog
|
||||||
open={dialogOpen}
|
open={dialogOpen}
|
||||||
positiveAction={handleConfirmResolve}
|
positiveAction={handleConfirmResolve}
|
||||||
|
|
@ -229,11 +148,10 @@ export default function Complaints() {
|
||||||
setDialogOpen(false);
|
setDialogOpen(false);
|
||||||
setSelectedComplaintId(null);
|
setSelectedComplaintId(null);
|
||||||
}}
|
}}
|
||||||
title="Resolve Complaint"
|
title="Mark as Resolved"
|
||||||
message="Add admin notes for this resolution:"
|
message="Add admin notes for this resolution:"
|
||||||
confirmText="Resolve"
|
confirmText="Resolve"
|
||||||
cancelText="Cancel"
|
cancelText="Cancel"
|
||||||
isLoading={resolveComplaint.isPending}
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -82,16 +82,6 @@ export default function OrderDetails() {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const removeDeliveryChargeMutation = trpc.admin.order.removeDeliveryCharge.useMutation({
|
|
||||||
onSuccess: () => {
|
|
||||||
Alert.alert("Success", "Delivery charge has been removed");
|
|
||||||
refetch();
|
|
||||||
},
|
|
||||||
onError: (error: any) => {
|
|
||||||
Alert.alert("Error", error.message || "Failed to remove delivery charge");
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<View style={tw`flex-1 justify-center items-center bg-gray-50`}>
|
<View style={tw`flex-1 justify-center items-center bg-gray-50`}>
|
||||||
|
|
@ -277,23 +267,6 @@ export default function OrderDetails() {
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Cancellation Reason */}
|
|
||||||
{order.status === "cancelled" && order.cancelReason && (
|
|
||||||
<View
|
|
||||||
style={tw`bg-red-50 p-5 rounded-2xl border border-red-100 mb-4`}
|
|
||||||
>
|
|
||||||
<View style={tw`flex-row items-center mb-2`}>
|
|
||||||
<MaterialIcons name="cancel" size={18} color="#DC2626" />
|
|
||||||
<MyText style={tw`text-sm font-bold text-red-800 ml-2`}>
|
|
||||||
Cancellation Reason
|
|
||||||
</MyText>
|
|
||||||
</View>
|
|
||||||
<MyText style={tw`text-sm text-red-900 leading-5`}>
|
|
||||||
{order.cancelReason}
|
|
||||||
</MyText>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Order Progress (Simplified Timeline) */}
|
{/* Order Progress (Simplified Timeline) */}
|
||||||
{order.status !== "cancelled" && (
|
{order.status !== "cancelled" && (
|
||||||
<View
|
<View
|
||||||
|
|
@ -518,40 +491,6 @@ export default function OrderDetails() {
|
||||||
-₹{discountAmount}
|
-₹{discountAmount}
|
||||||
</MyText>
|
</MyText>
|
||||||
</View>
|
</View>
|
||||||
)}
|
|
||||||
{order.deliveryCharge > 0 && (
|
|
||||||
<View style={tw`flex-row justify-between items-center mb-2`}>
|
|
||||||
<View style={tw`flex-row items-center`}>
|
|
||||||
<MyText style={tw`text-gray-600 font-medium`}>
|
|
||||||
Delivery Charge
|
|
||||||
</MyText>
|
|
||||||
<TouchableOpacity
|
|
||||||
onPress={() => {
|
|
||||||
Alert.alert(
|
|
||||||
'Remove Delivery Cost',
|
|
||||||
'Are you sure you want to remove the delivery cost from this order?',
|
|
||||||
[
|
|
||||||
{ text: 'Cancel', style: 'cancel' },
|
|
||||||
{
|
|
||||||
text: 'Remove',
|
|
||||||
style: 'destructive',
|
|
||||||
onPress: () => removeDeliveryChargeMutation.mutate({ orderId: order.id }),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
disabled={removeDeliveryChargeMutation.isPending}
|
|
||||||
style={tw`ml-2 px-2 py-1 bg-red-100 rounded-md`}
|
|
||||||
>
|
|
||||||
<MyText style={tw`text-xs font-bold text-red-600`}>
|
|
||||||
{removeDeliveryChargeMutation.isPending ? 'Removing...' : 'Remove'}
|
|
||||||
</MyText>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
<MyText style={tw`text-gray-600 font-medium`}>
|
|
||||||
₹{order.deliveryCharge}
|
|
||||||
</MyText>
|
|
||||||
</View>
|
|
||||||
)}
|
)}
|
||||||
<View style={tw`flex-row justify-between items-center pt-2 border-t border-gray-200`}>
|
<View style={tw`flex-row justify-between items-center pt-2 border-t border-gray-200`}>
|
||||||
<View style={tw`flex-row items-center`}>
|
<View style={tw`flex-row items-center`}>
|
||||||
|
|
|
||||||
|
|
@ -57,9 +57,6 @@ interface OrderType {
|
||||||
readableId: number;
|
readableId: number;
|
||||||
customerName: string | null;
|
customerName: string | null;
|
||||||
address: string;
|
address: string;
|
||||||
addressId: number;
|
|
||||||
latitude: number | null;
|
|
||||||
longitude: number | null;
|
|
||||||
totalAmount: number;
|
totalAmount: number;
|
||||||
deliveryCharge: number;
|
deliveryCharge: number;
|
||||||
items: {
|
items: {
|
||||||
|
|
@ -362,11 +359,11 @@ const OrderItem = ({ order, refetch }: { order: OrderType; refetch: () => void }
|
||||||
isDelivered: order.isDelivered,
|
isDelivered: order.isDelivered,
|
||||||
isFlashDelivery: order.isFlashDelivery,
|
isFlashDelivery: order.isFlashDelivery,
|
||||||
address: order.address,
|
address: order.address,
|
||||||
addressId: order.addressId,
|
addressId: 0,
|
||||||
adminNotes: order.adminNotes,
|
adminNotes: order.adminNotes,
|
||||||
userNotes: order.userNotes,
|
userNotes: order.userNotes,
|
||||||
latitude: order.latitude,
|
latitude: null,
|
||||||
longitude: order.longitude,
|
longitude: null,
|
||||||
status: order.status,
|
status: order.status,
|
||||||
}}
|
}}
|
||||||
onViewDetails={handleMenuOption}
|
onViewDetails={handleMenuOption}
|
||||||
|
|
@ -380,7 +377,7 @@ const OrderItem = ({ order, refetch }: { order: OrderType; refetch: () => void }
|
||||||
setMenuOpen(false);
|
setMenuOpen(false);
|
||||||
setCancelDialogOpen(true);
|
setCancelDialogOpen(true);
|
||||||
}}
|
}}
|
||||||
onAttachLocation={() => refetch()}
|
onAttachLocation={() => {}}
|
||||||
onWhatsApp={() => {}}
|
onWhatsApp={() => {}}
|
||||||
onDial={() => {}}
|
onDial={() => {}}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { MaterialCommunityIcons, Entypo } from '@expo/vector-icons';
|
import { MaterialCommunityIcons, Entypo } from '@expo/vector-icons';
|
||||||
import { View, TouchableOpacity, FlatList, Alert, ActivityIndicator } from 'react-native';
|
import { View, TouchableOpacity, FlatList, Alert } from 'react-native';
|
||||||
import { AppContainer, MyText, tw, MyFlatList , BottomDialog, MyTouchableOpacity, Checkbox } from 'common-ui';
|
import { AppContainer, MyText, tw, MyFlatList , BottomDialog, MyTouchableOpacity } from 'common-ui';
|
||||||
import { trpc } from '@/src/trpc-client';
|
import { trpc } from '@/src/trpc-client';
|
||||||
import { useRouter } from 'expo-router';
|
import { useRouter } from 'expo-router';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
@ -12,7 +12,6 @@ interface SlotItemProps {
|
||||||
router: any;
|
router: any;
|
||||||
setDialogProducts: React.Dispatch<React.SetStateAction<any[]>>;
|
setDialogProducts: React.Dispatch<React.SetStateAction<any[]>>;
|
||||||
setDialogOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
setDialogOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
refetch: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const SlotItemComponent: React.FC<SlotItemProps> = ({
|
const SlotItemComponent: React.FC<SlotItemProps> = ({
|
||||||
|
|
@ -20,7 +19,6 @@ const SlotItemComponent: React.FC<SlotItemProps> = ({
|
||||||
router,
|
router,
|
||||||
setDialogProducts,
|
setDialogProducts,
|
||||||
setDialogOpen,
|
setDialogOpen,
|
||||||
refetch,
|
|
||||||
}) => {
|
}) => {
|
||||||
const [menuOpen, setMenuOpen] = useState(false);
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
const slotProducts = slot.products?.map((p: any) => p.name).filter(Boolean) || [];
|
const slotProducts = slot.products?.map((p: any) => p.name).filter(Boolean) || [];
|
||||||
|
|
@ -30,29 +28,6 @@ const SlotItemComponent: React.FC<SlotItemProps> = ({
|
||||||
const statusColor = isActive ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700';
|
const statusColor = isActive ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700';
|
||||||
const statusText = isActive ? 'Active' : 'Inactive';
|
const statusText = isActive ? 'Active' : 'Inactive';
|
||||||
|
|
||||||
const updateSlotCapacity = trpc.admin.slots.updateSlotCapacity.useMutation();
|
|
||||||
|
|
||||||
const handleCapacityToggle = () => {
|
|
||||||
updateSlotCapacity.mutate(
|
|
||||||
{ slotId: slot.id, isCapacityFull: !slot.isCapacityFull },
|
|
||||||
{
|
|
||||||
onSuccess: () => {
|
|
||||||
setMenuOpen(false);
|
|
||||||
refetch();
|
|
||||||
Alert.alert(
|
|
||||||
'Success',
|
|
||||||
slot.isCapacityFull
|
|
||||||
? 'Slot capacity reset. It will now be visible to users.'
|
|
||||||
: 'Slot marked as full capacity. It will be hidden from users.'
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onError: (error: any) => {
|
|
||||||
Alert.alert('Error', error.message || 'Failed to update slot capacity');
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => router.push(`/(drawer)/slots/slot-details?slotId=${slot.id}`)}
|
onPress={() => router.push(`/(drawer)/slots/slot-details?slotId=${slot.id}`)}
|
||||||
|
|
@ -83,11 +58,6 @@ const SlotItemComponent: React.FC<SlotItemProps> = ({
|
||||||
<View style={tw`px-3 py-1 rounded-full ${statusColor.split(' ')[0]}`}>
|
<View style={tw`px-3 py-1 rounded-full ${statusColor.split(' ')[0]}`}>
|
||||||
<MyText style={tw`text-xs font-bold ${statusColor.split(' ')[1]}`}>{statusText}</MyText>
|
<MyText style={tw`text-xs font-bold ${statusColor.split(' ')[1]}`}>{statusText}</MyText>
|
||||||
</View>
|
</View>
|
||||||
{slot.isCapacityFull && (
|
|
||||||
<View style={tw`px-2 py-1 rounded-full bg-red-500 ml-2`}>
|
|
||||||
<MyText style={tw`text-xs font-bold text-white`}>FULL</MyText>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => setMenuOpen(true)}
|
onPress={() => setMenuOpen(true)}
|
||||||
style={tw`ml-2 p-1`}
|
style={tw`ml-2 p-1`}
|
||||||
|
|
@ -102,48 +72,6 @@ const SlotItemComponent: React.FC<SlotItemProps> = ({
|
||||||
<BottomDialog open={menuOpen} onClose={() => setMenuOpen(false)}>
|
<BottomDialog open={menuOpen} onClose={() => setMenuOpen(false)}>
|
||||||
<View style={tw`p-4`}>
|
<View style={tw`p-4`}>
|
||||||
<MyText style={tw`text-lg font-bold mb-4`}>Slot #{slot.id} Actions</MyText>
|
<MyText style={tw`text-lg font-bold mb-4`}>Slot #{slot.id} Actions</MyText>
|
||||||
|
|
||||||
{/* Capacity Toggle */}
|
|
||||||
<TouchableOpacity
|
|
||||||
onPress={handleCapacityToggle}
|
|
||||||
disabled={updateSlotCapacity.isPending}
|
|
||||||
style={tw`py-4 border-b border-gray-200`}
|
|
||||||
>
|
|
||||||
<View style={tw`flex-row items-center justify-between`}>
|
|
||||||
<View style={tw`flex-row items-center flex-1`}>
|
|
||||||
{updateSlotCapacity.isPending ? (
|
|
||||||
<ActivityIndicator size="small" color="#EF4444" style={tw`mr-3`} />
|
|
||||||
) : (
|
|
||||||
<MaterialCommunityIcons
|
|
||||||
name={slot.isCapacityFull ? "package-variant-closed" : "package-variant"}
|
|
||||||
size={20}
|
|
||||||
color={slot.isCapacityFull ? "#EF4444" : "#4B5563"}
|
|
||||||
style={tw`mr-3`}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<View>
|
|
||||||
<MyText style={tw`text-base text-gray-800`}>Mark as Full Capacity</MyText>
|
|
||||||
<MyText style={tw`text-xs text-gray-500 mt-0.5`}>
|
|
||||||
{slot.isCapacityFull
|
|
||||||
? "Slot is hidden from users"
|
|
||||||
: "Hidden from users when full"}
|
|
||||||
</MyText>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
{updateSlotCapacity.isPending ? (
|
|
||||||
<ActivityIndicator size="small" color="#EF4444" />
|
|
||||||
) : (
|
|
||||||
<Checkbox
|
|
||||||
checked={slot.isCapacityFull}
|
|
||||||
onPress={handleCapacityToggle}
|
|
||||||
size={22}
|
|
||||||
fillColor="#EF4444"
|
|
||||||
checkColor="#FFFFFF"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
</TouchableOpacity>
|
|
||||||
|
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
setMenuOpen(false);
|
setMenuOpen(false);
|
||||||
|
|
@ -265,7 +193,6 @@ export default function Slots() {
|
||||||
router={router}
|
router={router}
|
||||||
setDialogProducts={setDialogProducts}
|
setDialogProducts={setDialogProducts}
|
||||||
setDialogOpen={setDialogOpen}
|
setDialogOpen={setDialogOpen}
|
||||||
refetch={refetch}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
contentContainerStyle={tw`p-4`}
|
contentContainerStyle={tw`p-4`}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
ENV_MODE=PROD
|
ENV_MODE=PROD
|
||||||
# DATABASE_URL=postgresql://postgres:meatfarmer_master_password@57.128.212.174:7447/meatfarmer #technocracy
|
DATABASE_URL=postgresql://postgres:meatfarmer_master_password@57.128.212.174:7447/meatfarmer #technocracy
|
||||||
DATABASE_URL=postgres://postgres:meatfarmer_master_password@5.223.55.14:7447/meatfarmer #hetzner
|
# DATABASE_URL=postgres://postgres:meatfarmer_master_password@5.223.55.14:7447/meatfarmer #hetzner
|
||||||
PHONE_PE_BASE_URL=https://api-preprod.phonepe.com/
|
PHONE_PE_BASE_URL=https://api-preprod.phonepe.com/
|
||||||
PHONE_PE_CLIENT_ID=TEST-M23F2IGP34ZAR_25090
|
PHONE_PE_CLIENT_ID=TEST-M23F2IGP34ZAR_25090
|
||||||
PHONE_PE_CLIENT_VERSION=1
|
PHONE_PE_CLIENT_VERSION=1
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
This is a demo file.
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 27 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB |
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
||||||
ALTER TABLE "mf"."delivery_slot_info" ADD COLUMN "is_capacity_full" boolean DEFAULT false NOT NULL;
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -519,13 +519,6 @@
|
||||||
"when": 1770561175889,
|
"when": 1770561175889,
|
||||||
"tag": "0073_faithful_gravity",
|
"tag": "0073_faithful_gravity",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 74,
|
|
||||||
"version": "7",
|
|
||||||
"when": 1771674555093,
|
|
||||||
"tag": "0074_outgoing_black_cat",
|
|
||||||
"breakpoints": true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -163,15 +163,6 @@ if (fs.existsSync(fallbackUiIndex)) {
|
||||||
console.warn(`Fallback UI build not found at ${fallbackUiIndex}`)
|
console.warn(`Fallback UI build not found at ${fallbackUiIndex}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serve /assets/public folder at /assets route
|
|
||||||
const assetsPublicDir = path.resolve(__dirname, './assets/public');
|
|
||||||
if (fs.existsSync(assetsPublicDir)) {
|
|
||||||
app.use('/assets', express.static(assetsPublicDir));
|
|
||||||
console.log('Serving /assets from', assetsPublicDir);
|
|
||||||
} else {
|
|
||||||
console.warn('Assets public folder not found at', assetsPublicDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Global error handler
|
// Global error handler
|
||||||
app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
|
app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,6 @@ export const deliverySlotInfo = mf.table('delivery_slot_info', {
|
||||||
freezeTime: timestamp('freeze_time').notNull(),
|
freezeTime: timestamp('freeze_time').notNull(),
|
||||||
isActive: boolean('is_active').notNull().default(true),
|
isActive: boolean('is_active').notNull().default(true),
|
||||||
isFlash: boolean('is_flash').notNull().default(false),
|
isFlash: boolean('is_flash').notNull().default(false),
|
||||||
isCapacityFull: boolean('is_capacity_full').notNull().default(false),
|
|
||||||
deliverySequence: jsonb('delivery_sequence').$defaultFn(() => {}),
|
deliverySequence: jsonb('delivery_sequence').$defaultFn(() => {}),
|
||||||
groupIds: jsonb('group_ids').$defaultFn(() => []),
|
groupIds: jsonb('group_ids').$defaultFn(() => []),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ interface Product {
|
||||||
productQuantity: number;
|
productQuantity: number;
|
||||||
isFlashAvailable: boolean;
|
isFlashAvailable: boolean;
|
||||||
flashPrice: string | null;
|
flashPrice: string | null;
|
||||||
deliverySlots: Array<{ id: number; deliveryTime: Date; freezeTime: Date; isCapacityFull: boolean }>;
|
deliverySlots: Array<{ id: number; deliveryTime: Date; freezeTime: Date }>;
|
||||||
specialDeals: Array<{ quantity: string; price: string; validTill: Date }>;
|
specialDeals: Array<{ quantity: string; price: string; validTill: Date }>;
|
||||||
productTags: string[];
|
productTags: string[];
|
||||||
}
|
}
|
||||||
|
|
@ -57,21 +57,19 @@ export async function initializeProducts(): Promise<void> {
|
||||||
});
|
});
|
||||||
const storeMap = new Map(allStores.map(s => [s.id, s]));
|
const storeMap = new Map(allStores.map(s => [s.id, s]));
|
||||||
|
|
||||||
// Fetch all delivery slots (excluding full capacity slots)
|
// Fetch all delivery slots
|
||||||
const allDeliverySlots = await db
|
const allDeliverySlots = await db
|
||||||
.select({
|
.select({
|
||||||
productId: productSlots.productId,
|
productId: productSlots.productId,
|
||||||
id: deliverySlotInfo.id,
|
id: deliverySlotInfo.id,
|
||||||
deliveryTime: deliverySlotInfo.deliveryTime,
|
deliveryTime: deliverySlotInfo.deliveryTime,
|
||||||
freezeTime: deliverySlotInfo.freezeTime,
|
freezeTime: deliverySlotInfo.freezeTime,
|
||||||
isCapacityFull: deliverySlotInfo.isCapacityFull,
|
|
||||||
})
|
})
|
||||||
.from(productSlots)
|
.from(productSlots)
|
||||||
.innerJoin(deliverySlotInfo, eq(productSlots.slotId, deliverySlotInfo.id))
|
.innerJoin(deliverySlotInfo, eq(productSlots.slotId, deliverySlotInfo.id))
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(deliverySlotInfo.isActive, true),
|
eq(deliverySlotInfo.isActive, true),
|
||||||
eq(deliverySlotInfo.isCapacityFull, false),
|
|
||||||
gt(deliverySlotInfo.deliveryTime, sql`NOW()`)
|
gt(deliverySlotInfo.deliveryTime, sql`NOW()`)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
@ -134,7 +132,7 @@ export async function initializeProducts(): Promise<void> {
|
||||||
productQuantity: product.productQuantity,
|
productQuantity: product.productQuantity,
|
||||||
isFlashAvailable: product.isFlashAvailable,
|
isFlashAvailable: product.isFlashAvailable,
|
||||||
flashPrice: product.flashPrice?.toString() || null,
|
flashPrice: product.flashPrice?.toString() || null,
|
||||||
deliverySlots: deliverySlots.map(s => ({ id: s.id, deliveryTime: s.deliveryTime, freezeTime: s.freezeTime, isCapacityFull: s.isCapacityFull })),
|
deliverySlots: deliverySlots.map(s => ({ id: s.id, deliveryTime: s.deliveryTime, freezeTime: s.freezeTime })),
|
||||||
specialDeals: specialDeals.map(d => ({ quantity: d.quantity.toString(), price: d.price.toString(), validTill: d.validTill })),
|
specialDeals: specialDeals.map(d => ({ quantity: d.quantity.toString(), price: d.price.toString(), validTill: d.validTill })),
|
||||||
productTags: productTags,
|
productTags: productTags,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ interface SlotWithProducts {
|
||||||
deliveryTime: Date;
|
deliveryTime: Date;
|
||||||
freezeTime: Date;
|
freezeTime: Date;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
isCapacityFull: boolean;
|
|
||||||
products: Array<{
|
products: Array<{
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
|
@ -31,7 +30,6 @@ interface SlotInfo {
|
||||||
id: number;
|
id: number;
|
||||||
deliveryTime: Date;
|
deliveryTime: Date;
|
||||||
freezeTime: Date;
|
freezeTime: Date;
|
||||||
isCapacityFull: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function initializeSlotStore(): Promise<void> {
|
export async function initializeSlotStore(): Promise<void> {
|
||||||
|
|
@ -82,7 +80,6 @@ export async function initializeSlotStore(): Promise<void> {
|
||||||
deliveryTime: slot.deliveryTime,
|
deliveryTime: slot.deliveryTime,
|
||||||
freezeTime: slot.freezeTime,
|
freezeTime: slot.freezeTime,
|
||||||
isActive: slot.isActive,
|
isActive: slot.isActive,
|
||||||
isCapacityFull: slot.isCapacityFull,
|
|
||||||
products: await Promise.all(
|
products: await Promise.all(
|
||||||
slot.productSlots.map(async (productSlot) => ({
|
slot.productSlots.map(async (productSlot) => ({
|
||||||
id: productSlot.product.id,
|
id: productSlot.product.id,
|
||||||
|
|
@ -121,7 +118,6 @@ export async function initializeSlotStore(): Promise<void> {
|
||||||
id: slot.id,
|
id: slot.id,
|
||||||
deliveryTime: slot.deliveryTime,
|
deliveryTime: slot.deliveryTime,
|
||||||
freezeTime: slot.freezeTime,
|
freezeTime: slot.freezeTime,
|
||||||
isCapacityFull: slot.isCapacityFull,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -229,9 +225,7 @@ export async function getMultipleProductsSlots(productIds: number[]): Promise<Re
|
||||||
for (let i = 0; i < productIds.length; i++) {
|
for (let i = 0; i < productIds.length; i++) {
|
||||||
const data = productsData[i];
|
const data = productsData[i];
|
||||||
if (data) {
|
if (data) {
|
||||||
const slots = JSON.parse(data) as SlotInfo[];
|
result[productIds[i]] = JSON.parse(data) as SlotInfo[];
|
||||||
// Filter out slots that are at full capacity
|
|
||||||
result[productIds[i]] = slots.filter(slot => !slot.isCapacityFull);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,23 +2,22 @@ import { router, protectedProcedure } from '../trpc-index';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { db } from '../../db/db_index';
|
import { db } from '../../db/db_index';
|
||||||
import { complaints, users } from '../../db/schema';
|
import { complaints, users } from '../../db/schema';
|
||||||
import { eq, desc, lt, and } from 'drizzle-orm';
|
import { eq, desc } from 'drizzle-orm';
|
||||||
import { generateSignedUrlsFromS3Urls } from '../../lib/s3-client';
|
import { generateSignedUrlsFromS3Urls } from '../../lib/s3-client';
|
||||||
|
|
||||||
export const complaintRouter = router({
|
export const complaintRouter = router({
|
||||||
getAll: protectedProcedure
|
getAll: protectedProcedure
|
||||||
.input(z.object({
|
.input(z.object({
|
||||||
cursor: z.number().optional(),
|
page: z.number().optional().default(1),
|
||||||
limit: z.number().default(20),
|
limit: z.number().optional().default(10),
|
||||||
}))
|
}))
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
const { cursor, limit } = input;
|
const page = input.page;
|
||||||
|
const limit = input.limit;
|
||||||
|
const offset = (page - 1) * limit;
|
||||||
|
|
||||||
let whereCondition = cursor
|
const [complaintsData, totalCountResult] = await Promise.all([
|
||||||
? lt(complaints.id, cursor)
|
db
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const complaintsData = await db
|
|
||||||
.select({
|
.select({
|
||||||
id: complaints.id,
|
id: complaints.id,
|
||||||
complaintBody: complaints.complaintBody,
|
complaintBody: complaints.complaintBody,
|
||||||
|
|
@ -27,20 +26,23 @@ export const complaintRouter = router({
|
||||||
isResolved: complaints.isResolved,
|
isResolved: complaints.isResolved,
|
||||||
createdAt: complaints.createdAt,
|
createdAt: complaints.createdAt,
|
||||||
userName: users.name,
|
userName: users.name,
|
||||||
userMobile: users.mobile,
|
|
||||||
images: complaints.images,
|
images: complaints.images,
|
||||||
})
|
})
|
||||||
.from(complaints)
|
.from(complaints)
|
||||||
.leftJoin(users, eq(complaints.userId, users.id))
|
.leftJoin(users, eq(complaints.userId, users.id))
|
||||||
.where(whereCondition)
|
.orderBy(desc(complaints.createdAt))
|
||||||
.orderBy(desc(complaints.id))
|
.limit(limit)
|
||||||
.limit(limit + 1);
|
.offset(offset),
|
||||||
|
db
|
||||||
|
.select({ count: db.$count(complaints) })
|
||||||
|
.from(complaints),
|
||||||
|
]);
|
||||||
|
|
||||||
const hasMore = complaintsData.length > limit;
|
const totalCount = totalCountResult[0].count;
|
||||||
const complaintsToReturn = hasMore ? complaintsData.slice(0, limit) : complaintsData;
|
|
||||||
|
|
||||||
|
// Generate signed URLs for images
|
||||||
const complaintsWithSignedImages = await Promise.all(
|
const complaintsWithSignedImages = await Promise.all(
|
||||||
complaintsToReturn.map(async (c) => {
|
complaintsData.map(async (c) => {
|
||||||
const signedImages = c.images
|
const signedImages = c.images
|
||||||
? await generateSignedUrlsFromS3Urls(c.images as string[])
|
? await generateSignedUrlsFromS3Urls(c.images as string[])
|
||||||
: [];
|
: [];
|
||||||
|
|
@ -50,7 +52,6 @@ export const complaintRouter = router({
|
||||||
text: c.complaintBody,
|
text: c.complaintBody,
|
||||||
userId: c.userId,
|
userId: c.userId,
|
||||||
userName: c.userName,
|
userName: c.userName,
|
||||||
userMobile: c.userMobile,
|
|
||||||
orderId: c.orderId,
|
orderId: c.orderId,
|
||||||
status: c.isResolved ? 'resolved' : 'pending',
|
status: c.isResolved ? 'resolved' : 'pending',
|
||||||
createdAt: c.createdAt,
|
createdAt: c.createdAt,
|
||||||
|
|
@ -61,9 +62,7 @@ export const complaintRouter = router({
|
||||||
|
|
||||||
return {
|
return {
|
||||||
complaints: complaintsWithSignedImages,
|
complaints: complaintsWithSignedImages,
|
||||||
nextCursor: hasMore
|
totalCount,
|
||||||
? complaintsToReturn[complaintsToReturn.length - 1].id
|
|
||||||
: undefined,
|
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -325,8 +325,7 @@ export const orderRouter = router({
|
||||||
: null,
|
: null,
|
||||||
isCod: orderData.isCod,
|
isCod: orderData.isCod,
|
||||||
isOnlinePayment: orderData.isOnlinePayment,
|
isOnlinePayment: orderData.isOnlinePayment,
|
||||||
totalAmount: parseFloat(orderData.totalAmount?.toString() || '0') - parseFloat(orderData.deliveryCharge?.toString() || '0'),
|
totalAmount: orderData.totalAmount,
|
||||||
deliveryCharge: parseFloat(orderData.deliveryCharge?.toString() || '0'),
|
|
||||||
adminNotes: orderData.adminNotes,
|
adminNotes: orderData.adminNotes,
|
||||||
userNotes: orderData.userNotes,
|
userNotes: orderData.userNotes,
|
||||||
createdAt: orderData.createdAt,
|
createdAt: orderData.createdAt,
|
||||||
|
|
@ -461,34 +460,6 @@ export const orderRouter = router({
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}),
|
}),
|
||||||
|
|
||||||
removeDeliveryCharge: protectedProcedure
|
|
||||||
.input(z.object({ orderId: z.number() }))
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
const { orderId } = input;
|
|
||||||
|
|
||||||
const order = await db.query.orders.findFirst({
|
|
||||||
where: eq(orders.id, orderId),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!order) {
|
|
||||||
throw new Error('Order not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentDeliveryCharge = parseFloat(order.deliveryCharge?.toString() || '0');
|
|
||||||
const currentTotalAmount = parseFloat(order.totalAmount?.toString() || '0');
|
|
||||||
const newTotalAmount = currentTotalAmount - currentDeliveryCharge;
|
|
||||||
|
|
||||||
await db
|
|
||||||
.update(orders)
|
|
||||||
.set({
|
|
||||||
deliveryCharge: '0',
|
|
||||||
totalAmount: newTotalAmount.toString()
|
|
||||||
})
|
|
||||||
.where(eq(orders.id, orderId));
|
|
||||||
|
|
||||||
return { success: true, message: 'Delivery charge removed' };
|
|
||||||
}),
|
|
||||||
|
|
||||||
getSlotOrders: protectedProcedure
|
getSlotOrders: protectedProcedure
|
||||||
.input(getSlotOrdersSchema)
|
.input(getSlotOrdersSchema)
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
|
|
|
||||||
|
|
@ -574,36 +574,4 @@ export const slotsRouter = router({
|
||||||
message: "Delivery sequence updated successfully",
|
message: "Delivery sequence updated successfully",
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
|
||||||
updateSlotCapacity: protectedProcedure
|
|
||||||
.input(z.object({
|
|
||||||
slotId: z.number(),
|
|
||||||
isCapacityFull: z.boolean(),
|
|
||||||
}))
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
if (!ctx.staffUser?.id) {
|
|
||||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const { slotId, isCapacityFull } = input;
|
|
||||||
|
|
||||||
const [updatedSlot] = await db
|
|
||||||
.update(deliverySlotInfo)
|
|
||||||
.set({ isCapacityFull })
|
|
||||||
.where(eq(deliverySlotInfo.id, slotId))
|
|
||||||
.returning();
|
|
||||||
|
|
||||||
if (!updatedSlot) {
|
|
||||||
throw new ApiError("Slot not found", 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reinitialize stores to reflect changes
|
|
||||||
await initializeAllStores();
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
slot: updatedSlot,
|
|
||||||
message: `Slot ${isCapacityFull ? 'marked as full capacity' : 'capacity reset'}`,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ export const getNextDeliveryDate = async (productId: number): Promise<Date | nul
|
||||||
and(
|
and(
|
||||||
eq(productSlots.productId, productId),
|
eq(productSlots.productId, productId),
|
||||||
eq(deliverySlotInfo.isActive, true),
|
eq(deliverySlotInfo.isActive, true),
|
||||||
eq(deliverySlotInfo.isCapacityFull, false),
|
|
||||||
gt(deliverySlotInfo.deliveryTime, sql`NOW()`)
|
gt(deliverySlotInfo.deliveryTime, sql`NOW()`)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ import { RazorpayPaymentService } from "../../lib/payments-utils";
|
||||||
import { getNextDeliveryDate } from "../common-apis/common";
|
import { getNextDeliveryDate } from "../common-apis/common";
|
||||||
import { CONST_KEYS, getConstant, getConstants } from "../../lib/const-store";
|
import { CONST_KEYS, getConstant, getConstants } from "../../lib/const-store";
|
||||||
import { publishFormattedOrder, publishCancellation } from "../../lib/post-order-handler";
|
import { publishFormattedOrder, publishCancellation } from "../../lib/post-order-handler";
|
||||||
import { getSlotById } from "../../stores/slot-store";
|
|
||||||
|
|
||||||
|
|
||||||
const validateAndGetCoupon = async (
|
const validateAndGetCoupon = async (
|
||||||
|
|
@ -405,17 +404,6 @@ export const orderRouter = router({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if any selected slot is at full capacity (only for regular delivery)
|
|
||||||
if (!isFlashDelivery) {
|
|
||||||
const slotIds = [...new Set(selectedItems.filter(i => i.slotId !== null).map(i => i.slotId as number))];
|
|
||||||
for (const slotId of slotIds) {
|
|
||||||
const slot = await getSlotById(slotId);
|
|
||||||
if (slot?.isCapacityFull) {
|
|
||||||
throw new ApiError("Selected delivery slot is at full capacity. Please choose another slot.", 403);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let processedItems = selectedItems;
|
let processedItems = selectedItems;
|
||||||
|
|
||||||
// Handle flash delivery slot resolution
|
// Handle flash delivery slot resolution
|
||||||
|
|
|
||||||
|
|
@ -47,10 +47,10 @@ export const productRouter = router({
|
||||||
const cachedProduct = await getProductByIdFromCache(productId);
|
const cachedProduct = await getProductByIdFromCache(productId);
|
||||||
|
|
||||||
if (cachedProduct) {
|
if (cachedProduct) {
|
||||||
// Filter delivery slots to only include those with future freeze times and not at full capacity
|
// Filter delivery slots to only include those with future freeze times
|
||||||
const currentTime = new Date();
|
const currentTime = new Date();
|
||||||
const filteredSlots = cachedProduct.deliverySlots.filter(slot =>
|
const filteredSlots = cachedProduct.deliverySlots.filter(slot =>
|
||||||
dayjs(slot.freezeTime).isAfter(currentTime) && !slot.isCapacityFull
|
dayjs(slot.freezeTime).isAfter(currentTime)
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -107,7 +107,6 @@ export const productRouter = router({
|
||||||
and(
|
and(
|
||||||
eq(productSlots.productId, productId),
|
eq(productSlots.productId, productId),
|
||||||
eq(deliverySlotInfo.isActive, true),
|
eq(deliverySlotInfo.isActive, true),
|
||||||
eq(deliverySlotInfo.isCapacityFull, false),
|
|
||||||
gt(deliverySlotInfo.deliveryTime, sql`NOW()`),
|
gt(deliverySlotInfo.deliveryTime, sql`NOW()`),
|
||||||
gt(deliverySlotInfo.freezeTime, sql`NOW()`)
|
gt(deliverySlotInfo.freezeTime, sql`NOW()`)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ export const slotsRouter = router({
|
||||||
const allSlots = await getAllSlotsFromCache();
|
const allSlots = await getAllSlotsFromCache();
|
||||||
const currentTime = new Date();
|
const currentTime = new Date();
|
||||||
const validSlots = allSlots
|
const validSlots = allSlots
|
||||||
.filter((slot) => dayjs(slot.freezeTime).isAfter(currentTime) && !slot.isCapacityFull)
|
.filter((slot) => dayjs(slot.freezeTime).isAfter(currentTime))
|
||||||
.sort((a, b) => dayjs(a.deliveryTime).valueOf() - dayjs(b.deliveryTime).valueOf());
|
.sort((a, b) => dayjs(a.deliveryTime).valueOf() - dayjs(b.deliveryTime).valueOf());
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -35,30 +35,10 @@ const StoreCard = ({
|
||||||
|
|
||||||
const navigateToStore = () => router.push(`/(drawer)/(tabs)/stores/store-detail/${item.id}`);
|
const navigateToStore = () => router.push(`/(drawer)/(tabs)/stores/store-detail/${item.id}`);
|
||||||
|
|
||||||
const isMeatStore = item.name.toLowerCase().includes('meat');
|
|
||||||
const ASSETS_BASE_URL = 'http://localhost:4000/assets';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={tw`bg-white rounded-[24px] mb-4 shadow-lg shadow-slate-200 border border-slate-200 overflow-hidden`}
|
style={tw`bg-white rounded-[24px] mb-4 shadow-lg shadow-slate-200 border border-slate-200 overflow-hidden`}
|
||||||
>
|
>
|
||||||
{/* Meat Store Images - Show at top if store name contains 'meat' */}
|
|
||||||
{isMeatStore && (
|
|
||||||
<View style={tw`flex-row h-16`}>
|
|
||||||
<Image
|
|
||||||
source={{ uri: `${ASSETS_BASE_URL}/halal.jpg` }}
|
|
||||||
style={tw`flex-1 h-full`}
|
|
||||||
contentFit="cover"
|
|
||||||
/>
|
|
||||||
<View style={tw`w-px bg-white`} />
|
|
||||||
<Image
|
|
||||||
source={{ uri: `${ASSETS_BASE_URL}/preservs.jpg` }}
|
|
||||||
style={tw`flex-1 h-full`}
|
|
||||||
contentFit="cover"
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Top Header Section - Touchable */}
|
{/* Top Header Section - Touchable */}
|
||||||
<MyTouchableOpacity
|
<MyTouchableOpacity
|
||||||
onPress={navigateToStore}
|
onPress={navigateToStore}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { View, Dimensions, ScrollView, TouchableOpacity } from "react-native";
|
import { View, Dimensions, ScrollView, TouchableOpacity } from "react-native";
|
||||||
import { useRouter, useLocalSearchParams } from "expo-router";
|
import { useRouter, useLocalSearchParams } from "expo-router";
|
||||||
import { Image } from 'expo-image';
|
|
||||||
import {
|
import {
|
||||||
theme,
|
theme,
|
||||||
tw,
|
tw,
|
||||||
|
|
@ -142,22 +141,6 @@ export default function StoreDetail() {
|
||||||
contentContainerStyle={[tw`px-4 pb-24`, { gap: 16 }]}
|
contentContainerStyle={[tw`px-4 pb-24`, { gap: 16 }]}
|
||||||
ListHeaderComponent={
|
ListHeaderComponent={
|
||||||
<View style={tw`pt-4 pb-6`}>
|
<View style={tw`pt-4 pb-6`}>
|
||||||
{/* Meat Store Images - Show at top if store name contains 'meat' */}
|
|
||||||
{storeData?.store?.name?.toLowerCase().includes('meat') && (
|
|
||||||
<View style={tw`flex-row h-16 mb-4`}>
|
|
||||||
<Image
|
|
||||||
source={{ uri: 'http://localhost:4000/assets/halal.jpg' }}
|
|
||||||
style={tw`flex-1 h-full`}
|
|
||||||
contentFit="cover"
|
|
||||||
/>
|
|
||||||
<View style={tw`w-px bg-white`} />
|
|
||||||
<Image
|
|
||||||
source={{ uri: 'http://localhost:4000/assets/preservs.jpg' }}
|
|
||||||
style={tw`flex-1 h-full`}
|
|
||||||
contentFit="cover"
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
<View
|
<View
|
||||||
style={tw`bg-white p-6 rounded-2xl shadow-sm border border-gray-100 items-center`}
|
style={tw`bg-white p-6 rounded-2xl shadow-sm border border-gray-100 items-center`}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { View, Text, TouchableOpacity, ScrollView, Alert } from 'react-native';
|
import { View, Text, TouchableOpacity, ScrollView } from 'react-native';
|
||||||
import { tw, BottomDialog, RawBottomDialog } from 'common-ui';
|
import { tw, BottomDialog, RawBottomDialog } from 'common-ui';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import AddressForm from '@/src/components/AddressForm';
|
import AddressForm from '@/src/components/AddressForm';
|
||||||
import LocationAttacher from '@/src/components/LocationAttacher';
|
|
||||||
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
||||||
import { trpc } from '@/src/trpc-client';
|
import { trpc } from '@/src/trpc-client';
|
||||||
import * as Location from 'expo-location';
|
|
||||||
|
|
||||||
interface AddressSelectorProps {
|
interface AddressSelectorProps {
|
||||||
selectedAddress: number | null;
|
selectedAddress: number | null;
|
||||||
|
|
@ -18,24 +16,10 @@ const CheckoutAddressSelector: React.FC<AddressSelectorProps> = ({
|
||||||
onAddressSelect,
|
onAddressSelect,
|
||||||
}) => {
|
}) => {
|
||||||
const [showAddAddress, setShowAddAddress] = useState(false);
|
const [showAddAddress, setShowAddAddress] = useState(false);
|
||||||
const [editingLocationAddressId, setEditingLocationAddressId] = useState<number | null>(null);
|
|
||||||
const [locationLoading, setLocationLoading] = useState(false);
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const scrollViewRef = useRef<ScrollView>(null);
|
const scrollViewRef = useRef<ScrollView>(null);
|
||||||
const { data: addresses } = trpc.user.address.getUserAddresses.useQuery();
|
const { data: addresses } = trpc.user.address.getUserAddresses.useQuery();
|
||||||
|
|
||||||
const updateAddressMutation = trpc.user.address.updateAddress.useMutation({
|
|
||||||
onSuccess: () => {
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['user.address.getUserAddresses'] });
|
|
||||||
setEditingLocationAddressId(null);
|
|
||||||
Alert.alert('Success', 'Location attached successfully');
|
|
||||||
},
|
|
||||||
onError: (error: any) => {
|
|
||||||
setEditingLocationAddressId(null);
|
|
||||||
Alert.alert('Error', error.message || 'Failed to attach location');
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Sort addresses with selected first, then default, then others
|
// Sort addresses with selected first, then default, then others
|
||||||
const sortedAddresses = React.useMemo(() => {
|
const sortedAddresses = React.useMemo(() => {
|
||||||
if (!addresses?.data) return [];
|
if (!addresses?.data) return [];
|
||||||
|
|
@ -68,45 +52,6 @@ const CheckoutAddressSelector: React.FC<AddressSelectorProps> = ({
|
||||||
scrollViewRef.current?.scrollTo({ x: 0, y: 0, animated: true });
|
scrollViewRef.current?.scrollTo({ x: 0, y: 0, animated: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAttachLocation = async (address: any) => {
|
|
||||||
setEditingLocationAddressId(address.id);
|
|
||||||
setLocationLoading(true);
|
|
||||||
try {
|
|
||||||
const { status } = await Location.requestForegroundPermissionsAsync();
|
|
||||||
|
|
||||||
if (status !== 'granted') {
|
|
||||||
Alert.alert('Permission Denied', 'Location permission is required to attach your current location');
|
|
||||||
setLocationLoading(false);
|
|
||||||
setEditingLocationAddressId(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const location = await Location.getCurrentPositionAsync({
|
|
||||||
accuracy: Location.Accuracy.High,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { latitude, longitude } = location.coords;
|
|
||||||
|
|
||||||
updateAddressMutation.mutate({
|
|
||||||
id: address.id,
|
|
||||||
name: address.name,
|
|
||||||
phone: address.phone,
|
|
||||||
addressLine1: address.addressLine1,
|
|
||||||
addressLine2: address.addressLine2 || '',
|
|
||||||
city: address.city,
|
|
||||||
state: address.state,
|
|
||||||
pincode: address.pincode,
|
|
||||||
isDefault: address.isDefault,
|
|
||||||
latitude,
|
|
||||||
longitude,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
Alert.alert('Error', 'Unable to fetch location. Please check your GPS settings.');
|
|
||||||
setLocationLoading(false);
|
|
||||||
setEditingLocationAddressId(null);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<View style={tw`bg-white p-5 rounded-2xl shadow-sm mb-4 border border-gray-100`}>
|
<View style={tw`bg-white p-5 rounded-2xl shadow-sm mb-4 border border-gray-100`}>
|
||||||
|
|
@ -181,25 +126,8 @@ const CheckoutAddressSelector: React.FC<AddressSelectorProps> = ({
|
||||||
))}
|
))}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Attach Location for selected address - outside the white box */}
|
|
||||||
{selectedAddress && (() => {
|
|
||||||
const selectedAddr = sortedAddresses.find(a => a.id === selectedAddress);
|
|
||||||
return selectedAddr && !selectedAddr.latitude && !selectedAddr.longitude ? (
|
|
||||||
<TouchableOpacity
|
|
||||||
onPress={() => handleAttachLocation(selectedAddr)}
|
|
||||||
disabled={locationLoading && editingLocationAddressId === selectedAddr.id}
|
|
||||||
style={tw`mt-3 py-2 px-3 bg-blue-50 rounded-lg self-start`}
|
|
||||||
>
|
|
||||||
<Text style={tw`text-blue-600 text-sm font-bold`}>
|
|
||||||
{locationLoading && editingLocationAddressId === selectedAddr.id ? 'Attaching...' : '+ Attach Current Location'}
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
) : null;
|
|
||||||
})()}
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
|
||||||
{/* <BottomDialog open={showAddAddress} onClose={() => setShowAddAddress(false)}> */}
|
{/* <BottomDialog open={showAddAddress} onClose={() => setShowAddAddress(false)}> */}
|
||||||
<RawBottomDialog open={showAddAddress} onClose={() => setShowAddAddress(false)}>
|
<RawBottomDialog open={showAddAddress} onClose={() => setShowAddAddress(false)}>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,16 @@ const HealthTestWrapper: React.FC<HealthTestWrapperProps> = ({ children }) => {
|
||||||
}
|
}
|
||||||
}, [versionFromBackend]);
|
}, [versionFromBackend]);
|
||||||
|
|
||||||
if (error) {
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<View style={tw`flex-1 justify-center items-center bg-gray-50`}>
|
||||||
|
<ActivityIndicator size="large" color={theme.colors.brand500} />
|
||||||
|
<MyText style={tw`text-gray-500 mt-4 font-medium`}>Checking service status...</MyText>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error || data?.status !== "ok") {
|
||||||
return (
|
return (
|
||||||
<View style={tw`flex-1 justify-center items-center bg-gray-50 p-6`}>
|
<View style={tw`flex-1 justify-center items-center bg-gray-50 p-6`}>
|
||||||
<View style={tw`w-16 h-16 bg-red-100 rounded-full items-center justify-center mb-4`}>
|
<View style={tw`w-16 h-16 bg-red-100 rounded-full items-center justify-center mb-4`}>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, TouchableOpacity, ActivityIndicator, Linking } from 'react-native';
|
import { View, TouchableOpacity, ActivityIndicator } from 'react-native';
|
||||||
import { useRouter } from 'expo-router';
|
import { useRouter } from 'expo-router';
|
||||||
import { tw, MyText } from 'common-ui';
|
import { tw, MyText } from 'common-ui';
|
||||||
import { useNavigationTarget } from 'common-ui/hooks/useNavigationTarget';
|
import { useNavigationTarget } from 'common-ui/hooks/useNavigationTarget';
|
||||||
|
|
@ -8,7 +8,6 @@ import dayjs from 'dayjs';
|
||||||
import { trpc } from '@/src/trpc-client';
|
import { trpc } from '@/src/trpc-client';
|
||||||
import { Image } from 'expo-image';
|
import { Image } from 'expo-image';
|
||||||
import { orderStatusManipulator } from '@/src/lib/string-manipulators';
|
import { orderStatusManipulator } from '@/src/lib/string-manipulators';
|
||||||
import { useGetEssentialConsts } from '@/src/api-hooks/essential-consts.api';
|
|
||||||
|
|
||||||
interface OrderItem {
|
interface OrderItem {
|
||||||
productName: string;
|
productName: string;
|
||||||
|
|
@ -48,15 +47,23 @@ export default function NextOrderGlimpse() {
|
||||||
pageSize: 50,
|
pageSize: 50,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: essentialConsts } = useGetEssentialConsts();
|
|
||||||
|
|
||||||
const allOrders: Order[] = ordersData?.data || [];
|
const allOrders: Order[] = ordersData?.data || [];
|
||||||
|
|
||||||
|
const now = dayjs();
|
||||||
|
|
||||||
const upcomingOrders = allOrders.filter(order => {
|
const upcomingOrders = allOrders.filter(order => {
|
||||||
if (order.orderStatus.toLowerCase() === 'cancelled') return false;
|
if (order.orderStatus.toLowerCase() === 'cancelled') return false;
|
||||||
if (order.deliveryStatus.toLowerCase() === 'success') return false;
|
if (order.deliveryStatus.toLowerCase() === 'success') return false;
|
||||||
|
|
||||||
|
if (order.isFlashDelivery) {
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (order.deliveryDate) {
|
||||||
|
return dayjs(order.deliveryDate).isAfter(now);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
upcomingOrders.sort((a, b) => {
|
upcomingOrders.sort((a, b) => {
|
||||||
|
|
@ -170,19 +177,6 @@ export default function NextOrderGlimpse() {
|
||||||
<Ionicons name="chevron-forward" size={14} color="#D97706" style={tw`ml-auto`} />
|
<Ionicons name="chevron-forward" size={14} color="#D97706" style={tw`ml-auto`} />
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Support Mobile Number */}
|
|
||||||
{essentialConsts?.supportMobile && (
|
|
||||||
<TouchableOpacity
|
|
||||||
style={tw`flex-row items-center mt-3 pt-3 border-t border-amber-200`}
|
|
||||||
onPress={() => Linking.openURL(`tel:${essentialConsts.supportMobile}`)}
|
|
||||||
>
|
|
||||||
<MaterialIcons name="phone-in-talk" size={14} color="#D97706" style={tw`mr-1.5`} />
|
|
||||||
<MyText style={tw`text-xs text-amber-700 font-medium`}>
|
|
||||||
Need help? Call {essentialConsts.supportMobile}
|
|
||||||
</MyText>
|
|
||||||
</TouchableOpacity>
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -11,19 +11,6 @@ import { useGetEssentialConsts } from '@/src/api-hooks/essential-consts.api';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
|
|
||||||
const formatTimeRange = (deliveryTime: string) => {
|
|
||||||
const time = dayjs(deliveryTime);
|
|
||||||
const endTime = time.add(1, 'hour');
|
|
||||||
const startPeriod = time.format('A');
|
|
||||||
const endPeriod = endTime.format('A');
|
|
||||||
|
|
||||||
if (startPeriod === endPeriod) {
|
|
||||||
return `${time.format('h')}-${endTime.format('h')} ${startPeriod}`;
|
|
||||||
} else {
|
|
||||||
return `${time.format('h:mm')} ${startPeriod} - ${endTime.format('h:mm')} ${endPeriod}`;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function AddToCartDialog() {
|
export default function AddToCartDialog() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { addedToCartProduct, clearAddedToCartProduct } = useCartStore();
|
const { addedToCartProduct, clearAddedToCartProduct } = useCartStore();
|
||||||
|
|
@ -171,7 +158,7 @@ export default function AddToCartDialog() {
|
||||||
<MaterialIcons name="local-shipping" size={20} color="#3B82F6" style={tw`mt-0.5`} />
|
<MaterialIcons name="local-shipping" size={20} color="#3B82F6" style={tw`mt-0.5`} />
|
||||||
<View style={tw`ml-3 flex-1`}>
|
<View style={tw`ml-3 flex-1`}>
|
||||||
<MyText style={tw`text-gray-900 font-bold text-base`}>
|
<MyText style={tw`text-gray-900 font-bold text-base`}>
|
||||||
{dayjs(slot.deliveryTime).format('ddd, DD MMM • ')}{formatTimeRange(slot.deliveryTime)}
|
{dayjs(slot.deliveryTime).format('ddd, DD MMM • h:mm A')}
|
||||||
</MyText>
|
</MyText>
|
||||||
</View>
|
</View>
|
||||||
{selectedSlotId === slot.id ? (
|
{selectedSlotId === slot.id ? (
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ const isDevMode = Constants.executionEnvironment !== "standalone";
|
||||||
// const BASE_API_URL = API_URL;
|
// const BASE_API_URL = API_URL;
|
||||||
// const BASE_API_URL = 'http://10.0.2.2:4000';
|
// 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.100.101:4000';
|
||||||
// const BASE_API_URL = 'http://192.168.100.105:4000';
|
// const BASE_API_URL = 'http://192.168.1.3:4000';
|
||||||
let BASE_API_URL = "https://mf.freshyo.in";
|
let BASE_API_URL = "https://mf.freshyo.in";
|
||||||
// let BASE_API_URL = 'http://192.168.100.104:4000';
|
// let BASE_API_URL = 'http://192.168.100.104:4000';
|
||||||
// let BASE_API_URL = 'http://192.168.29.176:4000';
|
// let BASE_API_URL = 'http://192.168.29.176:4000';
|
||||||
|
|
|
||||||
|
|
@ -200,7 +200,6 @@ interface ConfirmationDialogProps {
|
||||||
message?: string;
|
message?: string;
|
||||||
confirmText?: string;
|
confirmText?: string;
|
||||||
cancelText?: string;
|
cancelText?: string;
|
||||||
isLoading?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ConfirmationDialog: React.FC<ConfirmationDialogProps> = (props) => {
|
export const ConfirmationDialog: React.FC<ConfirmationDialogProps> = (props) => {
|
||||||
|
|
@ -212,8 +211,7 @@ export const ConfirmationDialog: React.FC<ConfirmationDialogProps> = (props) =>
|
||||||
title = "Are you sure?",
|
title = "Are you sure?",
|
||||||
message = "Do you really want to proceed with this action?",
|
message = "Do you really want to proceed with this action?",
|
||||||
confirmText = "Confirm",
|
confirmText = "Confirm",
|
||||||
cancelText = "Cancel",
|
cancelText = "Cancel"
|
||||||
isLoading = false,
|
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const [comment, setComment] = useState('');
|
const [comment, setComment] = useState('');
|
||||||
|
|
@ -255,15 +253,8 @@ export const ConfirmationDialog: React.FC<ConfirmationDialogProps> = (props) =>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<View style={{ flexDirection: 'row', justifyContent: 'space-between', marginTop: commentNeeded ? 16 : 0 }}>
|
<View style={{ flexDirection: 'row', justifyContent: 'space-between', marginTop: commentNeeded ? 16 : 0 }}>
|
||||||
<MyButton textContent={cancelText} onPress={handleCancel} fillColor="gray1" textColor="white1" disabled={isLoading} />
|
<MyButton textContent={cancelText} onPress={handleCancel} fillColor="gray1" textColor="white1" />
|
||||||
<MyButton
|
<MyButton textContent={confirmText} style={{ flexShrink: 0 }} onPress={handleConfirm} fillColor="red1" textColor="white1" />
|
||||||
textContent={isLoading ? "Processing..." : confirmText}
|
|
||||||
style={{ flexShrink: 0 }}
|
|
||||||
onPress={handleConfirm}
|
|
||||||
fillColor="red1"
|
|
||||||
textColor="white1"
|
|
||||||
disabled={isLoading}
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</BottomDialog>
|
</BottomDialog>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue