enh
This commit is contained in:
parent
d1d7db55a0
commit
55c41fa0af
8 changed files with 93 additions and 59 deletions
|
|
@ -1,10 +1,11 @@
|
||||||
import React, { useState, useEffect, useMemo } from "react";
|
import React, { useState, useEffect, useMemo } from "react";
|
||||||
import {
|
import {
|
||||||
View,
|
View,
|
||||||
TouchableOpacity,
|
|
||||||
Alert,
|
Alert,
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
|
ScrollView,
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
|
import { TouchableOpacity } from "react-native-gesture-handler";
|
||||||
import { Image } from "expo-image";
|
import { Image } from "expo-image";
|
||||||
import DraggableFlatList, {
|
import DraggableFlatList, {
|
||||||
RenderItemParams,
|
RenderItemParams,
|
||||||
|
|
@ -16,6 +17,7 @@ import {
|
||||||
tw,
|
tw,
|
||||||
BottomDialog,
|
BottomDialog,
|
||||||
BottomDropdown,
|
BottomDropdown,
|
||||||
|
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";
|
||||||
|
|
@ -27,8 +29,8 @@ interface PopularProduct {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
shortDescription: string | null;
|
shortDescription: string | null;
|
||||||
price: string;
|
price: number;
|
||||||
marketPrice: string | null;
|
marketPrice: number | null;
|
||||||
unit: string;
|
unit: string;
|
||||||
incrementStep: number;
|
incrementStep: number;
|
||||||
productQuantity: number;
|
productQuantity: number;
|
||||||
|
|
@ -119,7 +121,7 @@ export default function CustomizePopularItems() {
|
||||||
const [popularProducts, setPopularProducts] = useState<PopularProduct[]>([]);
|
const [popularProducts, setPopularProducts] = useState<PopularProduct[]>([]);
|
||||||
const [hasChanges, setHasChanges] = useState(false);
|
const [hasChanges, setHasChanges] = useState(false);
|
||||||
const [showAddDialog, setShowAddDialog] = useState(false);
|
const [showAddDialog, setShowAddDialog] = useState(false);
|
||||||
const [selectedProductId, setSelectedProductId] = useState<number | null>(null);
|
const [selectedProductIds, setSelectedProductIds] = useState<number[]>([]);
|
||||||
|
|
||||||
// Get current popular items from constants
|
// Get current popular items from constants
|
||||||
const { data: constants, isLoading: isLoadingConstants, error: constantsError } = trpc.admin.const.getConstants.useQuery();
|
const { data: constants, isLoading: isLoadingConstants, error: constantsError } = trpc.admin.const.getConstants.useQuery();
|
||||||
|
|
@ -182,14 +184,20 @@ export default function CustomizePopularItems() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddProduct = () => {
|
const handleAddProduct = () => {
|
||||||
if (selectedProductId) {
|
if (selectedProductIds.length > 0) {
|
||||||
const product = allProducts?.products.find(p => p.id === selectedProductId);
|
const newProducts = selectedProductIds
|
||||||
if (product && !popularProducts.find(p => p.id === product.id)) {
|
.map(id => allProducts?.products.find(p => p.id === id))
|
||||||
setPopularProducts(prev => [...prev, product as PopularProduct]);
|
.filter((product): product is NonNullable<typeof product> =>
|
||||||
|
product !== undefined && !popularProducts.find(p => p.id === product.id)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (newProducts.length > 0) {
|
||||||
|
setPopularProducts(prev => [...prev, ...newProducts as PopularProduct[]]);
|
||||||
setHasChanges(true);
|
setHasChanges(true);
|
||||||
setSelectedProductId(null);
|
|
||||||
setShowAddDialog(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setSelectedProductIds([]);
|
||||||
|
setShowAddDialog(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -293,20 +301,19 @@ export default function CustomizePopularItems() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppContainer>
|
<View style={[tw`flex-1 bg-gray-50 relative`]}>
|
||||||
<View style={tw`flex-1 bg-gray-50`}>
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<View style={tw`bg-white px-4 py-4 border-b border-gray-200 flex-row items-center justify-between`}>
|
<View style={tw`bg-white px-4 py-4 border-b border-gray-200 flex-row items-center justify-between`}>
|
||||||
<TouchableOpacity
|
<MyTouchableOpacity
|
||||||
onPress={() => router.back()}
|
onPress={() => router.back()}
|
||||||
style={tw`p-2 -ml-4`}
|
style={tw`p-2 -ml-4`}
|
||||||
>
|
>
|
||||||
<MaterialIcons name="chevron-left" size={24} color="#374151" />
|
<MaterialIcons name="chevron-left" size={24} color="#374151" />
|
||||||
</TouchableOpacity>
|
</MyTouchableOpacity>
|
||||||
|
|
||||||
<MyText style={tw`text-xl font-bold text-gray-900`}>Popular Items</MyText>
|
<MyText style={tw`text-xl font-bold text-gray-900`}>Popular Items</MyText>
|
||||||
|
|
||||||
<TouchableOpacity
|
<MyTouchableOpacity
|
||||||
onPress={handleSave}
|
onPress={handleSave}
|
||||||
disabled={!hasChanges || updateConstants.isPending}
|
disabled={!hasChanges || updateConstants.isPending}
|
||||||
style={tw`px-4 py-2 rounded-lg ${
|
style={tw`px-4 py-2 rounded-lg ${
|
||||||
|
|
@ -322,7 +329,7 @@ export default function CustomizePopularItems() {
|
||||||
} font-semibold`}>
|
} font-semibold`}>
|
||||||
{updateConstants.isPending ? 'Saving...' : 'Save'}
|
{updateConstants.isPending ? 'Saving...' : 'Save'}
|
||||||
</MyText>
|
</MyText>
|
||||||
</TouchableOpacity>
|
</MyTouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
|
|
@ -356,35 +363,41 @@ export default function CustomizePopularItems() {
|
||||||
)}
|
)}
|
||||||
keyExtractor={(item) => item.id.toString()}
|
keyExtractor={(item) => item.id.toString()}
|
||||||
onDragEnd={handleDragEnd}
|
onDragEnd={handleDragEnd}
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={true}
|
||||||
contentContainerStyle={tw`pb-8`}
|
scrollEnabled={true}
|
||||||
|
contentContainerStyle={{ paddingBottom: 80 }}
|
||||||
|
containerStyle={tw`flex-1`}
|
||||||
|
keyboardShouldPersistTaps="handled"
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* FAB for Add Product */}
|
{/* FAB for Add Product - Fixed position */}
|
||||||
<View style={tw`absolute bottom-4 right-4`}>
|
<View style={tw`absolute bottom-12 right-6 z-50`}>
|
||||||
<TouchableOpacity
|
<MyTouchableOpacity
|
||||||
onPress={() => setShowAddDialog(true)}
|
onPress={() => setShowAddDialog(true)}
|
||||||
style={tw`bg-blue-600 p-4 rounded-full shadow-lg`}
|
style={tw`bg-blue-600 p-4 rounded-full shadow-lg elevation-5`}
|
||||||
>
|
>
|
||||||
<MaterialIcons name="add" size={24} color="white" />
|
<MaterialIcons name="add" size={24} color="white" />
|
||||||
</TouchableOpacity>
|
</MyTouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Add Product Dialog */}
|
{/* Add Product Dialog */}
|
||||||
<BottomDialog
|
<BottomDialog
|
||||||
open={showAddDialog}
|
open={showAddDialog}
|
||||||
onClose={() => setShowAddDialog(false)}
|
onClose={() => {
|
||||||
|
setShowAddDialog(false);
|
||||||
|
setSelectedProductIds([]);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<View style={tw`pb-8 pt-2 px-4`}>
|
<View style={tw`pb-8 pt-2 px-4`}>
|
||||||
<View style={tw`items-center mb-6`}>
|
<View style={tw`items-center mb-6`}>
|
||||||
<View style={tw`w-12 h-1.5 bg-gray-200 rounded-full mb-4`} />
|
<View style={tw`w-12 h-1.5 bg-gray-200 rounded-full mb-4`} />
|
||||||
<MyText style={tw`text-lg font-bold text-gray-900`}>
|
<MyText style={tw`text-lg font-bold text-gray-900`}>
|
||||||
Add Popular Item
|
Add Popular Items
|
||||||
</MyText>
|
</MyText>
|
||||||
<MyText style={tw`text-sm text-gray-500`}>
|
<MyText style={tw`text-sm text-gray-500`}>
|
||||||
Select a product to add to popular items
|
Select products to add to popular items
|
||||||
</MyText>
|
</MyText>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
|
@ -398,41 +411,43 @@ export default function CustomizePopularItems() {
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<ProductsSelector
|
<ProductsSelector
|
||||||
value={selectedProductId || 0}
|
value={selectedProductIds}
|
||||||
onChange={(val) => setSelectedProductId(val as number)}
|
onChange={(val) => setSelectedProductIds(val as number[])}
|
||||||
multiple={false}
|
multiple={true}
|
||||||
label="Select Product"
|
label="Select Products"
|
||||||
placeholder="Choose a product..."
|
placeholder="Choose products..."
|
||||||
labelFormat={(product) => `${product.name} - ₹${product.price}`}
|
labelFormat={(product) => `${product.name} - ₹${product.price}`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<View style={tw`flex-row gap-3 mt-6`}>
|
<View style={tw`flex-row gap-3 mt-6`}>
|
||||||
<TouchableOpacity
|
<MyTouchableOpacity
|
||||||
onPress={() => setShowAddDialog(false)}
|
onPress={() => setShowAddDialog(false)}
|
||||||
style={tw`flex-1 bg-gray-100 p-3 rounded-lg`}
|
style={tw`flex-1 bg-gray-100 p-3 rounded-lg`}
|
||||||
>
|
>
|
||||||
<MyText style={tw`text-gray-700 text-center font-semibold`}>
|
<MyText style={tw`text-gray-700 text-center font-semibold`}>
|
||||||
Cancel
|
Cancel
|
||||||
</MyText>
|
</MyText>
|
||||||
</TouchableOpacity>
|
</MyTouchableOpacity>
|
||||||
|
|
||||||
<TouchableOpacity
|
<MyTouchableOpacity
|
||||||
onPress={handleAddProduct}
|
onPress={handleAddProduct}
|
||||||
disabled={!selectedProductId}
|
disabled={selectedProductIds.length === 0}
|
||||||
style={tw`flex-1 ${
|
style={tw`flex-1 ${
|
||||||
selectedProductId ? 'bg-blue-600' : 'bg-gray-300'
|
selectedProductIds.length > 0 ? 'bg-blue-600' : 'bg-gray-300'
|
||||||
} p-3 rounded-lg`}
|
} p-3 rounded-lg`}
|
||||||
>
|
>
|
||||||
<MyText style={tw`text-white text-center font-semibold`}>
|
<MyText style={tw`text-white text-center font-semibold`}>
|
||||||
Add Product
|
{selectedProductIds.length > 0
|
||||||
|
? `Add ${selectedProductIds.length} Product${selectedProductIds.length > 1 ? 's' : ''}`
|
||||||
|
: 'Add Products'}
|
||||||
</MyText>
|
</MyText>
|
||||||
</TouchableOpacity>
|
</MyTouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</BottomDialog>
|
</BottomDialog>
|
||||||
|
|
||||||
</View>
|
</View>
|
||||||
</AppContainer>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -8,6 +8,7 @@ import MaterialIcons from "@expo/vector-icons/MaterialIcons";
|
||||||
import Ionicons from '@expo/vector-icons/Ionicons';
|
import Ionicons from '@expo/vector-icons/Ionicons';
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import ComplaintForm from "@/components/ComplaintForm";
|
import ComplaintForm from "@/components/ComplaintForm";
|
||||||
|
import { orderStatusManipulator } from "@/src/lib/string-manipulators";
|
||||||
|
|
||||||
export default function OrderDetails() {
|
export default function OrderDetails() {
|
||||||
const { id } = useLocalSearchParams<{ id: string }>();
|
const { id } = useLocalSearchParams<{ id: string }>();
|
||||||
|
|
@ -145,7 +146,7 @@ export default function OrderDetails() {
|
||||||
<View style={tw`flex-row items-center gap-2`}>
|
<View style={tw`flex-row items-center gap-2`}>
|
||||||
<View style={[tw`px-3 py-1 rounded-full`, { backgroundColor: statusConfig.color + '10' }]}>
|
<View style={[tw`px-3 py-1 rounded-full`, { backgroundColor: statusConfig.color + '10' }]}>
|
||||||
<MyText style={[tw`text-[10px] font-bold uppercase`, { color: statusConfig.color }]}>
|
<MyText style={[tw`text-[10px] font-bold uppercase`, { color: statusConfig.color }]}>
|
||||||
{statusConfig.label}
|
{orderStatusManipulator(statusConfig.label)}
|
||||||
</MyText>
|
</MyText>
|
||||||
</View>
|
</View>
|
||||||
{order.isFlashDelivery && (
|
{order.isFlashDelivery && (
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import { trpc } from '@/src/trpc-client';
|
||||||
// import RazorpayCheckout from 'react-native-razorpay';
|
// import RazorpayCheckout from 'react-native-razorpay';
|
||||||
import OrderMenu from '@/components/OrderMenu';
|
import OrderMenu from '@/components/OrderMenu';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import { orderStatusManipulator } from '@/src/lib/string-manipulators';
|
||||||
|
|
||||||
// Type definitions
|
// Type definitions
|
||||||
interface OrderItem {
|
interface OrderItem {
|
||||||
|
|
@ -136,9 +137,9 @@ const OrderItem: React.FC<OrderItemProps> = ({
|
||||||
{item.orderStatus.toLowerCase() === 'cancelled' && (
|
{item.orderStatus.toLowerCase() === 'cancelled' && (
|
||||||
<View style={[tw`flex-row items-center px-3 py-1.5 rounded-full border ${mainStatus.bg} ${mainStatus.border}`]}>
|
<View style={[tw`flex-row items-center px-3 py-1.5 rounded-full border ${mainStatus.bg} ${mainStatus.border}`]}>
|
||||||
<MaterialIcons name={mainStatus.icon as any} size={14} color={mainStatus.color} />
|
<MaterialIcons name={mainStatus.icon as any} size={14} color={mainStatus.color} />
|
||||||
<MyText style={[tw`text-[11px] font-bold ml-1.5 uppercase tracking-wide`, { color: mainStatus.color }]}>
|
<MyText style={[tw`text-[11px] font-bold ml-1.5 uppercase tracking-wide`, { color: mainStatus.color }]}>
|
||||||
{mainStatus.label}
|
{orderStatusManipulator(mainStatus.label)}
|
||||||
</MyText>
|
</MyText>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
{item.isFlashDelivery && (
|
{item.isFlashDelivery && (
|
||||||
|
|
@ -265,7 +266,7 @@ const OrderItem: React.FC<OrderItemProps> = ({
|
||||||
{item.isFlashDelivery ? "1 Hr Delivery:" : "Shipping Status:"}
|
{item.isFlashDelivery ? "1 Hr Delivery:" : "Shipping Status:"}
|
||||||
</MyText>
|
</MyText>
|
||||||
<MyText style={[tw`text-xs font-bold`, item.isFlashDelivery ? tw`text-amber-700` : { color: deliveryStatus.color }]} numberOfLines={1}>
|
<MyText style={[tw`text-xs font-bold`, item.isFlashDelivery ? tw`text-amber-700` : { color: deliveryStatus.color }]} numberOfLines={1}>
|
||||||
{item.deliveryStatus}
|
{orderStatusManipulator(item.deliveryStatus)}
|
||||||
</MyText>
|
</MyText>
|
||||||
</View>
|
</View>
|
||||||
{item.isFlashDelivery && (
|
{item.isFlashDelivery && (
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import { MaterialIcons, Ionicons } from '@expo/vector-icons';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { trpc } from '@/src/trpc-client';
|
import { trpc } from '@/src/trpc-client';
|
||||||
import { Image } from 'expo-image';
|
import { Image } from 'expo-image';
|
||||||
|
import { orderStatusManipulator } from '@/src/lib/string-manipulators';
|
||||||
|
|
||||||
interface OrderItem {
|
interface OrderItem {
|
||||||
productName: string;
|
productName: string;
|
||||||
|
|
@ -126,7 +127,7 @@ export default function NextOrderGlimpse() {
|
||||||
</MyText>
|
</MyText>
|
||||||
</View>
|
</View>
|
||||||
<View style={tw`bg-white px-2 py-1 rounded-lg border border-amber-200`}>
|
<View style={tw`bg-white px-2 py-1 rounded-lg border border-amber-200`}>
|
||||||
<MyText style={tw`text-[10px] font-bold text-amber-700 uppercase`}>{nextOrder.deliveryStatus}</MyText>
|
<MyText style={tw`text-[10px] font-bold text-amber-700 uppercase`}>{orderStatusManipulator(nextOrder.deliveryStatus)}</MyText>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,9 @@
|
||||||
import { trpc } from '@/src/trpc-client';
|
import { trpc } from '@/src/trpc-client';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
let isPrinted = false;
|
|
||||||
export function useProductSlotIdentifier() {
|
export function useProductSlotIdentifier() {
|
||||||
// Fetch all slots with products
|
// Fetch all slots with products
|
||||||
const { data: slotsData, isLoading: isProductsLoading } = trpc.user.slots.getSlotsWithProducts.useQuery();
|
const { data: slotsData, isLoading: isProductsLoading } = trpc.user.slots.getSlotsWithProducts.useQuery();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if(!isProductsLoading && !isPrinted) {
|
|
||||||
isPrinted = true;
|
|
||||||
const slotInfo = slotsData?.slots.map(slot => ({...slot, products: slot.products.map(item => ({id: item.id, name: item.name}))}))
|
|
||||||
console.log(JSON.stringify(slotInfo))
|
|
||||||
}
|
|
||||||
|
|
||||||
},[slotsData])
|
|
||||||
|
|
||||||
|
|
||||||
const productSlotsMap = new Map<number, number[]>();
|
const productSlotsMap = new Map<number, number[]>();
|
||||||
|
|
|
||||||
27
apps/user-ui/src/lib/string-manipulators.ts
Normal file
27
apps/user-ui/src/lib/string-manipulators.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
/**
|
||||||
|
* String manipulation utilities for the user UI
|
||||||
|
*
|
||||||
|
* This file contains helper functions for transforming and formatting
|
||||||
|
* strings throughout the application. These utilities ensure consistent
|
||||||
|
* display formatting across all components.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms delivery status for display.
|
||||||
|
*
|
||||||
|
* Why: The term "Pending" can be confusing for users as it implies
|
||||||
|
* uncertainty. "Confirmed" provides clearer communication that the
|
||||||
|
* order has been received and acknowledged by the system.
|
||||||
|
*
|
||||||
|
* @param status - The raw delivery status from API
|
||||||
|
* @returns User-friendly status label
|
||||||
|
* @example
|
||||||
|
* orderStatusManipulator('pending') // returns 'Confirmed'
|
||||||
|
* orderStatusManipulator('packaged') // returns 'packaged' (unchanged)
|
||||||
|
*/
|
||||||
|
export const orderStatusManipulator = (status: string): string => {
|
||||||
|
if (status.toLowerCase() === 'pending') {
|
||||||
|
return 'Confirmed';
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
};
|
||||||
|
|
@ -63,8 +63,8 @@ const isDevMode = Constants.executionEnvironment !== "standalone";
|
||||||
// const BASE_API_URL = API_URL;
|
// const BASE_API_URL = API_URL;
|
||||||
// const BASE_API_URL = 'http://10.0.2.2:4000';
|
// const BASE_API_URL = 'http://10.0.2.2:4000';
|
||||||
// const BASE_API_URL = 'http://192.168.100.101:4000';
|
// const BASE_API_URL = 'http://192.168.100.101:4000';
|
||||||
// const BASE_API_URL = 'http://192.168.1.3:4000';
|
const BASE_API_URL = 'http://192.168.1.3:4000';
|
||||||
let BASE_API_URL = "https://mf.freshyo.in";
|
// let BASE_API_URL = "https://mf.freshyo.in";
|
||||||
// let BASE_API_URL = 'http://192.168.100.104:4000';
|
// let BASE_API_URL = 'http://192.168.100.104:4000';
|
||||||
// let BASE_API_URL = 'http://192.168.29.176:4000';
|
// let BASE_API_URL = 'http://192.168.29.176:4000';
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue