Compare commits
No commits in common. "3d7e02396509d957b516a3c145e2d263d8f12272" and "31f011ba8c4645a093fbcad158a1951096746cc0" have entirely different histories.
3d7e023965
...
31f011ba8c
25 changed files with 72 additions and 190 deletions
6
apps/admin-ui/.expo/types/router.d.ts
vendored
6
apps/admin-ui/.expo/types/router.d.ts
vendored
File diff suppressed because one or more lines are too long
|
|
@ -201,7 +201,9 @@ export default function Layout() {
|
||||||
<Drawer.Screen name="dashboard" options={{ title: "Dashboard" }} />
|
<Drawer.Screen name="dashboard" options={{ title: "Dashboard" }} />
|
||||||
<Drawer.Screen name="products" options={{ title: "Products" }} />
|
<Drawer.Screen name="products" options={{ title: "Products" }} />
|
||||||
<Drawer.Screen name="prices-overview" options={{ title: "Prices Overview" }} />
|
<Drawer.Screen name="prices-overview" options={{ title: "Prices Overview" }} />
|
||||||
<Drawer.Screen name="product-groupings" options={{ title: "Product Groupings" }} />
|
<Drawer.Screen name="product-groupings" options={{ title: "Product Groupings" }} />
|
||||||
|
<Drawer.Screen name="create-product-group" options={{ title: "Create Product Group" }} />
|
||||||
|
<Drawer.Screen name="edit-product-group/[id]" options={{ title: "Edit Product Group" }} />
|
||||||
|
|
||||||
|
|
||||||
<Drawer.Screen
|
<Drawer.Screen
|
||||||
|
|
@ -211,12 +213,14 @@ export default function Layout() {
|
||||||
<Drawer.Screen name="complaints" options={{ title: "Complaints" }} />
|
<Drawer.Screen name="complaints" options={{ title: "Complaints" }} />
|
||||||
<Drawer.Screen name="coupons" options={{ title: "Coupons" }} />
|
<Drawer.Screen name="coupons" options={{ title: "Coupons" }} />
|
||||||
<Drawer.Screen name="slots" options={{ title: "Slots" }} />
|
<Drawer.Screen name="slots" options={{ title: "Slots" }} />
|
||||||
<Drawer.Screen name="vendor-snippets" options={{ title: "Vendor Snippets" }} />
|
<Drawer.Screen name="vendor-snippets" options={{ title: "Vendor Snippets" }} />
|
||||||
<Drawer.Screen name="stores" options={{ title: "Stores" }} />
|
<Drawer.Screen name="delivery-sequences" options={{ title: "Delivery Sequences", headerShown: false }} />
|
||||||
|
<Drawer.Screen name="stores" options={{ title: "Stores" }} />
|
||||||
<Drawer.Screen name="address-management" options={{ title: "Address Management" }} />
|
<Drawer.Screen name="address-management" options={{ title: "Address Management" }} />
|
||||||
<Drawer.Screen name="product-tags" options={{ title: "Product Tags" }} />
|
<Drawer.Screen name="product-tags" options={{ title: "Product Tags" }} />
|
||||||
<Drawer.Screen name="order-details/[id]" options={{ title: "Order Details" }} />
|
<Drawer.Screen name="order-details/[id]" options={{ title: "Order Details" }} />
|
||||||
<Drawer.Screen name="rebalance-orders" options={{ title: "Rebalance Orders" }} />
|
<Drawer.Screen name="orders" options={{ title: "Orders" }} />
|
||||||
|
<Drawer.Screen name="rebalance-orders" options={{ title: "Rebalance Orders" }} />
|
||||||
</Drawer>
|
</Drawer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View } from 'react-native';
|
import { View } from 'react-native';
|
||||||
import { AppContainer } from 'common-ui';
|
import { AppContainer } from 'common-ui';
|
||||||
import ProductGroupForm from '../../../components/ProductGroupForm';
|
import ProductGroupForm from '../../components/ProductGroupForm';
|
||||||
import { useRouter } from 'expo-router';
|
import { useRouter } from 'expo-router';
|
||||||
|
|
||||||
export default function CreateProductGroup() {
|
export default function CreateProductGroup() {
|
||||||
|
|
@ -4,8 +4,8 @@ export default function Layout() {
|
||||||
return (
|
return (
|
||||||
<Stack screenOptions={{ headerShown: false }}>
|
<Stack screenOptions={{ headerShown: false }}>
|
||||||
<Stack.Screen name="index" options={{ title: 'Dashboard Banners' }} />
|
<Stack.Screen name="index" options={{ title: 'Dashboard Banners' }} />
|
||||||
<Stack.Screen name="create" options={{ title: 'Create Banner' }} />
|
<Stack.Screen name="create-banner" options={{ title: 'Create Banner' }} />
|
||||||
<Stack.Screen name="edit/[id]" options={{ title: 'Edit Banner' }} />
|
<Stack.Screen name="edit-banner" options={{ title: 'Edit Banner' }} />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { Stack } from 'expo-router';
|
||||||
|
|
||||||
|
export default function Layout() {
|
||||||
|
return (
|
||||||
|
<Stack screenOptions={{ headerShown: false }}>
|
||||||
|
<Stack.Screen name="index" options={{ title: 'Create Banner' }} />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,7 @@ import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
||||||
import { useRouter } from 'expo-router';
|
import { useRouter } from 'expo-router';
|
||||||
import { FormikHelpers } from 'formik';
|
import { FormikHelpers } from 'formik';
|
||||||
import BannerForm, { BannerFormData } from '@/components/BannerForm';
|
import BannerForm, { BannerFormData } from '@/components/BannerForm';
|
||||||
import { trpc } from '@/src/trpc-client';
|
import { trpc } from '../../../../src/trpc-client';
|
||||||
|
|
||||||
export default function CreateBanner() {
|
export default function CreateBanner() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -5,7 +5,7 @@ import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
||||||
import { useRouter, useLocalSearchParams } from 'expo-router';
|
import { useRouter, useLocalSearchParams } from 'expo-router';
|
||||||
import { FormikHelpers } from 'formik';
|
import { FormikHelpers } from 'formik';
|
||||||
import BannerForm, { BannerFormData } from '@/components/BannerForm';
|
import BannerForm, { BannerFormData } from '@/components/BannerForm';
|
||||||
import { trpc } from '@/src/trpc-client';
|
import { trpc } from '../../../../src/trpc-client';
|
||||||
|
|
||||||
interface Banner {
|
interface Banner {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { Stack } from 'expo-router';
|
||||||
|
|
||||||
|
export default function Layout() {
|
||||||
|
return (
|
||||||
|
<Stack screenOptions={{ headerShown: false }}>
|
||||||
|
<Stack.Screen name="[id]" options={{ title: 'Edit Banner' }} />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -180,7 +180,7 @@ export default function DashboardBanners() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEdit = (banner: Banner) => {
|
const handleEdit = (banner: Banner) => {
|
||||||
router.push(`/dashboard-banners/edit/${banner.id}` as any);
|
router.push(`/dashboard-banners/edit-banner/${banner.id}` as any);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (id: number) => {
|
const handleDelete = (id: number) => {
|
||||||
|
|
@ -207,7 +207,7 @@ export default function DashboardBanners() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCreate = () => {
|
const handleCreate = () => {
|
||||||
router.push('/dashboard-banners/create' as any);
|
router.push('/(drawer)/dashboard-banners/create-banner' as any);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import { MyText, tw } from 'common-ui';
|
||||||
import { LinearGradient } from 'expo-linear-gradient';
|
import { LinearGradient } from 'expo-linear-gradient';
|
||||||
import { theme } from 'common-ui/src/theme';
|
import { theme } from 'common-ui/src/theme';
|
||||||
import { trpc } from '@/src/trpc-client';
|
import { trpc } from '@/src/trpc-client';
|
||||||
import { useNavigationTarget } from 'common-ui/hooks/useNavigationTarget';
|
|
||||||
|
|
||||||
interface MenuItem {
|
interface MenuItem {
|
||||||
title: string;
|
title: string;
|
||||||
|
|
@ -17,7 +16,6 @@ interface MenuItem {
|
||||||
iconColor?: string;
|
iconColor?: string;
|
||||||
iconBg?: string;
|
iconBg?: string;
|
||||||
badgeCount?: number;
|
badgeCount?: number;
|
||||||
onPress?: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MenuItemComponentProps {
|
interface MenuItemComponentProps {
|
||||||
|
|
@ -29,7 +27,8 @@ const MenuItemComponent: React.FC<MenuItemComponentProps> = ({ item, router }) =
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
onPress={() => item.onPress ? item.onPress() : router.push(item.route as any)}
|
key={item.route}
|
||||||
|
onPress={() => router.push(item.route as any)}
|
||||||
style={({ pressed }) => [
|
style={({ pressed }) => [
|
||||||
tw`flex-row items-center p-4 bg-white border border-gray-100 rounded-xl mb-3 shadow-sm`,
|
tw`flex-row items-center p-4 bg-white border border-gray-100 rounded-xl mb-3 shadow-sm`,
|
||||||
pressed && tw`bg-gray-50`,
|
pressed && tw`bg-gray-50`,
|
||||||
|
|
@ -56,41 +55,28 @@ const MenuItemComponent: React.FC<MenuItemComponentProps> = ({ item, router }) =
|
||||||
|
|
||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { setNavigationTarget } = useNavigationTarget();
|
|
||||||
|
|
||||||
const { data: essentialsData } = trpc.admin.user.getEssentials.useQuery();
|
const { data: essentialsData } = trpc.admin.user.getEssentials.useQuery();
|
||||||
|
|
||||||
|
|
||||||
const handleManageOrdersPress = () => {
|
|
||||||
setNavigationTarget('/manage-orders/orders');
|
|
||||||
router.push('/(drawer)/manage-orders');
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeliverySequencesPress = () => {
|
|
||||||
setNavigationTarget('/manage-orders/delivery-sequences');
|
|
||||||
router.push('/(drawer)/manage-orders');
|
|
||||||
};
|
|
||||||
|
|
||||||
const menuItems: MenuItem[] = [
|
const menuItems: MenuItem[] = [
|
||||||
{
|
{
|
||||||
title: 'Manage Orders',
|
title: 'Manage Orders',
|
||||||
icon: 'shopping-bag',
|
icon: 'shopping-bag',
|
||||||
description: 'View and manage customer orders',
|
description: 'View and manage customer orders',
|
||||||
route: '/(drawer)/manage-orders',
|
route: '/(drawer)/orders',
|
||||||
category: 'orders',
|
category: 'orders',
|
||||||
iconColor: '#10B981',
|
iconColor: '#10B981',
|
||||||
iconBg: '#D1FAE5',
|
iconBg: '#D1FAE5',
|
||||||
onPress: handleManageOrdersPress,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Delivery Sequences',
|
title: 'Delivery Sequences',
|
||||||
icon: 'alt-route',
|
icon: 'alt-route',
|
||||||
description: 'Plan and optimize delivery routes',
|
description: 'Plan and optimize delivery routes',
|
||||||
route: '/manage-orders/delivery-sequences',
|
route: '/(drawer)/delivery-sequences',
|
||||||
category: 'orders',
|
category: 'orders',
|
||||||
iconColor: '#8B5CF6',
|
iconColor: '#8B5CF6',
|
||||||
iconBg: '#EDE9FE',
|
iconBg: '#EDE9FE',
|
||||||
onPress: handleDeliverySequencesPress,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Delivery Slots',
|
title: 'Delivery Slots',
|
||||||
|
|
@ -225,8 +211,8 @@ export default function Dashboard() {
|
||||||
<View style={tw`flex-row flex-wrap gap-3`}>
|
<View style={tw`flex-row flex-wrap gap-3`}>
|
||||||
{quickActions.map((item) => (
|
{quickActions.map((item) => (
|
||||||
<Pressable
|
<Pressable
|
||||||
key={`quick-${item.route}`}
|
key={item.route}
|
||||||
onPress={() => item.onPress ? item.onPress() : router.push(item.route as any)}
|
onPress={() => router.push(item.route as any)}
|
||||||
style={({ pressed }) => [
|
style={({ pressed }) => [
|
||||||
tw`bg-white rounded-xl p-3 shadow-sm border border-gray-100 items-center`,
|
tw`bg-white rounded-xl p-3 shadow-sm border border-gray-100 items-center`,
|
||||||
{ width: 'calc(25% - 9px)' },
|
{ width: 'calc(25% - 9px)' },
|
||||||
|
|
@ -263,7 +249,7 @@ export default function Dashboard() {
|
||||||
</View>
|
</View>
|
||||||
<MyText style={tw`text-gray-700 font-bold text-base`}>{category.title}</MyText>
|
<MyText style={tw`text-gray-700 font-bold text-base`}>{category.title}</MyText>
|
||||||
</View>
|
</View>
|
||||||
{categoryItems.map(item => <MenuItemComponent key={`menu-${item.route}`} item={item} router={router} />)}
|
{categoryItems.map(item => <MenuItemComponent key={item.route} item={item} router={router} />)}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ import { Stack } from 'expo-router';
|
||||||
|
|
||||||
export default function Layout() {
|
export default function Layout() {
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack screenOptions={{ headerShown: false }}>
|
||||||
<Stack.Screen name="index" options={{ title: 'Delivery Sequences', headerShown: false }} />
|
<Stack.Screen name="index" options={{ title: 'Delivery Sequences' }} />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -302,7 +302,7 @@ export default function DeliverySequences() {
|
||||||
// Auto-select first slot if no slotId provided
|
// Auto-select first slot if no slotId provided
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!slotId && slotsData?.slots && slotsData.slots.length > 0) {
|
if (!slotId && slotsData?.slots && slotsData.slots.length > 0) {
|
||||||
router.replace(`/manage-orders/delivery-sequences?slotId=${slotsData.slots[0].id}`);
|
router.replace(`/delivery-sequences?slotId=${slotsData.slots[0].id}`);
|
||||||
}
|
}
|
||||||
}, [slotId, slotsData, router]);
|
}, [slotId, slotsData, router]);
|
||||||
|
|
||||||
|
|
@ -505,7 +505,7 @@ export default function DeliverySequences() {
|
||||||
value={selectedSlotId || ""}
|
value={selectedSlotId || ""}
|
||||||
onValueChange={(val) => {
|
onValueChange={(val) => {
|
||||||
if (val) {
|
if (val) {
|
||||||
router.replace(`/manage-orders/delivery-sequences?slotId=${val}`);
|
router.replace(`/delivery-sequences?slotId=${val}`);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
placeholder="Select slot"
|
placeholder="Select slot"
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View } from 'react-native';
|
import { View } from 'react-native';
|
||||||
import { AppContainer, MyText } from 'common-ui';
|
import { AppContainer, MyText } from 'common-ui';
|
||||||
import ProductGroupForm from '../../../../components/ProductGroupForm';
|
import ProductGroupForm from '../../../components/ProductGroupForm';
|
||||||
import { useRouter, useLocalSearchParams } from 'expo-router';
|
import { useRouter, useLocalSearchParams } from 'expo-router';
|
||||||
import { trpc } from '@/src/trpc-client';
|
import { trpc } from '../../../src/trpc-client';
|
||||||
|
|
||||||
export default function EditProductGroup() {
|
export default function EditProductGroup() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -4,8 +4,6 @@ export default function Layout() {
|
||||||
return (
|
return (
|
||||||
<Stack screenOptions={{ headerShown: false }}>
|
<Stack screenOptions={{ headerShown: false }}>
|
||||||
<Stack.Screen name="index" options={{ title: 'Manage Orders' }} />
|
<Stack.Screen name="index" options={{ title: 'Manage Orders' }} />
|
||||||
<Stack.Screen name="delivery-sequences" options={{ title: 'Delivery Sequences' }} />
|
|
||||||
<Stack.Screen name="orders" options={{ title: 'Orders' }} />
|
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1,28 +1,16 @@
|
||||||
import { View, TouchableOpacity, Alert } from 'react-native';
|
import { View, TouchableOpacity, Alert } from 'react-native';
|
||||||
import { MyText, BottomDropdown, tw, MyFlatList, useMarkDataFetchers } from 'common-ui';
|
import { MyText, BottomDropdown, tw, MyFlatList, useMarkDataFetchers } from 'common-ui';
|
||||||
import { useRouter } from 'expo-router';
|
import { useRouter } from 'expo-router';
|
||||||
import { useFocusEffect } from '@react-navigation/native';
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { useState, useCallback } from 'react';
|
import { useState } from 'react';
|
||||||
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 { useNavigationTarget } from 'common-ui/hooks/useNavigationTarget';
|
|
||||||
|
|
||||||
export default function ManageOrders() {
|
export default function ManageOrders() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { getNavigationTarget } = useNavigationTarget();
|
|
||||||
const [selectedSlotId, setSelectedSlotId] = useState<string | null>(null);
|
const [selectedSlotId, setSelectedSlotId] = useState<string | null>(null);
|
||||||
const { data: slotsData, refetch } = trpc.admin.slots.getAll.useQuery();
|
const { data: slotsData, refetch } = trpc.admin.slots.getAll.useQuery();
|
||||||
|
|
||||||
useFocusEffect(
|
|
||||||
useCallback(() => {
|
|
||||||
const target = getNavigationTarget();
|
|
||||||
if (target) {
|
|
||||||
router.replace(target as any);
|
|
||||||
}
|
|
||||||
}, [router, getNavigationTarget])
|
|
||||||
);
|
|
||||||
|
|
||||||
useMarkDataFetchers(() => {
|
useMarkDataFetchers(() => {
|
||||||
refetch();
|
refetch();
|
||||||
});
|
});
|
||||||
|
|
@ -46,7 +34,7 @@ export default function ManageOrders() {
|
||||||
Alert.alert('Flash Deliveries', 'Flash deliveries do not have delivery sequences. Use the Orders menu to manage flash deliveries.');
|
Alert.alert('Flash Deliveries', 'Flash deliveries do not have delivery sequences. Use the Orders menu to manage flash deliveries.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
router.push(`/manage-orders/delivery-sequences?slotId=${selectedSlotId}`);
|
router.push(`/(drawer)/delivery-sequences?slotId=${selectedSlotId}`);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -55,9 +43,9 @@ export default function ManageOrders() {
|
||||||
color: 'bg-cyan-500',
|
color: 'bg-cyan-500',
|
||||||
onPress: () => {
|
onPress: () => {
|
||||||
if (selectedSlotId === 'flash') {
|
if (selectedSlotId === 'flash') {
|
||||||
router.push('/manage-orders/orders?filter=flash');
|
router.push('/(drawer)/orders?filter=flash');
|
||||||
} else {
|
} else {
|
||||||
router.push('/manage-orders/orders');
|
router.push('/(drawer)/orders');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
import { Stack } from 'expo-router';
|
|
||||||
|
|
||||||
export default function Layout() {
|
|
||||||
return (
|
|
||||||
<Stack>
|
|
||||||
<Stack.Screen name="index" options={{ title: 'Orders', headerShown: false }} />
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
9
apps/admin-ui/app/(drawer)/orders/_layout.tsx
Normal file
9
apps/admin-ui/app/(drawer)/orders/_layout.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { Stack } from 'expo-router';
|
||||||
|
|
||||||
|
export default function Layout() {
|
||||||
|
return (
|
||||||
|
<Stack screenOptions={{ headerShown: false }}>
|
||||||
|
<Stack.Screen name="index" options={{ title: 'Orders' }} />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { useState , useEffect } from 'react';
|
import React, { useState , useEffect } from 'react';
|
||||||
import { View, TouchableOpacity, Alert, TextInput, ActivityIndicator, Linking } 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 { AppContainer, MyText, tw, MyFlatList, BottomDialog, BottomDropdown, Checkbox, theme, MyTextInput } from 'common-ui';
|
||||||
import { trpc } from '@/src/trpc-client';
|
import { trpc } from '../../../src/trpc-client';
|
||||||
import { useRouter, useLocalSearchParams } from 'expo-router';
|
import { useRouter, useLocalSearchParams } from 'expo-router';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
||||||
|
|
@ -4,8 +4,6 @@ export default function Layout() {
|
||||||
return (
|
return (
|
||||||
<Stack screenOptions={{ headerShown: false }}>
|
<Stack screenOptions={{ headerShown: false }}>
|
||||||
<Stack.Screen name="index" options={{ title: 'Product Groupings' }} />
|
<Stack.Screen name="index" options={{ title: 'Product Groupings' }} />
|
||||||
<Stack.Screen name="create" options={{ title: 'Create Product Group' }} />
|
|
||||||
<Stack.Screen name="edit/[id]" options={{ title: 'Edit Product Group' }} />
|
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -143,11 +143,11 @@ export default function ProductGroupings() {
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleCreate = () => {
|
const handleCreate = () => {
|
||||||
router.push("/product-groupings/create");
|
router.push("/(drawer)/create-product-group");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEdit = (group: ProductGroup) => {
|
const handleEdit = (group: ProductGroup) => {
|
||||||
router.push(`/product-groupings/edit/${group.id}`);
|
router.push(`/(drawer)/edit-product-group/${group.id}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (id: number) => {
|
const handleDelete = (id: number) => {
|
||||||
|
|
|
||||||
|
|
@ -198,7 +198,7 @@ export default function SlotDetails() {
|
||||||
|
|
||||||
{/* FAB for Edit Slot */}
|
{/* FAB for Edit Slot */}
|
||||||
<MyTouchableOpacity
|
<MyTouchableOpacity
|
||||||
onPress={() => router.push(`/slots/edit/${slot.id}` as any)}
|
onPress={() => router.push(`/edit-slot/${slot.id}` as any)}
|
||||||
activeOpacity={0.95}
|
activeOpacity={0.95}
|
||||||
style={{ position: 'absolute', bottom: 32, right: 24, zIndex: 100 }}
|
style={{ position: 'absolute', bottom: 32, right: 24, zIndex: 100 }}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import './notif-job';
|
import './notif-job';
|
||||||
import { initializeAllStores } from '../stores/store-initializer';
|
import { initializeAllStores } from '../stores/store-initializer';
|
||||||
import { startOrderHandler, startCancellationHandler, publishOrder } from './post-order-handler';
|
import { startOrderHandler, publishOrder } from './post-order-handler';
|
||||||
import { deleteOrders } from './delete-orders';
|
import { deleteOrders } from './delete-orders';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -9,7 +9,6 @@ import { deleteOrders } from './delete-orders';
|
||||||
* - Role Manager (fetches and caches all roles)
|
* - Role Manager (fetches and caches all roles)
|
||||||
* - Const Store (syncs constants from DB to Redis)
|
* - Const Store (syncs constants from DB to Redis)
|
||||||
* - Post Order Handler (Redis Pub/Sub subscriber)
|
* - Post Order Handler (Redis Pub/Sub subscriber)
|
||||||
* - Cancellation Handler (Redis Pub/Sub subscriber for order cancellations)
|
|
||||||
* - Other services can be added here in the future
|
* - Other services can be added here in the future
|
||||||
*/
|
*/
|
||||||
export const initFunc = async (): Promise<void> => {
|
export const initFunc = async (): Promise<void> => {
|
||||||
|
|
@ -19,7 +18,6 @@ export const initFunc = async (): Promise<void> => {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
initializeAllStores(),
|
initializeAllStores(),
|
||||||
startOrderHandler(),
|
startOrderHandler(),
|
||||||
startCancellationHandler(),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
console.log('Application initialization completed successfully');
|
console.log('Application initialization completed successfully');
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,15 @@
|
||||||
import { db } from '../db/db_index';
|
import { db } from '../db/db_index';
|
||||||
import { orders, orderStatus } from '../db/schema';
|
import { orders } from '../db/schema';
|
||||||
import redisClient from './redis-client';
|
import redisClient from './redis-client';
|
||||||
import { sendTelegramMessage } from './telegram-service';
|
import { sendTelegramMessage } from './telegram-service';
|
||||||
import { inArray, eq } from 'drizzle-orm';
|
import { inArray } from 'drizzle-orm';
|
||||||
|
|
||||||
const ORDER_CHANNEL = 'orders:placed';
|
const ORDER_CHANNEL = 'orders:placed';
|
||||||
const CANCELLED_CHANNEL = 'orders:cancelled';
|
|
||||||
|
|
||||||
interface OrderIdMessage {
|
interface OrderIdMessage {
|
||||||
orderIds: number[];
|
orderIds: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CancellationMessage {
|
|
||||||
orderId: number;
|
|
||||||
cancelledBy: 'user' | 'admin';
|
|
||||||
reason: string;
|
|
||||||
cancelledAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const formatDateTime = (dateStr: string | null | undefined): string => {
|
const formatDateTime = (dateStr: string | null | undefined): string => {
|
||||||
if (!dateStr) return 'N/A';
|
if (!dateStr) return 'N/A';
|
||||||
return new Date(dateStr).toLocaleString('en-IN', {
|
return new Date(dateStr).toLocaleString('en-IN', {
|
||||||
|
|
@ -63,28 +55,6 @@ const formatOrderMessageWithFullData = (ordersData: any[]): string => {
|
||||||
return message;
|
return message;
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatCancellationMessage = (orderData: any, cancellationData: CancellationMessage): string => {
|
|
||||||
const message = `❌ <b>Order Cancelled</b>
|
|
||||||
|
|
||||||
<b>Order #${orderData.id}</b>
|
|
||||||
|
|
||||||
👤 <b>Name:</b> ${orderData.address?.name || 'N/A'}
|
|
||||||
📞 <b>Phone:</b> ${orderData.address?.phone || 'N/A'}
|
|
||||||
|
|
||||||
📦 <b>Items:</b>
|
|
||||||
${orderData.orderItems?.map((item: any) => ` • ${item.product?.name || 'Unknown'} x${item.quantity}`).join('\n') || ' N/A'}
|
|
||||||
|
|
||||||
💰 <b>Total:</b> ₹${orderData.totalAmount}
|
|
||||||
💳 <b>Refund:</b> ${orderData.refundStatus === 'na' ? 'N/A (COD)' : orderData.refundStatus || 'Pending'}
|
|
||||||
|
|
||||||
❓ <b>Reason:</b> ${cancellationData.reason}
|
|
||||||
👤 <b>Cancelled by:</b> ${cancellationData.cancelledBy === 'admin' ? 'Admin' : 'User'}
|
|
||||||
⏰ <b>Time:</b> ${formatDateTime(cancellationData.cancelledAt)}
|
|
||||||
`;
|
|
||||||
|
|
||||||
return message;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start the post order handler
|
* Start the post order handler
|
||||||
* Subscribes to the orders:placed channel and sends to Telegram
|
* Subscribes to the orders:placed channel and sends to Telegram
|
||||||
|
|
@ -134,56 +104,6 @@ export const stopOrderHandler = async (): Promise<void> => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const startCancellationHandler = async (): Promise<void> => {
|
|
||||||
try {
|
|
||||||
console.log('Starting cancellation handler...');
|
|
||||||
|
|
||||||
await redisClient.subscribe(CANCELLED_CHANNEL, async (message: string) => {
|
|
||||||
try {
|
|
||||||
const cancellationData: CancellationMessage = JSON.parse(message);
|
|
||||||
console.log('Order cancellation received, sending to Telegram...');
|
|
||||||
|
|
||||||
const orderData = await db.query.orders.findFirst({
|
|
||||||
where: eq(orders.id, cancellationData.orderId),
|
|
||||||
with: {
|
|
||||||
address: true,
|
|
||||||
orderItems: { with: { product: true } },
|
|
||||||
refunds: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!orderData) {
|
|
||||||
console.error('Order not found for cancellation:', cancellationData.orderId);
|
|
||||||
await sendTelegramMessage(`⚠️ Order ${cancellationData.orderId} was cancelled but could not be found in database`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const refundStatus = orderData.refunds?.[0]?.refundStatus || 'pending';
|
|
||||||
|
|
||||||
const telegramMessage = formatCancellationMessage({ ...orderData, refundStatus }, cancellationData);
|
|
||||||
await sendTelegramMessage(telegramMessage);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to process cancellation message:', error);
|
|
||||||
await sendTelegramMessage(`⚠️ Error processing cancellation: ${message}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Cancellation handler started successfully');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to start cancellation handler:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const stopCancellationHandler = async (): Promise<void> => {
|
|
||||||
try {
|
|
||||||
await redisClient.unsubscribe(CANCELLED_CHANNEL);
|
|
||||||
console.log('Cancellation handler stopped');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error stopping cancellation handler:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const publishOrder = async (orderDetails: OrderIdMessage): Promise<boolean> => {
|
export const publishOrder = async (orderDetails: OrderIdMessage): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
const message = JSON.stringify(orderDetails);
|
const message = JSON.stringify(orderDetails);
|
||||||
|
|
@ -207,24 +127,3 @@ export const publishFormattedOrder = async (
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const publishCancellation = async (
|
|
||||||
orderId: number,
|
|
||||||
cancelledBy: 'user' | 'admin',
|
|
||||||
reason: string
|
|
||||||
): Promise<boolean> => {
|
|
||||||
try {
|
|
||||||
const message: CancellationMessage = {
|
|
||||||
orderId,
|
|
||||||
cancelledBy,
|
|
||||||
reason,
|
|
||||||
cancelledAt: new Date().toISOString(),
|
|
||||||
};
|
|
||||||
await redisClient.publish(CANCELLED_CHANNEL, JSON.stringify(message));
|
|
||||||
console.log('Cancellation published to Redis:', orderId);
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to publish cancellation:', error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ import {
|
||||||
sendOrderPackagedNotification,
|
sendOrderPackagedNotification,
|
||||||
sendOrderDeliveredNotification,
|
sendOrderDeliveredNotification,
|
||||||
} from "../../lib/notif-job";
|
} from "../../lib/notif-job";
|
||||||
import { publishCancellation } from "../../lib/post-order-handler";
|
|
||||||
|
|
||||||
const updateOrderNotesSchema = z.object({
|
const updateOrderNotesSchema = z.object({
|
||||||
orderId: z.number(),
|
orderId: z.number(),
|
||||||
|
|
@ -956,9 +955,6 @@ export const orderRouter = router({
|
||||||
return { orderId: order.id, userId: order.userId };
|
return { orderId: order.id, userId: order.userId };
|
||||||
});
|
});
|
||||||
|
|
||||||
// Publish to Redis for Telegram notification
|
|
||||||
await publishCancellation(result.orderId, 'admin', reason);
|
|
||||||
|
|
||||||
return { success: true, message: "Order cancelled successfully" };
|
return { success: true, message: "Order cancelled successfully" };
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import {
|
||||||
import { RazorpayPaymentService } from "../../lib/payments-utils";
|
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 } from "../../lib/post-order-handler";
|
||||||
|
|
||||||
|
|
||||||
const validateAndGetCoupon = async (
|
const validateAndGetCoupon = async (
|
||||||
|
|
@ -785,9 +785,6 @@ export const orderRouter = router({
|
||||||
result.orderId.toString()
|
result.orderId.toString()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Publish to Redis for Telegram notification
|
|
||||||
await publishCancellation(result.orderId, 'user', reason);
|
|
||||||
|
|
||||||
return { success: true, message: "Order cancelled successfully" };
|
return { success: true, message: "Order cancelled successfully" };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue