enh
This commit is contained in:
parent
e5d9de33d7
commit
e2c6a292a4
65 changed files with 413 additions and 353 deletions
|
|
@ -5,7 +5,10 @@
|
||||||
"Shell(npx tsc --noEmit 2 >& 1)",
|
"Shell(npx tsc --noEmit 2 >& 1)",
|
||||||
"Shell(grep:*)",
|
"Shell(grep:*)",
|
||||||
"Shell(npx tsc --noEmit -p packages/shared/tsconfig.json 2 >& 1)",
|
"Shell(npx tsc --noEmit -p packages/shared/tsconfig.json 2 >& 1)",
|
||||||
"Shell(cp:*)"
|
"Shell(cp:*)",
|
||||||
|
"Shell(rm:*)",
|
||||||
|
"Shell(ls:*)",
|
||||||
|
"Shell(cd /Users/mohammedshafiuddin/WebDev/freshyo/apps/admin-ui && ls \"app/(drawer)\" 2>/dev/null; echo \"---\"; find app -name \"create.tsx\" -path \"*coupon*\" 2>/dev/null)"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"defaultMode": "default"
|
"defaultMode": "default"
|
||||||
|
|
|
||||||
|
|
@ -19,3 +19,4 @@
|
||||||
- Prefers test plans written for a non-technical audience, using plain-language, click-by-click instructions ("tap this", "type that", "check there") rather than technical terms or API names. Confidence: 0.9
|
- Prefers test plans written for a non-technical audience, using plain-language, click-by-click instructions ("tap this", "type that", "check there") rather than technical terms or API names. Confidence: 0.9
|
||||||
- When updating or creating a document, prefers the agent first compare it against existing source documents, identify missing items or gaps, and add them in the same established format. Confidence: 0.9
|
- When updating or creating a document, prefers the agent first compare it against existing source documents, identify missing items or gaps, and add them in the same established format. Confidence: 0.9
|
||||||
- Dislikes nested headers in mobile/drawer navigation; prefers a single, shared header (e.g., the drawer header) and relies on device back buttons or gestures for returning to previous screens. Confidence: 0.9
|
- Dislikes nested headers in mobile/drawer navigation; prefers a single, shared header (e.g., the drawer header) and relies on device back buttons or gestures for returning to previous screens. Confidence: 0.9
|
||||||
|
- After reviewing a presented plan, prefers brief, action-oriented approval (e.g., "nice. go ahead") before implementation proceeds. Confidence: 0.6
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { Drawer } from "expo-router/drawer";
|
import { Drawer } from "expo-router/drawer";
|
||||||
import { DrawerContentScrollView, DrawerItem } from "@react-navigation/drawer";
|
import { DrawerContentScrollView, DrawerItem } from "@react-navigation/drawer";
|
||||||
import { useRouter, Redirect } from "expo-router";
|
import { useRouter, Redirect } from "expo-router";
|
||||||
import { useNavigation, DrawerActions } from "@react-navigation/native";
|
|
||||||
import {
|
import {
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
DeviceEventEmitter,
|
DeviceEventEmitter,
|
||||||
|
|
@ -13,6 +12,25 @@ import { REFRESH_EVENT } from "common-ui/src/lib/const-strs";
|
||||||
import { useStaffAuth } from "@/components/context/staff-auth-context";
|
import { useStaffAuth } from "@/components/context/staff-auth-context";
|
||||||
import { tw, MyText, theme } from "common-ui";
|
import { tw, MyText, theme } from "common-ui";
|
||||||
|
|
||||||
|
const SCREEN_TITLES: Record<string, string> = {
|
||||||
|
index: "Dashboard",
|
||||||
|
products: "Products",
|
||||||
|
"prices-overview": "Prices Overview",
|
||||||
|
"product-groupings": "Product Groupings",
|
||||||
|
"dashboard-banners": "Dashboard Banners",
|
||||||
|
slots: "Slots",
|
||||||
|
complaints: "Complaints",
|
||||||
|
"manage-orders": "Manage Orders",
|
||||||
|
coupons: "Coupons",
|
||||||
|
"vendor-snippets": "Vendor Snippets",
|
||||||
|
stores: "Stores",
|
||||||
|
"user-management": "User Management",
|
||||||
|
"send-notifications": "Send Notifications",
|
||||||
|
"product-tags": "Product Tags",
|
||||||
|
"rebalance-orders": "Rebalance Orders",
|
||||||
|
"customize-app": "Customize App",
|
||||||
|
};
|
||||||
|
|
||||||
function CustomDrawerContent() {
|
function CustomDrawerContent() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { logout, staff } = useStaffAuth();
|
const { logout, staff } = useStaffAuth();
|
||||||
|
|
@ -32,121 +50,108 @@ function CustomDrawerContent() {
|
||||||
)}
|
)}
|
||||||
<DrawerItem
|
<DrawerItem
|
||||||
label="Dashboard"
|
label="Dashboard"
|
||||||
onPress={() => router.push("/(drawer)/dashboard" as any)}
|
onPress={() => router.dismissTo("/(drawer)/dashboard" as any)}
|
||||||
icon={({ color, size }) => (
|
icon={({ color, size }) => (
|
||||||
<MaterialIcons name="dashboard" size={size} color={color} />
|
<MaterialIcons name="dashboard" size={size} color={color} />
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{/* <DrawerItem
|
<DrawerItem
|
||||||
label="Add Product"
|
label="Products"
|
||||||
onPress={() => router.push("/(drawer)/add-product" as any)}
|
onPress={() => router.push("/(drawer)/dashboard/products" as any)}
|
||||||
icon={({ color, size }) => (
|
|
||||||
<MaterialIcons name="add" size={size} color={color} />
|
|
||||||
)}
|
|
||||||
/> */}
|
|
||||||
<DrawerItem
|
|
||||||
label="Products"
|
|
||||||
onPress={() => router.push("/(drawer)/products" as any)}
|
|
||||||
icon={({ color, size }) => (
|
|
||||||
<MaterialIcons name="inventory" size={size} color={color} />
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<DrawerItem
|
|
||||||
label="Prices Overview"
|
|
||||||
onPress={() => router.push("/(drawer)/prices-overview" as any)}
|
|
||||||
icon={({ color, size }) => (
|
|
||||||
<MaterialIcons name="attach-money" size={size} color={color} />
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<DrawerItem
|
|
||||||
label="Product Groupings"
|
|
||||||
onPress={() => router.push("/(drawer)/product-groupings" as any)}
|
|
||||||
icon={({ color, size }) => (
|
|
||||||
<MaterialIcons name="group-work" size={size} color={color} />
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<DrawerItem
|
|
||||||
label="Dashboard Banners"
|
|
||||||
onPress={() => router.push("/(drawer)/dashboard-banners" as any)}
|
|
||||||
icon={({ color, size }) => (
|
|
||||||
<MaterialIcons name="view-carousel" size={size} color={color} />
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<DrawerItem
|
|
||||||
label="Slots"
|
|
||||||
onPress={() => router.push("/(drawer)/slots" as any)}
|
|
||||||
icon={({ color, size }) => (
|
|
||||||
<MaterialIcons name="schedule" size={size} color={color} />
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{/* <DrawerItem
|
|
||||||
label="Edit Product"
|
|
||||||
onPress={() => router.push("/(drawer)/edit-product" as any)}
|
|
||||||
icon={({ color, size }) => (
|
icon={({ color, size }) => (
|
||||||
<MaterialIcons name="edit" size={size} color={color} />
|
<MaterialIcons name="inventory" size={size} color={color} />
|
||||||
)}
|
)}
|
||||||
/> */}
|
/>
|
||||||
|
<DrawerItem
|
||||||
|
label="Prices Overview"
|
||||||
|
onPress={() => router.push("/(drawer)/dashboard/prices-overview" as any)}
|
||||||
|
icon={({ color, size }) => (
|
||||||
|
<MaterialIcons name="attach-money" size={size} color={color} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<DrawerItem
|
||||||
|
label="Product Groupings"
|
||||||
|
onPress={() => router.push("/(drawer)/dashboard/product-groupings" as any)}
|
||||||
|
icon={({ color, size }) => (
|
||||||
|
<MaterialIcons name="group-work" size={size} color={color} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<DrawerItem
|
||||||
|
label="Dashboard Banners"
|
||||||
|
onPress={() => router.push("/(drawer)/dashboard/dashboard-banners" as any)}
|
||||||
|
icon={({ color, size }) => (
|
||||||
|
<MaterialIcons name="view-carousel" size={size} color={color} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<DrawerItem
|
||||||
|
label="Slots"
|
||||||
|
onPress={() => router.push("/(drawer)/dashboard/slots" as any)}
|
||||||
|
icon={({ color, size }) => (
|
||||||
|
<MaterialIcons name="schedule" size={size} color={color} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
<DrawerItem
|
<DrawerItem
|
||||||
label="Complaints"
|
label="Complaints"
|
||||||
onPress={() => router.push("/(drawer)/complaints" as any)}
|
onPress={() => router.push("/(drawer)/dashboard/complaints" as any)}
|
||||||
icon={({ color, size }) => (
|
icon={({ color, size }) => (
|
||||||
<MaterialIcons name="report-problem" size={size} color={color} />
|
<MaterialIcons name="report-problem" size={size} color={color} />
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<DrawerItem
|
<DrawerItem
|
||||||
label="Manage Orders"
|
label="Manage Orders"
|
||||||
onPress={() => router.push("/(drawer)/manage-orders" as any)}
|
onPress={() => router.push("/(drawer)/dashboard/manage-orders" as any)}
|
||||||
icon={({ color, size }) => (
|
icon={({ color, size }) => (
|
||||||
<MaterialIcons name="shopping-bag" size={size} color={color} />
|
<MaterialIcons name="shopping-bag" size={size} color={color} />
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<DrawerItem
|
<DrawerItem
|
||||||
label="Coupons"
|
label="Coupons"
|
||||||
onPress={() => router.push("coupons" as any)}
|
onPress={() => router.push("/(drawer)/dashboard/coupons" as any)}
|
||||||
icon={({ color, size }) => (
|
icon={({ color, size }) => (
|
||||||
<MaterialIcons name="local-offer" size={size} color={color} />
|
<MaterialIcons name="local-offer" size={size} color={color} />
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<DrawerItem
|
<DrawerItem
|
||||||
label="Vendor Snippets"
|
label="Vendor Snippets"
|
||||||
onPress={() => router.push("vendor-snippets" as any)}
|
onPress={() => router.push("/(drawer)/dashboard/vendor-snippets" as any)}
|
||||||
icon={({ color, size }) => (
|
icon={({ color, size }) => (
|
||||||
<MaterialIcons name="code" size={size} color={color} />
|
<MaterialIcons name="code" size={size} color={color} />
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<DrawerItem
|
<DrawerItem
|
||||||
label="Stores"
|
label="Stores"
|
||||||
onPress={() => router.push("/(drawer)/stores" as any)}
|
onPress={() => router.push("/(drawer)/dashboard/stores" as any)}
|
||||||
icon={({ color, size }) => (
|
icon={({ color, size }) => (
|
||||||
<MaterialIcons name="store" size={size} color={color} />
|
<MaterialIcons name="store" size={size} color={color} />
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<DrawerItem
|
<DrawerItem
|
||||||
label="User Management"
|
label="User Management"
|
||||||
onPress={() => router.push("/(drawer)/user-management" as any)}
|
onPress={() => router.push("/(drawer)/dashboard/user-management" as any)}
|
||||||
icon={({ color, size }) => (
|
icon={({ color, size }) => (
|
||||||
<MaterialIcons name="people" size={size} color={color} />
|
<MaterialIcons name="people" size={size} color={color} />
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<DrawerItem
|
<DrawerItem
|
||||||
label="Send Notifications"
|
label="Send Notifications"
|
||||||
onPress={() => router.push("/(drawer)/send-notifications" as any)}
|
onPress={() => router.push("/(drawer)/dashboard/send-notifications" as any)}
|
||||||
icon={({ color, size }) => (
|
icon={({ color, size }) => (
|
||||||
<MaterialIcons name="campaign" size={size} color={color} />
|
<MaterialIcons name="campaign" size={size} color={color} />
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<DrawerItem
|
<DrawerItem
|
||||||
label="Logout"
|
label="Logout"
|
||||||
onPress={() => logout()}
|
onPress={() => logout()}
|
||||||
icon={({ color, size }) => (
|
icon={({ color, size }) => (
|
||||||
<MaterialIcons name="logout" size={size} color={color} />
|
<MaterialIcons name="logout" size={size} color={color} />
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</DrawerContentScrollView>
|
</DrawerContentScrollView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Layout() {
|
export default function Layout() {
|
||||||
|
const router = useRouter();
|
||||||
const { isLoggedIn, isLoading } = useStaffAuth();
|
const { isLoggedIn, isLoading } = useStaffAuth();
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
|
|
@ -163,74 +168,72 @@ export default function Layout() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
backBehavior="history"
|
|
||||||
drawerContent={CustomDrawerContent}
|
drawerContent={CustomDrawerContent}
|
||||||
screenOptions={({ navigation, route }) => ({
|
screenOptions={({ navigation }) => {
|
||||||
swipeEnabled: false,
|
const state = navigation.getState();
|
||||||
headerShown: true,
|
const focusedRoute = state?.routes?.[state?.index ?? 0];
|
||||||
headerStyle: {
|
const dashboardStack = (focusedRoute as any)?.state;
|
||||||
backgroundColor: theme.colors.gray1,
|
const nestedIndex = dashboardStack?.index ?? 0;
|
||||||
shadowOpacity: 0,
|
const canGoBack = nestedIndex > 0;
|
||||||
shadowRadius: 0,
|
const nestedRoute = dashboardStack?.routes?.[nestedIndex];
|
||||||
shadowOffset: { height: 0, width: 0 },
|
const title = SCREEN_TITLES[nestedRoute?.name ?? ""] ?? "Dashboard";
|
||||||
elevation: 0,
|
|
||||||
},
|
return {
|
||||||
headerTitleAlign: "center",
|
swipeEnabled: false,
|
||||||
headerLeft: () => (
|
headerShown: true,
|
||||||
<TouchableOpacity
|
headerStyle: {
|
||||||
onPress={() => (navigation as any).openDrawer()}
|
backgroundColor: theme.colors.gray1,
|
||||||
style={{
|
shadowOpacity: 0,
|
||||||
marginLeft: 15,
|
shadowRadius: 0,
|
||||||
width: 40,
|
shadowOffset: { height: 0, width: 0 },
|
||||||
height: 40,
|
elevation: 0,
|
||||||
borderRadius: 20,
|
},
|
||||||
backgroundColor: "#f2f2f2",
|
headerTitle: title,
|
||||||
justifyContent: "center",
|
headerTitleAlign: "center",
|
||||||
alignItems: "center",
|
headerLeft: () => (
|
||||||
}}
|
<TouchableOpacity
|
||||||
>
|
onPress={() =>
|
||||||
<MaterialIcons name="menu" size={24} color="black" />
|
canGoBack ? router.back() : (navigation as any).openDrawer()
|
||||||
</TouchableOpacity>
|
}
|
||||||
),
|
style={{
|
||||||
headerRight: () => (
|
marginLeft: 15,
|
||||||
<TouchableOpacity
|
width: 40,
|
||||||
style={{
|
height: 40,
|
||||||
marginRight: 15,
|
borderRadius: 20,
|
||||||
width: 40,
|
backgroundColor: "#f2f2f2",
|
||||||
height: 40,
|
justifyContent: "center",
|
||||||
borderRadius: 20,
|
alignItems: "center",
|
||||||
backgroundColor: "#f2f2f2",
|
}}
|
||||||
justifyContent: "center",
|
>
|
||||||
alignItems: "center",
|
<MaterialIcons
|
||||||
}}
|
name={canGoBack ? "arrow-back" : "menu"}
|
||||||
onPress={() => {
|
size={24}
|
||||||
DeviceEventEmitter.emit(REFRESH_EVENT);
|
color="black"
|
||||||
}}
|
/>
|
||||||
>
|
</TouchableOpacity>
|
||||||
<MaterialIcons name="refresh" size={24} color="black" />
|
),
|
||||||
</TouchableOpacity>
|
headerRight: () => (
|
||||||
),
|
<TouchableOpacity
|
||||||
})}
|
style={{
|
||||||
|
marginRight: 15,
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
borderRadius: 20,
|
||||||
|
backgroundColor: "#f2f2f2",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
onPress={() => {
|
||||||
|
DeviceEventEmitter.emit(REFRESH_EVENT);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MaterialIcons name="refresh" size={24} color="black" />
|
||||||
|
</TouchableOpacity>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Drawer.Screen name="dashboard" options={{ title: "Dashboard" }} />
|
<Drawer.Screen name="dashboard" options={{ title: "Dashboard" }} />
|
||||||
<Drawer.Screen name="products" options={{ title: "Products" }} />
|
</Drawer>
|
||||||
<Drawer.Screen name="prices-overview" options={{ title: "Prices Overview" }} />
|
|
||||||
<Drawer.Screen name="product-groupings" options={{ title: "Product Groupings" }} />
|
|
||||||
|
|
||||||
|
|
||||||
<Drawer.Screen
|
|
||||||
name="manage-orders"
|
|
||||||
options={{ title: "Manage Orders" }}
|
|
||||||
/>
|
|
||||||
<Drawer.Screen name="complaints" options={{ title: "Complaints" }} />
|
|
||||||
<Drawer.Screen name="coupons" options={{ title: "Coupons" }} />
|
|
||||||
<Drawer.Screen name="slots" options={{ title: "Slots" }} />
|
|
||||||
<Drawer.Screen name="vendor-snippets" options={{ title: "Vendor Snippets" }} />
|
|
||||||
<Drawer.Screen name="stores" options={{ title: "Stores" }} />
|
|
||||||
<Drawer.Screen name="product-tags" options={{ title: "Product Tags" }} />
|
|
||||||
<Drawer.Screen name="rebalance-orders" options={{ title: "Rebalance Orders" }} />
|
|
||||||
<Drawer.Screen name="user-management" options={{ title: "User Management" }} />
|
|
||||||
<Drawer.Screen name="send-notifications" options={{ title: "Send Notifications" }} />
|
|
||||||
</Drawer>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,41 @@ 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: "Dashboard" }} />
|
||||||
|
<Stack.Screen name="complaints" options={{ title: "Complaints" }} />
|
||||||
|
<Stack.Screen name="coupons" options={{ title: "Coupons" }} />
|
||||||
|
<Stack.Screen name="customize-app" options={{ title: "Customize App" }} />
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="index"
|
name="dashboard-banners"
|
||||||
options={{
|
options={{ title: "Dashboard Banners" }}
|
||||||
title: "Dashboard",
|
|
||||||
headerShown: false,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
<Stack.Screen name="manage-orders" options={{ title: "Manage Orders" }} />
|
||||||
|
<Stack.Screen
|
||||||
|
name="prices-overview"
|
||||||
|
options={{ title: "Prices Overview" }}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name="product-groupings"
|
||||||
|
options={{ title: "Product Groupings" }}
|
||||||
|
/>
|
||||||
|
<Stack.Screen name="product-tags" options={{ title: "Product Tags" }} />
|
||||||
|
<Stack.Screen name="products" options={{ title: "Products" }} />
|
||||||
|
<Stack.Screen
|
||||||
|
name="rebalance-orders"
|
||||||
|
options={{ title: "Rebalance Orders" }}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name="send-notifications"
|
||||||
|
options={{ title: "Send Notifications" }}
|
||||||
|
/>
|
||||||
|
<Stack.Screen name="slots" options={{ title: "Slots" }} />
|
||||||
|
<Stack.Screen name="stores" options={{ title: "Stores" }} />
|
||||||
|
<Stack.Screen
|
||||||
|
name="user-management"
|
||||||
|
options={{ title: "User Management" }}
|
||||||
|
/>
|
||||||
|
<Stack.Screen name="vendor-snippets" options={{ title: "Vendor Snippets" }} />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -47,11 +47,11 @@ export default function Complaints() {
|
||||||
}, [hasNextPage, isFetchingNextPage, fetchNextPage]);
|
}, [hasNextPage, isFetchingNextPage, fetchNextPage]);
|
||||||
|
|
||||||
const handleUserPress = useCallback((userId: number) => {
|
const handleUserPress = useCallback((userId: number) => {
|
||||||
router.push(`/(drawer)/user-management/${userId}`);
|
router.push(`/(drawer)/dashboard/user-management/${userId}`);
|
||||||
}, [router]);
|
}, [router]);
|
||||||
|
|
||||||
const handleOrderPress = useCallback((orderId: number) => {
|
const handleOrderPress = useCallback((orderId: number) => {
|
||||||
router.push(`/(drawer)/manage-orders/order-details/${orderId}`);
|
router.push(`/(drawer)/dashboard/manage-orders/order-details/${orderId}`);
|
||||||
}, [router]);
|
}, [router]);
|
||||||
|
|
||||||
const handleMarkResolved = (id: number) => {
|
const handleMarkResolved = (id: number) => {
|
||||||
|
|
@ -24,9 +24,9 @@ export default function CreateCoupon() {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleCreateCoupon = (values: any) => {
|
const handleCreateCoupon = async (values: any) => {
|
||||||
console.log('Form values:', values); // Debug log
|
console.log('Form values:', values); // Debug log
|
||||||
const { isReservedCoupon, ...rest } = values;
|
const { isReservedCoupon, couponCodes, ...rest } = values;
|
||||||
// Transform targetUsers array to targetUser for backend compatibility
|
// Transform targetUsers array to targetUser for backend compatibility
|
||||||
const payload = {
|
const payload = {
|
||||||
...rest,
|
...rest,
|
||||||
|
|
@ -35,6 +35,24 @@ export default function CreateCoupon() {
|
||||||
delete payload.targetUsers;
|
delete payload.targetUsers;
|
||||||
console.log('Payload:', payload); // Debug log
|
console.log('Payload:', payload); // Debug log
|
||||||
|
|
||||||
|
if (isReservedCoupon && Array.isArray(couponCodes) && couponCodes.length > 0) {
|
||||||
|
// Create one reserved coupon per code, same config
|
||||||
|
const codes = couponCodes.map((c: string) => c.trim()).filter(Boolean);
|
||||||
|
try {
|
||||||
|
for (const code of codes) {
|
||||||
|
await createReservedCoupon.mutateAsync({ ...payload, couponCode: code });
|
||||||
|
}
|
||||||
|
await refetchCoupons()
|
||||||
|
await refetchReservedCoupons()
|
||||||
|
Alert.alert('Success', `${codes.length} reserved coupon${codes.length > 1 ? 's' : ''} created successfully`, [
|
||||||
|
{ text: 'OK', onPress: () => router.back() }
|
||||||
|
]);
|
||||||
|
} catch (error: any) {
|
||||||
|
Alert.alert('Error', error.message || 'Failed to create reserved coupons');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const mutation = isReservedCoupon ? createReservedCoupon : createCoupon;
|
const mutation = isReservedCoupon ? createReservedCoupon : createCoupon;
|
||||||
const isLoading = isReservedCoupon ? createReservedCoupon.isPending : createCoupon.isPending;
|
const isLoading = isReservedCoupon ? createReservedCoupon.isPending : createCoupon.isPending;
|
||||||
|
|
||||||
|
|
@ -90,7 +90,7 @@ const CouponItem = ({ item, onDelete }: { item: any; onDelete: (id: number) => v
|
||||||
|
|
||||||
<View style={tw`flex-row mt-3 gap-2`}>
|
<View style={tw`flex-row mt-3 gap-2`}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => router.push(`/coupons/edit/${item.id}`)}
|
onPress={() => router.push(`/(drawer)/dashboard/coupons/edit/${item.id}`)}
|
||||||
style={tw`bg-blue-500 p-3 rounded-lg shadow-md flex-1 flex-row items-center justify-center`}
|
style={tw`bg-blue-500 p-3 rounded-lg shadow-md flex-1 flex-row items-center justify-center`}
|
||||||
>
|
>
|
||||||
<MaterialCommunityIcons name="pencil" size={16} color="white" />
|
<MaterialCommunityIcons name="pencil" size={16} color="white" />
|
||||||
|
|
@ -497,7 +497,7 @@ export default function Coupons() {
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<MyTouchableOpacity
|
<MyTouchableOpacity
|
||||||
onPress={() => router.push('/coupons/create')}
|
onPress={() => router.push('/(drawer)/dashboard/coupons/create')}
|
||||||
activeOpacity={0.95}
|
activeOpacity={0.95}
|
||||||
style={{ position: 'absolute', bottom: 32, right: 24, zIndex: 100 }}
|
style={{ position: 'absolute', bottom: 32, right: 24, zIndex: 100 }}
|
||||||
>
|
>
|
||||||
|
|
@ -217,7 +217,7 @@ export default function ReservedCoupons() {
|
||||||
</View>
|
</View>
|
||||||
<MyText style={tw`text-xl font-semibold text-gray-600 mb-2`}>No Reserved Coupons Yet</MyText>
|
<MyText style={tw`text-xl font-semibold text-gray-600 mb-2`}>No Reserved Coupons Yet</MyText>
|
||||||
<MyText style={tw`text-gray-500 text-center mb-4`}>Create your first reserved coupon to start offering secret discounts</MyText>
|
<MyText style={tw`text-gray-500 text-center mb-4`}>Create your first reserved coupon to start offering secret discounts</MyText>
|
||||||
<MyButton onPress={() => router.push('/coupons/create')} style={tw`bg-blue-500`}>
|
<MyButton onPress={() => router.push('/(drawer)/dashboard/coupons/create')} style={tw`bg-blue-500`}>
|
||||||
<View style={tw`flex-row items-center`}>
|
<View style={tw`flex-row items-center`}>
|
||||||
<MaterialCommunityIcons name="plus" size={16} color="white" />
|
<MaterialCommunityIcons name="plus" size={16} color="white" />
|
||||||
<MyText style={tw`text-white font-semibold ml-1`}>Create Reserved Coupon</MyText>
|
<MyText style={tw`text-white font-semibold ml-1`}>Create Reserved Coupon</MyText>
|
||||||
|
|
@ -230,7 +230,7 @@ export default function ReservedCoupons() {
|
||||||
|
|
||||||
{/* FAB for Add New Reserved Coupon */}
|
{/* FAB for Add New Reserved Coupon */}
|
||||||
<MyTouchableOpacity
|
<MyTouchableOpacity
|
||||||
onPress={() => router.push('/coupons/create')}
|
onPress={() => router.push('/(drawer)/dashboard/coupons/create')}
|
||||||
activeOpacity={0.95}
|
activeOpacity={0.95}
|
||||||
style={{ position: 'absolute', bottom: 32, right: 24, zIndex: 100 }}
|
style={{ position: 'absolute', bottom: 32, right: 24, zIndex: 100 }}
|
||||||
>
|
>
|
||||||
|
|
@ -18,7 +18,7 @@ import {
|
||||||
MyTouchableOpacity,
|
MyTouchableOpacity,
|
||||||
} from "common-ui";
|
} from "common-ui";
|
||||||
import { useRouter } from "expo-router";
|
import { useRouter } from "expo-router";
|
||||||
import { trpc } from "../../../src/trpc-client";
|
import { trpc } from "../../../../src/trpc-client";
|
||||||
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
|
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
import { View, ScrollView, Alert } from 'react-native';
|
import { View, ScrollView, Alert } from 'react-native';
|
||||||
import { Formik } from 'formik';
|
import { Formik } from 'formik';
|
||||||
import { MyText, MyTextInput, MyTouchableOpacity, tw, AppContainer, Checkbox } from 'common-ui';
|
import { MyText, MyTextInput, MyTouchableOpacity, tw, AppContainer, Checkbox } 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 MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
||||||
|
|
||||||
|
|
@ -71,7 +71,7 @@ const ConstantInput: React.FC<ConstantInputProps> = ({ constantKey, value, setFi
|
||||||
{CONST_LABELS[constantKey] || constantKey}
|
{CONST_LABELS[constantKey] || constantKey}
|
||||||
</MyText>
|
</MyText>
|
||||||
<MyTouchableOpacity
|
<MyTouchableOpacity
|
||||||
onPress={() => router.push('/(drawer)/customize-app/popular-items')}
|
onPress={() => router.push('/(drawer)/dashboard/customize-app/popular-items')}
|
||||||
style={tw`bg-blue-50 border-2 border-dashed border-blue-200 p-4 rounded-lg flex-row items-center justify-center`}
|
style={tw`bg-blue-50 border-2 border-dashed border-blue-200 p-4 rounded-lg flex-row items-center justify-center`}
|
||||||
>
|
>
|
||||||
<MaterialIcons name="edit" size={20} color="#3b82f6" style={tw`mr-2`} />
|
<MaterialIcons name="edit" size={20} color="#3b82f6" style={tw`mr-2`} />
|
||||||
|
|
@ -93,7 +93,7 @@ const ConstantInput: React.FC<ConstantInputProps> = ({ constantKey, value, setFi
|
||||||
{CONST_LABELS[constantKey] || constantKey}
|
{CONST_LABELS[constantKey] || constantKey}
|
||||||
</MyText>
|
</MyText>
|
||||||
<MyTouchableOpacity
|
<MyTouchableOpacity
|
||||||
onPress={() => router.push('/(drawer)/customize-app/all-items-order')}
|
onPress={() => router.push('/(drawer)/dashboard/customize-app/all-items-order')}
|
||||||
style={tw`bg-green-50 border-2 border-dashed border-green-200 p-4 rounded-lg flex-row items-center justify-center`}
|
style={tw`bg-green-50 border-2 border-dashed border-green-200 p-4 rounded-lg flex-row items-center justify-center`}
|
||||||
>
|
>
|
||||||
<MaterialIcons name="reorder" size={20} color="#16a34a" style={tw`mr-2`} />
|
<MaterialIcons name="reorder" size={20} color="#16a34a" style={tw`mr-2`} />
|
||||||
|
|
@ -19,9 +19,9 @@ import {
|
||||||
BottomDropdown,
|
BottomDropdown,
|
||||||
MyTouchableOpacity,
|
MyTouchableOpacity,
|
||||||
} from "common-ui";
|
} from "common-ui";
|
||||||
import ProductsSelector from "../../../components/ProductsSelector";
|
import ProductsSelector from "../../../../components/ProductsSelector";
|
||||||
import { useRouter } from "expo-router";
|
import { useRouter } from "expo-router";
|
||||||
import { trpc } from "../../../src/trpc-client";
|
import { trpc } from "../../../../src/trpc-client";
|
||||||
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
|
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { View, ScrollView, TouchableOpacity, Alert, Image, RefreshControl } from 'react-native';
|
import { View, ScrollView, TouchableOpacity, Alert, Image, RefreshControl } from 'react-native';
|
||||||
import { AppContainer, MyText, tw, MyTouchableOpacity } from 'common-ui';
|
import { AppContainer, MyText, tw, MyTouchableOpacity } from 'common-ui';
|
||||||
import { trpc } from '../../../src/trpc-client';
|
import { trpc } from '../../../../src/trpc-client';
|
||||||
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
||||||
import { useRouter } from 'expo-router';
|
import { useRouter } from 'expo-router';
|
||||||
|
|
||||||
|
|
@ -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(`/(drawer)/dashboard/dashboard-banners/edit/${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/dashboard-banners/create' as any);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
|
|
@ -63,13 +63,13 @@ export default function Dashboard() {
|
||||||
|
|
||||||
|
|
||||||
const handleManageOrdersPress = () => {
|
const handleManageOrdersPress = () => {
|
||||||
setNavigationTarget('/manage-orders/orders');
|
setNavigationTarget('/(drawer)/dashboard/manage-orders/orders');
|
||||||
router.push('/(drawer)/manage-orders');
|
router.push('/(drawer)/dashboard/manage-orders');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeliverySequencesPress = () => {
|
const handleDeliverySequencesPress = () => {
|
||||||
setNavigationTarget('/manage-orders/delivery-sequences');
|
setNavigationTarget('/(drawer)/dashboard/manage-orders/delivery-sequences');
|
||||||
router.push('/(drawer)/manage-orders');
|
router.push('/(drawer)/dashboard/manage-orders');
|
||||||
};
|
};
|
||||||
|
|
||||||
const menuItems: MenuItem[] = [
|
const menuItems: MenuItem[] = [
|
||||||
|
|
@ -77,7 +77,7 @@ export default function Dashboard() {
|
||||||
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)/dashboard/manage-orders',
|
||||||
category: 'orders',
|
category: 'orders',
|
||||||
iconColor: '#10B981',
|
iconColor: '#10B981',
|
||||||
iconBg: '#D1FAE5',
|
iconBg: '#D1FAE5',
|
||||||
|
|
@ -87,7 +87,7 @@ export default function Dashboard() {
|
||||||
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)/dashboard/manage-orders/delivery-sequences',
|
||||||
category: 'orders',
|
category: 'orders',
|
||||||
iconColor: '#8B5CF6',
|
iconColor: '#8B5CF6',
|
||||||
iconBg: '#EDE9FE',
|
iconBg: '#EDE9FE',
|
||||||
|
|
@ -97,7 +97,7 @@ export default function Dashboard() {
|
||||||
title: 'Delivery Slots',
|
title: 'Delivery Slots',
|
||||||
icon: 'schedule',
|
icon: 'schedule',
|
||||||
description: 'Configure delivery times',
|
description: 'Configure delivery times',
|
||||||
route: '/(drawer)/slots' as any,
|
route: '/(drawer)/dashboard/slots' as any,
|
||||||
category: 'quick',
|
category: 'quick',
|
||||||
iconColor: '#06B6D4',
|
iconColor: '#06B6D4',
|
||||||
iconBg: '#CFFAFE',
|
iconBg: '#CFFAFE',
|
||||||
|
|
@ -107,7 +107,7 @@ export default function Dashboard() {
|
||||||
title: 'Add Product',
|
title: 'Add Product',
|
||||||
icon: 'add-circle',
|
icon: 'add-circle',
|
||||||
description: 'Create a new product listing',
|
description: 'Create a new product listing',
|
||||||
route: '/products/add',
|
route: '/(drawer)/dashboard/products/add',
|
||||||
category: 'quick',
|
category: 'quick',
|
||||||
iconColor: theme.colors.brand500,
|
iconColor: theme.colors.brand500,
|
||||||
iconBg: theme.colors.brand50,
|
iconBg: theme.colors.brand50,
|
||||||
|
|
@ -116,7 +116,7 @@ export default function Dashboard() {
|
||||||
title: 'Complaints',
|
title: 'Complaints',
|
||||||
icon: 'report-problem',
|
icon: 'report-problem',
|
||||||
description: 'Handle customer complaints',
|
description: 'Handle customer complaints',
|
||||||
route: '/(drawer)/complaints',
|
route: '/(drawer)/dashboard/complaints',
|
||||||
category: 'quick',
|
category: 'quick',
|
||||||
iconColor: '#F59E0B',
|
iconColor: '#F59E0B',
|
||||||
iconBg: '#FEF3C7',
|
iconBg: '#FEF3C7',
|
||||||
|
|
@ -126,7 +126,7 @@ export default function Dashboard() {
|
||||||
title: 'Products',
|
title: 'Products',
|
||||||
icon: 'inventory-2',
|
icon: 'inventory-2',
|
||||||
description: 'Manage product catalog',
|
description: 'Manage product catalog',
|
||||||
route: '/(drawer)/products',
|
route: '/(drawer)/dashboard/products',
|
||||||
category: 'products',
|
category: 'products',
|
||||||
iconColor: '#6366F1',
|
iconColor: '#6366F1',
|
||||||
iconBg: '#E0E7FF',
|
iconBg: '#E0E7FF',
|
||||||
|
|
@ -135,7 +135,7 @@ export default function Dashboard() {
|
||||||
title: 'Prices Overview',
|
title: 'Prices Overview',
|
||||||
icon: 'attach-money',
|
icon: 'attach-money',
|
||||||
description: 'Update product pricing',
|
description: 'Update product pricing',
|
||||||
route: '/(drawer)/prices-overview',
|
route: '/(drawer)/dashboard/prices-overview',
|
||||||
category: 'products',
|
category: 'products',
|
||||||
iconColor: '#059669',
|
iconColor: '#059669',
|
||||||
iconBg: '#ECFDF5',
|
iconBg: '#ECFDF5',
|
||||||
|
|
@ -144,7 +144,7 @@ export default function Dashboard() {
|
||||||
title: 'Product Tags',
|
title: 'Product Tags',
|
||||||
icon: 'label',
|
icon: 'label',
|
||||||
description: 'Organize with tags',
|
description: 'Organize with tags',
|
||||||
route: '/(drawer)/product-tags',
|
route: '/(drawer)/dashboard/product-tags',
|
||||||
category: 'products',
|
category: 'products',
|
||||||
iconColor: '#EC4899',
|
iconColor: '#EC4899',
|
||||||
iconBg: '#FCE7F3',
|
iconBg: '#FCE7F3',
|
||||||
|
|
@ -153,7 +153,7 @@ export default function Dashboard() {
|
||||||
title: 'Product Groupings',
|
title: 'Product Groupings',
|
||||||
icon: 'category',
|
icon: 'category',
|
||||||
description: 'Group products together',
|
description: 'Group products together',
|
||||||
route: '/(drawer)/product-groupings',
|
route: '/(drawer)/dashboard/product-groupings',
|
||||||
category: 'products',
|
category: 'products',
|
||||||
iconColor: '#8B5CF6',
|
iconColor: '#8B5CF6',
|
||||||
iconBg: '#F3E8FF',
|
iconBg: '#F3E8FF',
|
||||||
|
|
@ -162,7 +162,7 @@ export default function Dashboard() {
|
||||||
title: 'Stores',
|
title: 'Stores',
|
||||||
icon: 'store',
|
icon: 'store',
|
||||||
description: 'Manage store locations',
|
description: 'Manage store locations',
|
||||||
route: '/(drawer)/stores',
|
route: '/(drawer)/dashboard/stores',
|
||||||
category: 'settings',
|
category: 'settings',
|
||||||
iconColor: '#14B8A6',
|
iconColor: '#14B8A6',
|
||||||
iconBg: '#CCFBF1',
|
iconBg: '#CCFBF1',
|
||||||
|
|
@ -171,7 +171,7 @@ export default function Dashboard() {
|
||||||
title: 'Coupons',
|
title: 'Coupons',
|
||||||
icon: 'local-offer',
|
icon: 'local-offer',
|
||||||
description: 'Create discount coupons',
|
description: 'Create discount coupons',
|
||||||
route: '/(drawer)/coupons',
|
route: '/(drawer)/dashboard/coupons',
|
||||||
category: 'marketing',
|
category: 'marketing',
|
||||||
iconColor: '#F97316',
|
iconColor: '#F97316',
|
||||||
iconBg: '#FFEDD5',
|
iconBg: '#FFEDD5',
|
||||||
|
|
@ -180,7 +180,7 @@ export default function Dashboard() {
|
||||||
title: 'App Constants',
|
title: 'App Constants',
|
||||||
icon: 'settings-applications',
|
icon: 'settings-applications',
|
||||||
description: 'Customize app settings',
|
description: 'Customize app settings',
|
||||||
route: '/(drawer)/customize-app',
|
route: '/(drawer)/dashboard/customize-app',
|
||||||
category: 'settings',
|
category: 'settings',
|
||||||
iconColor: '#7C3AED',
|
iconColor: '#7C3AED',
|
||||||
iconBg: '#F3E8FF',
|
iconBg: '#F3E8FF',
|
||||||
|
|
@ -189,7 +189,7 @@ export default function Dashboard() {
|
||||||
title: 'User Management',
|
title: 'User Management',
|
||||||
icon: 'people',
|
icon: 'people',
|
||||||
description: 'View and manage all users',
|
description: 'View and manage all users',
|
||||||
route: '/(drawer)/user-management',
|
route: '/(drawer)/dashboard/user-management',
|
||||||
category: 'users',
|
category: 'users',
|
||||||
iconColor: '#0EA5E9',
|
iconColor: '#0EA5E9',
|
||||||
iconBg: '#E0F2FE',
|
iconBg: '#E0F2FE',
|
||||||
|
|
@ -198,7 +198,7 @@ export default function Dashboard() {
|
||||||
title: 'Send Notifications',
|
title: 'Send Notifications',
|
||||||
icon: 'campaign',
|
icon: 'campaign',
|
||||||
description: 'Send push notifications to users',
|
description: 'Send push notifications to users',
|
||||||
route: '/(drawer)/send-notifications',
|
route: '/(drawer)/dashboard/send-notifications',
|
||||||
category: 'users',
|
category: 'users',
|
||||||
iconColor: '#8B5CF6',
|
iconColor: '#8B5CF6',
|
||||||
iconBg: '#F3E8FF',
|
iconBg: '#F3E8FF',
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,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)/dashboard/manage-orders/delivery-sequences?slotId=${selectedSlotId}`);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -55,9 +55,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)/dashboard/manage-orders/orders?filter=flash');
|
||||||
} else {
|
} else {
|
||||||
router.push('/manage-orders/orders');
|
router.push('/(drawer)/dashboard/manage-orders/orders');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -378,7 +378,7 @@ export default function OrderDetails() {
|
||||||
|
|
||||||
{/* Customer Details */}
|
{/* Customer Details */}
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => order.userId && router.push(`/(drawer)/user-management/${order.userId}`)}
|
onPress={() => order.userId && router.push(`/(drawer)/dashboard/user-management/${order.userId}`)}
|
||||||
activeOpacity={0.7}
|
activeOpacity={0.7}
|
||||||
style={tw`bg-white p-5 rounded-2xl shadow-sm mb-4 border border-gray-100`}
|
style={tw`bg-white p-5 rounded-2xl shadow-sm mb-4 border border-gray-100`}
|
||||||
>
|
>
|
||||||
|
|
@ -784,7 +784,7 @@ export default function OrderDetails() {
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)}
|
)}
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => router.push("/(drawer)/manage-orders")}
|
onPress={() => router.push("/(drawer)/dashboard/manage-orders")}
|
||||||
style={tw`bg-gray-900 rounded-xl py-4 items-center shadow-lg`}
|
style={tw`bg-gray-900 rounded-xl py-4 items-center shadow-lg`}
|
||||||
>
|
>
|
||||||
<MyText style={tw`text-white font-bold text-base`}>
|
<MyText style={tw`text-white font-bold text-base`}>
|
||||||
|
|
@ -284,17 +284,17 @@ const OrderItem = ({ order, refetch }: { order: OrderType; refetch: () => void }
|
||||||
>
|
>
|
||||||
<MaterialIcons name="shopping-cart" size={16} color="#3B82F6" />
|
<MaterialIcons name="shopping-cart" size={16} color="#3B82F6" />
|
||||||
<MyText style={tw`text-sm font-medium text-blue-700 ml-2`}>
|
<MyText style={tw`text-sm font-medium text-blue-700 ml-2`}>
|
||||||
{order.items.length} {order.items.length === 1 ? 'item' : 'items'}
|
{`${order.items.length} ${order.items.length === 1 ? 'item' : 'items'}`}
|
||||||
</MyText>
|
</MyText>
|
||||||
{order.isFlashDelivery && (
|
{order.isFlashDelivery && (
|
||||||
<View style={tw`ml-2 bg-amber-100 px-1.5 py-0.5 rounded-full`}>
|
<View style={tw`ml-2 bg-amber-100 px-1.5 py-0.5 rounded-full`}>
|
||||||
<MyText style={tw`text-xs font-bold text-amber-700`}>⚡</MyText>
|
<MaterialIcons name="bolt" size={12} color="#D97706" />
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<View style={tw`flex-row items-center`}>
|
<View style={tw`flex-row items-center`}>
|
||||||
<MyText style={tw`text-base font-semibold text-gray-900 mr-2`}>Total:</MyText>
|
<MyText style={tw`text-base font-semibold text-gray-900 mr-2`}>Total:</MyText>
|
||||||
<MyText style={tw`text-lg font-bold ${order.isFlashDelivery ? 'text-amber-700' : 'text-gray-900'}`}>₹{order.totalAmount}</MyText>
|
<MyText style={tw`text-lg font-bold ${order.isFlashDelivery ? 'text-amber-700' : 'text-gray-900'}`}>{`₹${order.totalAmount}`}</MyText>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
@ -315,8 +315,8 @@ const OrderItem = ({ order, refetch }: { order: OrderType; refetch: () => void }
|
||||||
)}
|
)}
|
||||||
{order.discountAmount && (
|
{order.discountAmount && (
|
||||||
<MyText style={tw`text-sm font-bold text-pink-800`}>
|
<MyText style={tw`text-sm font-bold text-pink-800`}>
|
||||||
Discount: ₹{order.discountAmount}
|
{`Discount: ₹${order.discountAmount}`}
|
||||||
</MyText>
|
</MyText>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
@ -353,7 +353,7 @@ const OrderItem = ({ order, refetch }: { order: OrderType; refetch: () => void }
|
||||||
<View style={tw`pt-3 border-t border-gray-100`}>
|
<View style={tw`pt-3 border-t border-gray-100`}>
|
||||||
<View style={tw`flex-row justify-between items-center`}>
|
<View style={tw`flex-row justify-between items-center`}>
|
||||||
<MyText style={tw`text-sm text-gray-500`}>Delivery Charge</MyText>
|
<MyText style={tw`text-sm text-gray-500`}>Delivery Charge</MyText>
|
||||||
<MyText style={tw`text-sm text-gray-900`}>₹{order.deliveryCharge}</MyText>
|
<MyText style={tw`text-sm text-gray-900`}>{`₹${order.deliveryCharge}`}</MyText>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
@ -407,13 +407,13 @@ const OrderItem = ({ order, refetch }: { order: OrderType; refetch: () => void }
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
<MyText style={tw`text-sm text-gray-600 mb-6`}>
|
<MyText style={tw`text-sm text-gray-600 mb-6`}>
|
||||||
Total: ₹{order.totalAmount}
|
{`Total: ₹${order.totalAmount}`}
|
||||||
</MyText>
|
</MyText>
|
||||||
{order.items.map((item, idx) => (
|
{order.items.map((item, idx) => (
|
||||||
<View key={idx} style={tw`py-2 border-b border-gray-50 last:border-0`}>
|
<View key={idx} style={tw`py-2 border-b border-gray-50 last:border-0`}>
|
||||||
<View style={tw`flex-row items-center`}>
|
<View style={tw`flex-row items-center`}>
|
||||||
<View style={tw`bg-gray-100 px-2 py-1 rounded items-center justify-center mr-2`}>
|
<View style={tw`bg-gray-100 px-2 py-1 rounded items-center justify-center mr-2`}>
|
||||||
<MyText style={tw`text-xs font-bold text-gray-600`}>{item.quantity} x {item.productSize}{item.unit}</MyText>
|
<MyText style={tw`text-xs font-bold text-gray-600`}>{`${item.quantity} x ${item.productSize}${item.unit}`}</MyText>
|
||||||
</View>
|
</View>
|
||||||
<MyText style={tw`text-sm text-gray-800 flex-1`} numberOfLines={1} ellipsizeMode="tail">
|
<MyText style={tw`text-sm text-gray-800 flex-1`} numberOfLines={1} ellipsizeMode="tail">
|
||||||
{item.name.length > 30 ? `${item.name.substring(0, 30)}...` : item.name}
|
{item.name.length > 30 ? `${item.name.substring(0, 30)}...` : item.name}
|
||||||
|
|
@ -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() {
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
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';
|
||||||
|
|
||||||
|
|
@ -10,7 +10,7 @@ import {
|
||||||
MyTouchableOpacity,
|
MyTouchableOpacity,
|
||||||
BottomDialog,
|
BottomDialog,
|
||||||
} from "common-ui";
|
} from "common-ui";
|
||||||
import { trpc } from "../../../src/trpc-client";
|
import { trpc } from "../../../../src/trpc-client";
|
||||||
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
|
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
|
||||||
import { LinearGradient } from "expo-linear-gradient";
|
import { LinearGradient } from "expo-linear-gradient";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
@ -143,11 +143,11 @@ export default function ProductGroupings() {
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleCreate = () => {
|
const handleCreate = () => {
|
||||||
router.push("/product-groupings/create");
|
router.push("/(drawer)/dashboard/product-groupings/create");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEdit = (group: ProductGroup) => {
|
const handleEdit = (group: ProductGroup) => {
|
||||||
router.push(`/product-groupings/edit/${group.id}`);
|
router.push(`/(drawer)/dashboard/product-groupings/edit/${group.id}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (id: number) => {
|
const handleDelete = (id: number) => {
|
||||||
|
|
@ -18,7 +18,7 @@ import {
|
||||||
MyTouchableOpacity,
|
MyTouchableOpacity,
|
||||||
} from 'common-ui';
|
} from 'common-ui';
|
||||||
import { useRouter } from 'expo-router';
|
import { useRouter } from 'expo-router';
|
||||||
import { trpc } from '../../../src/trpc-client';
|
import { trpc } from '../../../../src/trpc-client';
|
||||||
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
|
@ -153,7 +153,7 @@ export default function ProductDetail() {
|
||||||
const defaultSku = product?.skus?.[0]
|
const defaultSku = product?.skus?.[0]
|
||||||
|
|
||||||
const handleEdit = () => {
|
const handleEdit = () => {
|
||||||
router.push(`/products/edit?id=${productId}` as any);
|
router.push(`/(drawer)/dashboard/products/edit?id=${productId}` as any);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -52,11 +52,11 @@ export default function Products() {
|
||||||
}, [products, searchTerm, activeFilter]);
|
}, [products, searchTerm, activeFilter]);
|
||||||
|
|
||||||
const handleEdit = (productId: number) => {
|
const handleEdit = (productId: number) => {
|
||||||
router.push(`/products/edit?id=${productId}` as any);
|
router.push(`/(drawer)/dashboard/products/edit?id=${productId}` as any);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleViewDetails = (productId: number) => {
|
const handleViewDetails = (productId: number) => {
|
||||||
router.push(`/products/detail/${productId}` as any);
|
router.push(`/(drawer)/dashboard/products/detail/${productId}` as any);
|
||||||
};
|
};
|
||||||
|
|
||||||
const FilterButton = ({ filter, label, count }: { filter: FilterType; label: string; count: number }) => (
|
const FilterButton = ({ filter, label, count }: { filter: FilterType; label: string; count: number }) => (
|
||||||
|
|
@ -105,7 +105,7 @@ export default function Products() {
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<View style={tw`flex-row justify-between items-center mb-6`}>
|
<View style={tw`flex-row justify-between items-center mb-6`}>
|
||||||
<MyText style={tw`text-2xl font-bold text-gray-800`}>Products</MyText>
|
<MyText style={tw`text-2xl font-bold text-gray-800`}>Products</MyText>
|
||||||
<MyButton onPress={() => router.push('/products/add' as any)}>
|
<MyButton onPress={() => router.push('/(drawer)/dashboard/products/add' as any)}>
|
||||||
Add Product
|
Add Product
|
||||||
</MyButton>
|
</MyButton>
|
||||||
</View>
|
</View>
|
||||||
|
|
@ -2,7 +2,7 @@ import React, { useState } from 'react';
|
||||||
import { View, TouchableOpacity, Alert, FlatList } from 'react-native';
|
import { View, TouchableOpacity, Alert, FlatList } from 'react-native';
|
||||||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||||
import { MyText, tw, MyTouchableOpacity, MyFlatList, BottomDialog } from 'common-ui';
|
import { MyText, tw, MyTouchableOpacity, MyFlatList, BottomDialog } from 'common-ui';
|
||||||
import { trpc } from '../../../src/trpc-client';
|
import { trpc } from '../../../../src/trpc-client';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { LinearGradient } from 'expo-linear-gradient';
|
import { LinearGradient } from 'expo-linear-gradient';
|
||||||
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { Stack } from 'expo-router'
|
||||||
|
|
||||||
|
export default function Layout() {
|
||||||
|
return (
|
||||||
|
<Stack screenOptions={{ headerShown: false }}>
|
||||||
|
<Stack.Screen name="index" options={{ title: 'Send Notifications' }} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -55,7 +55,7 @@ const SlotItemComponent: React.FC<SlotItemProps> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => router.push(`/(drawer)/slots/slot-details?slotId=${slot.id}`)}
|
onPress={() => router.push(`/(drawer)/dashboard/slots/slot-details?slotId=${slot.id}`)}
|
||||||
activeOpacity={0.7}
|
activeOpacity={0.7}
|
||||||
>
|
>
|
||||||
<View style={tw`bg-white p-5 mb-4 rounded-3xl shadow-sm border border-gray-100`}>
|
<View style={tw`bg-white p-5 mb-4 rounded-3xl shadow-sm border border-gray-100`}>
|
||||||
|
|
@ -18,7 +18,7 @@ export default function AddStore() {
|
||||||
onSuccess: async (data) => {
|
onSuccess: async (data) => {
|
||||||
await refetchStores();
|
await refetchStores();
|
||||||
Alert.alert('Success', data.message);
|
Alert.alert('Success', data.message);
|
||||||
router.push('/stores' as any); // Navigate back to stores list
|
router.push('/(drawer)/dashboard/stores' as any); // Navigate back to stores list
|
||||||
},
|
},
|
||||||
onError: (error: any) => {
|
onError: (error: any) => {
|
||||||
Alert.alert('Error', error.message || 'Failed to create store');
|
Alert.alert('Error', error.message || 'Failed to create store');
|
||||||
|
|
@ -23,7 +23,7 @@ export default function EditStore() {
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
refetch();
|
refetch();
|
||||||
Alert.alert('Success', data.message);
|
Alert.alert('Success', data.message);
|
||||||
router.push('/stores' as any);
|
router.push('/(drawer)/dashboard/stores' as any);
|
||||||
},
|
},
|
||||||
onError: (error: any) => {
|
onError: (error: any) => {
|
||||||
Alert.alert('Error', error.message || 'Failed to update store');
|
Alert.alert('Error', error.message || 'Failed to update store');
|
||||||
|
|
@ -106,7 +106,7 @@ export default function Stores() {
|
||||||
const stores = storesData?.stores || [];
|
const stores = storesData?.stores || [];
|
||||||
|
|
||||||
const handleEdit = (storeId: number) => {
|
const handleEdit = (storeId: number) => {
|
||||||
router.push({ pathname: '/stores/edit', params: { id: storeId } });
|
router.push({ pathname: '/(drawer)/dashboard/stores/edit', params: { id: storeId } });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (storeId: number) => {
|
const handleDelete = (storeId: number) => {
|
||||||
|
|
@ -207,7 +207,7 @@ export default function Stores() {
|
||||||
<Animated.View entering={FadeInUp.delay(500)} style={tw`absolute bottom-8 right-6 shadow-xl`}>
|
<Animated.View entering={FadeInUp.delay(500)} style={tw`absolute bottom-8 right-6 shadow-xl`}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
activeOpacity={0.8}
|
activeOpacity={0.8}
|
||||||
onPress={() => router.push('/stores/add' as any)}
|
onPress={() => router.push('/(drawer)/dashboard/stores/add' as any)}
|
||||||
>
|
>
|
||||||
<LinearGradient
|
<LinearGradient
|
||||||
colors={['#2563EB', '#1D4ED8']}
|
colors={['#2563EB', '#1D4ED8']}
|
||||||
|
|
@ -112,7 +112,7 @@ export default function UserDetails() {
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleOrderPress = useCallback((orderId: number) => {
|
const handleOrderPress = useCallback((orderId: number) => {
|
||||||
router.push(`/(drawer)/manage-orders/order-details/${orderId}`);
|
router.push(`/(drawer)/dashboard/manage-orders/order-details/${orderId}`);
|
||||||
}, [router]);
|
}, [router]);
|
||||||
|
|
||||||
const handleSuspensionToggle = useCallback((isSuspended: boolean) => {
|
const handleSuspensionToggle = useCallback((isSuspended: boolean) => {
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { Stack } from 'expo-router'
|
||||||
|
|
||||||
|
export default function Layout() {
|
||||||
|
return (
|
||||||
|
<Stack screenOptions={{ headerShown: false }}>
|
||||||
|
<Stack.Screen name="index" options={{ title: 'User Management' }} />
|
||||||
|
<Stack.Screen name="[id]" options={{ title: 'User Details' }} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -189,7 +189,7 @@ export default function UserManagement() {
|
||||||
}, [hasNextPage, isFetchingNextPage, fetchNextPage]);
|
}, [hasNextPage, isFetchingNextPage, fetchNextPage]);
|
||||||
|
|
||||||
const handleUserPress = useCallback((userId: number) => {
|
const handleUserPress = useCallback((userId: number) => {
|
||||||
router.push(`/(drawer)/user-management/${userId}`);
|
router.push(`/(drawer)/dashboard/user-management/${userId}`);
|
||||||
}, [router]);
|
}, [router]);
|
||||||
|
|
||||||
const renderUserItem = useCallback(({ item, index }: { item: User; index: number }) => {
|
const renderUserItem = useCallback(({ item, index }: { item: User; index: number }) => {
|
||||||
|
|
@ -16,22 +16,22 @@ import {
|
||||||
useMarkDataFetchers,
|
useMarkDataFetchers,
|
||||||
MyTouchableOpacity,
|
MyTouchableOpacity,
|
||||||
} from "common-ui";
|
} from "common-ui";
|
||||||
import { trpc, trpcClient } from "../../../src/trpc-client";
|
import { trpc, trpcClient } from "../../../../src/trpc-client";
|
||||||
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
|
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
|
||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { LinearGradient } from "expo-linear-gradient";
|
import { LinearGradient } from "expo-linear-gradient";
|
||||||
import { useRouter } from "expo-router";
|
import { useRouter } from "expo-router";
|
||||||
|
|
||||||
import VendorSnippetForm from "../../../components/VendorSnippetForm";
|
import VendorSnippetForm from "../../../../components/VendorSnippetForm";
|
||||||
import SnippetOrdersView from "../../../components/SnippetOrdersView";
|
import SnippetOrdersView from "../../../../components/SnippetOrdersView";
|
||||||
import { SnippetMenu } from "../../../components/SnippetMenu";
|
import { SnippetMenu } from "../../../../components/SnippetMenu";
|
||||||
import { ProductListDialog } from "../../../components/ProductListDialog";
|
import { ProductListDialog } from "../../../../components/ProductListDialog";
|
||||||
import {
|
import {
|
||||||
VendorSnippet,
|
VendorSnippet,
|
||||||
VendorSnippetProduct,
|
VendorSnippetProduct,
|
||||||
VendorSnippetForm as VendorSnippetFormType,
|
VendorSnippetForm as VendorSnippetFormType,
|
||||||
} from "../../../types/vendor-snippets";
|
} from "../../../../types/vendor-snippets";
|
||||||
|
|
||||||
const SnippetItem = ({
|
const SnippetItem = ({
|
||||||
snippet,
|
snippet,
|
||||||
|
|
@ -212,7 +212,7 @@ const SnippetItem = ({
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<MyTouchableOpacity
|
<MyTouchableOpacity
|
||||||
// onPress={() => router.push(`/(drawer)/slots/slot-details?slotId=${snippet.slotId}`)}
|
// onPress={() => router.push(`/(drawer)/dashboard/slots/slot-details?slotId=${snippet.slotId}`)}
|
||||||
onPress={() => {}}
|
onPress={() => {}}
|
||||||
activeOpacity={0.7}
|
activeOpacity={0.7}
|
||||||
style={tw`flex-row items-center`}
|
style={tw`flex-row items-center`}
|
||||||
|
|
@ -1,93 +0,0 @@
|
||||||
import React, { useState } from 'react';
|
|
||||||
import { View, TouchableOpacity } from 'react-native';
|
|
||||||
import { Image } from 'expo-image';
|
|
||||||
import { useRouter } from 'expo-router';
|
|
||||||
import { AppContainer, MyText, tw, MyFlatList, MyTextInput } from 'common-ui';
|
|
||||||
import { trpc } from '@/src/trpc-client';
|
|
||||||
import Ionicons from '@expo/vector-icons/Ionicons';
|
|
||||||
|
|
||||||
interface UserItemProps {
|
|
||||||
item: any;
|
|
||||||
onPress: (userId: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const UserItem: React.FC<UserItemProps> = ({ item, onPress }) => (
|
|
||||||
<TouchableOpacity
|
|
||||||
style={tw`flex-row items-center p-4 bg-white border-b border-gray-100`}
|
|
||||||
onPress={() => onPress(item.id)}
|
|
||||||
>
|
|
||||||
{item.image ? (
|
|
||||||
<Image
|
|
||||||
source={{ uri: item.image }}
|
|
||||||
style={tw`w-12 h-12 rounded-full mr-4`}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<View style={tw`w-12 h-12 rounded-full bg-gray-200 items-center justify-center mr-4`}>
|
|
||||||
<Ionicons name="person" size={24} color="#6b7280" />
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
<View style={tw`flex-1`}>
|
|
||||||
<MyText style={tw`font-bold text-gray-800 text-lg`}>{item.name}</MyText>
|
|
||||||
<MyText style={tw`text-gray-600`}>{item.email}</MyText>
|
|
||||||
<MyText style={tw`text-gray-500 text-sm`}>{item.mobile}</MyText>
|
|
||||||
</View>
|
|
||||||
</TouchableOpacity>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default function Users() {
|
|
||||||
const router = useRouter();
|
|
||||||
const [search, setSearch] = useState('');
|
|
||||||
|
|
||||||
const {
|
|
||||||
data,
|
|
||||||
isLoading,
|
|
||||||
fetchNextPage,
|
|
||||||
hasNextPage,
|
|
||||||
refetch,
|
|
||||||
} = trpc.admin.staffUser.getUsers.useInfiniteQuery(
|
|
||||||
{ search },
|
|
||||||
{
|
|
||||||
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const users = data?.pages.flatMap(page => page.users) || [];
|
|
||||||
|
|
||||||
const handleUserPress = (userId: string) => {
|
|
||||||
router.push(`/(drawer)/user-management/${userId}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AppContainer>
|
|
||||||
<View style={tw`flex-1`}>
|
|
||||||
<View style={tw`bg-white px-4 py-2 border-b border-gray-100`}>
|
|
||||||
<MyTextInput
|
|
||||||
topLabel="Search Users"
|
|
||||||
placeholder="Search by name, email, or mobile"
|
|
||||||
value={search}
|
|
||||||
onChangeText={setSearch}
|
|
||||||
style={{ marginBottom: 0 }}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<MyFlatList
|
|
||||||
data={users}
|
|
||||||
renderItem={({ item }) => <UserItem item={item} onPress={handleUserPress} />}
|
|
||||||
keyExtractor={(item) => item.id.toString()}
|
|
||||||
onRefresh={refetch}
|
|
||||||
onEndReached={() => {
|
|
||||||
if (hasNextPage) {
|
|
||||||
fetchNextPage();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onEndReachedThreshold={0.5}
|
|
||||||
ListEmptyComponent={
|
|
||||||
<View style={tw`flex-1 justify-center items-center py-8`}>
|
|
||||||
<MyText style={tw`text-gray-500`}>No users found</MyText>
|
|
||||||
</View>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</AppContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -83,9 +83,9 @@ export const NotificationProvider: React.FC<NotificationProviderProps> = ({
|
||||||
Notifications.addNotificationResponseReceivedListener((response) => {
|
Notifications.addNotificationResponseReceivedListener((response) => {
|
||||||
const data = response.notification.request.content.data;
|
const data = response.notification.request.content.data;
|
||||||
if (data && data.doctorId) {
|
if (data && data.doctorId) {
|
||||||
router.push(`/(drawer)/dashboard`);
|
router.navigate(`/(drawer)/dashboard`);
|
||||||
} else if (data && data.tokenId) {
|
} else if (data && data.tokenId) {
|
||||||
router.push(`/(drawer)/dashboard`);
|
router.navigate(`/(drawer)/dashboard`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,16 @@ import { CreateCouponPayload } from 'common-ui/shared-types';
|
||||||
|
|
||||||
const USERS_PAGE_SIZE = 10;
|
const USERS_PAGE_SIZE = 10;
|
||||||
|
|
||||||
|
interface CouponFormValues extends CreateCouponPayload {
|
||||||
|
isReservedCoupon?: boolean;
|
||||||
|
couponCodes?: string[];
|
||||||
|
skuIds?: number[];
|
||||||
|
}
|
||||||
|
|
||||||
interface CouponFormProps {
|
interface CouponFormProps {
|
||||||
onSubmit: (values: CreateCouponPayload & { isReservedCoupon?: boolean }) => void;
|
onSubmit: (values: CouponFormValues) => void;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
initialValues?: Partial<CreateCouponPayload & { isReservedCoupon?: boolean }>;
|
initialValues?: Partial<CouponFormValues>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const couponValidationSchema = Yup.object().shape({
|
const couponValidationSchema = Yup.object().shape({
|
||||||
|
|
@ -22,6 +28,13 @@ const couponValidationSchema = Yup.object().shape({
|
||||||
.min(3, 'Coupon code must be at least 3 characters')
|
.min(3, 'Coupon code must be at least 3 characters')
|
||||||
.max(50, 'Coupon code cannot exceed 50 characters')
|
.max(50, 'Coupon code cannot exceed 50 characters')
|
||||||
.matches(/^[A-Z0-9_-]+$/, 'Coupon code can only contain uppercase letters, numbers, underscores, and hyphens'),
|
.matches(/^[A-Z0-9_-]+$/, 'Coupon code can only contain uppercase letters, numbers, underscores, and hyphens'),
|
||||||
|
couponCodes: Yup.array().of(
|
||||||
|
Yup.string()
|
||||||
|
.required('Code is required')
|
||||||
|
.min(3, 'Code must be at least 3 characters')
|
||||||
|
.max(50, 'Code cannot exceed 50 characters')
|
||||||
|
.matches(/^[A-Z0-9_-]+$/, 'Code can only contain uppercase letters, numbers, underscores, and hyphens')
|
||||||
|
),
|
||||||
discountPercent: Yup.number()
|
discountPercent: Yup.number()
|
||||||
.min(0, 'Must be positive')
|
.min(0, 'Must be positive')
|
||||||
.max(100, 'Cannot exceed 100%')
|
.max(100, 'Cannot exceed 100%')
|
||||||
|
|
@ -49,6 +62,22 @@ const couponValidationSchema = Yup.object().shape({
|
||||||
return this.createError({ message: 'Must provide either percentage or flat discount' });
|
return this.createError({ message: 'Must provide either percentage or flat discount' });
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
}).test('reserved-codes-required', 'At least one coupon code is required', function(value: any) {
|
||||||
|
if (value.isReservedCoupon) {
|
||||||
|
const codes = (value.couponCodes || []).filter((c: string) => c && c.trim());
|
||||||
|
if (codes.length === 0) {
|
||||||
|
return this.createError({ path: 'couponCodes', message: 'Add at least one coupon code' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}).test('reserved-codes-unique', 'Coupon codes must be unique', function(value: any) {
|
||||||
|
if (value.isReservedCoupon) {
|
||||||
|
const codes = (value.couponCodes || []).map((c: string) => c.trim()).filter(Boolean);
|
||||||
|
if (new Set(codes).size !== codes.length) {
|
||||||
|
return this.createError({ path: 'couponCodes', message: 'Coupon codes must be unique' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function CouponForm({ onSubmit, isLoading, initialValues }: CouponFormProps) {
|
export default function CouponForm({ onSubmit, isLoading, initialValues }: CouponFormProps) {
|
||||||
|
|
@ -94,8 +123,9 @@ export default function CouponForm({ onSubmit, isLoading, initialValues }: Coupo
|
||||||
|
|
||||||
// User search functionality will be inside Formik
|
// User search functionality will be inside Formik
|
||||||
|
|
||||||
const defaultValues: CreateCouponPayload & { isReservedCoupon?: boolean } = {
|
const defaultValues: CouponFormValues = {
|
||||||
couponCode: '',
|
couponCode: '',
|
||||||
|
couponCodes: [],
|
||||||
isUserBased: false,
|
isUserBased: false,
|
||||||
isApplyForAll: false,
|
isApplyForAll: false,
|
||||||
targetUsers: [],
|
targetUsers: [],
|
||||||
|
|
@ -113,8 +143,8 @@ export default function CouponForm({ onSubmit, isLoading, initialValues }: Coupo
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Formik
|
<Formik<CouponFormValues>
|
||||||
initialValues={(initialValues || defaultValues) as CreateCouponPayload}
|
initialValues={(initialValues || defaultValues) as CouponFormValues}
|
||||||
validationSchema={couponValidationSchema}
|
validationSchema={couponValidationSchema}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
>
|
>
|
||||||
|
|
@ -143,18 +173,70 @@ export default function CouponForm({ onSubmit, isLoading, initialValues }: Coupo
|
||||||
<MyText style={tw`ml-2 text-sm font-medium text-gray-700`}>Is Reserved Coupon</MyText>
|
<MyText style={tw`ml-2 text-sm font-medium text-gray-700`}>Is Reserved Coupon</MyText>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Coupon Code */}
|
{/* Coupon Code (single, non-reserved) OR Coupon Codes (array, reserved) */}
|
||||||
<View style={tw`mb-4`}>
|
{!isReserved ? (
|
||||||
<MyTextInput
|
<View style={tw`mb-4`}>
|
||||||
topLabel="Coupon Code"
|
<MyTextInput
|
||||||
placeholder="e.g., SAVE20"
|
topLabel="Coupon Code"
|
||||||
value={values.couponCode || ''}
|
placeholder="e.g., SAVE20"
|
||||||
onChangeText={(text: string) => setFieldValue('couponCode', text.toUpperCase())}
|
value={values.couponCode || ''}
|
||||||
keyboardType="default"
|
onChangeText={(text: string) => setFieldValue('couponCode', text.toUpperCase())}
|
||||||
autoCapitalize="characters"
|
keyboardType="default"
|
||||||
error={!!(touched.couponCode && errors.couponCode)}
|
autoCapitalize="characters"
|
||||||
/>
|
error={!!(touched.couponCode && errors.couponCode)}
|
||||||
</View>
|
/>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<View style={tw`mb-4`}>
|
||||||
|
<Text style={tw`text-base font-bold mb-1`}>Coupon Codes</Text>
|
||||||
|
<Text style={tw`text-xs text-gray-500 mb-2`}>
|
||||||
|
Each code creates a separate reserved coupon with the same settings.
|
||||||
|
</Text>
|
||||||
|
{(values.couponCodes || []).map((code: string, index: number) => {
|
||||||
|
const codeError = Array.isArray(errors.couponCodes)
|
||||||
|
? (errors.couponCodes as any)[index]
|
||||||
|
: errors.couponCodes;
|
||||||
|
return (
|
||||||
|
<View key={index} style={tw`flex-row items-center mb-2`}>
|
||||||
|
<View style={tw`flex-1`}>
|
||||||
|
<MyTextInput
|
||||||
|
topLabel={`Code ${index + 1}`}
|
||||||
|
placeholder="e.g., SECRET10"
|
||||||
|
value={code}
|
||||||
|
onChangeText={(text: string) => {
|
||||||
|
const next = [...(values.couponCodes || [])];
|
||||||
|
next[index] = text.toUpperCase();
|
||||||
|
setFieldValue('couponCodes', next);
|
||||||
|
}}
|
||||||
|
keyboardType="default"
|
||||||
|
autoCapitalize="characters"
|
||||||
|
error={!!codeError}
|
||||||
|
/>
|
||||||
|
{codeError && <Text style={tw`text-red-500 text-xs mt-1`}>{codeError}</Text>}
|
||||||
|
</View>
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => {
|
||||||
|
const next = (values.couponCodes || []).filter((_: string, i: number) => i !== index);
|
||||||
|
setFieldValue('couponCodes', next);
|
||||||
|
}}
|
||||||
|
style={tw`ml-2 p-2`}
|
||||||
|
>
|
||||||
|
<Text style={tw`text-red-500 font-bold text-lg`}>✕</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{(errors.couponCodes as any)?.message && (
|
||||||
|
<Text style={tw`text-red-500 text-xs mb-2`}>{(errors.couponCodes as any).message}</Text>
|
||||||
|
)}
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => setFieldValue('couponCodes', [...(values.couponCodes || []), ''])}
|
||||||
|
style={tw`mt-2 py-2 px-3 bg-blue-50 rounded-lg border border-blue-200 self-start`}
|
||||||
|
>
|
||||||
|
<Text style={tw`text-blue-600 font-medium`}>+ Add Code</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Discount Type Selection */}
|
{/* Discount Type Selection */}
|
||||||
<Text style={tw`text-base font-bold mb-2`}>
|
<Text style={tw`text-base font-bold mb-2`}>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue