mostly functional apps
This commit is contained in:
parent
ad714493fc
commit
5c2b3aaa67
25 changed files with 395 additions and 426 deletions
|
|
@ -48,7 +48,9 @@ const formatOrderMessageWithFullData = (ordersData: any[]): string => {
|
||||||
|
|
||||||
message += '📦 <b>Items:</b>\n';
|
message += '📦 <b>Items:</b>\n';
|
||||||
order.orderItems?.forEach((item: any) => {
|
order.orderItems?.forEach((item: any) => {
|
||||||
message += ` • ${item.product?.name || 'Unknown'} • ${item.product.productQuantity}${item.product.unit?.shortNotation}x${item.quantity}\n`;
|
const sku = item.sku
|
||||||
|
const features = (sku?.features || []).map((f: any) => f.featureValue).join(' ')
|
||||||
|
message += ` • ${sku?.product?.name || 'Unknown'} ${features} x${item.quantity}\n`;
|
||||||
});
|
});
|
||||||
|
|
||||||
message += `\n💰 <b>Total:</b> ₹${order.totalAmount}\n`;
|
message += `\n💰 <b>Total:</b> ₹${order.totalAmount}\n`;
|
||||||
|
|
@ -87,7 +89,7 @@ const formatCancellationMessage = (orderData: any, cancellationData: Cancellatio
|
||||||
📞 <b>Phone:</b> ${orderData.address?.phone || 'N/A'}
|
📞 <b>Phone:</b> ${orderData.address?.phone || 'N/A'}
|
||||||
|
|
||||||
📦 <b>Items:</b>
|
📦 <b>Items:</b>
|
||||||
${orderData.orderItems?.map((item: any) => ` • ${item.product?.name || 'Unknown'} x${item.quantity}`).join('\n') || ' N/A'}
|
${orderData.orderItems?.map((item: any) => ` • ${item.sku?.product?.name || 'Unknown'} x${item.quantity}`).join('\n') || ' N/A'}
|
||||||
|
|
||||||
💰 <b>Total:</b> ₹${orderData.totalAmount}
|
💰 <b>Total:</b> ₹${orderData.totalAmount}
|
||||||
💳 <b>Refund:</b> ${orderData.refundStatus === 'na' ? 'N/A (COD)' : orderData.refundStatus || 'Pending'}
|
💳 <b>Refund:</b> ${orderData.refundStatus === 'na' ? 'N/A (COD)' : orderData.refundStatus || 'Pending'}
|
||||||
|
|
|
||||||
|
|
@ -282,7 +282,7 @@ export {
|
||||||
type OrderWithFullData,
|
type OrderWithFullData,
|
||||||
type OrderWithCancellationData,
|
type OrderWithCancellationData,
|
||||||
// Common API helpers
|
// Common API helpers
|
||||||
getSuspendedProductIds,
|
getSuspendedSkuIds,
|
||||||
getNextDeliveryDateWithCapacity,
|
getNextDeliveryDateWithCapacity,
|
||||||
getStoresSummary,
|
getStoresSummary,
|
||||||
healthCheck,
|
healthCheck,
|
||||||
|
|
|
||||||
|
|
@ -76,18 +76,18 @@ export async function initializeProducts(): Promise<void> {
|
||||||
const allDeliverySlots = await getAllDeliverySlotsForCache()
|
const allDeliverySlots = await getAllDeliverySlotsForCache()
|
||||||
const deliverySlotsMap = new Map<number, DeliverySlotData[]>()
|
const deliverySlotsMap = new Map<number, DeliverySlotData[]>()
|
||||||
for (const slot of allDeliverySlots) {
|
for (const slot of allDeliverySlots) {
|
||||||
if (!deliverySlotsMap.has(slot.productId))
|
if (!deliverySlotsMap.has(slot.skuId))
|
||||||
deliverySlotsMap.set(slot.productId, [])
|
deliverySlotsMap.set(slot.skuId, [])
|
||||||
deliverySlotsMap.get(slot.productId)!.push(slot)
|
deliverySlotsMap.get(slot.skuId)!.push(slot)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch all special deals
|
// Fetch all special deals
|
||||||
const allSpecialDeals = await getAllSpecialDealsForCache()
|
const allSpecialDeals = await getAllSpecialDealsForCache()
|
||||||
const specialDealsMap = new Map<number, SpecialDealData[]>()
|
const specialDealsMap = new Map<number, SpecialDealData[]>()
|
||||||
for (const deal of allSpecialDeals) {
|
for (const deal of allSpecialDeals) {
|
||||||
if (!specialDealsMap.has(deal.productId))
|
if (!specialDealsMap.has(deal.skuId))
|
||||||
specialDealsMap.set(deal.productId, [])
|
specialDealsMap.set(deal.skuId, [])
|
||||||
specialDealsMap.get(deal.productId)!.push(deal)
|
specialDealsMap.get(deal.skuId)!.push(deal)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch all product tags
|
// Fetch all product tags
|
||||||
|
|
@ -153,70 +153,11 @@ export async function initializeProducts(): Promise<void> {
|
||||||
|
|
||||||
export async function getProductById(id: number): Promise<Product | null> {
|
export async function getProductById(id: number): Promise<Product | null> {
|
||||||
try {
|
try {
|
||||||
// const key = `product:${id}`
|
const allProducts = await getAllProducts()
|
||||||
// const data = await redisClient.get(key)
|
const product = allProducts.find(p => p.id === id)
|
||||||
// if (!data) return null
|
return product || null
|
||||||
// return JSON.parse(data) as Product
|
|
||||||
|
|
||||||
const product = await getProductByIdFromDb(id)
|
|
||||||
if (!product) return null
|
|
||||||
|
|
||||||
const signedImages = scaffoldAssetUrl(
|
|
||||||
(product.images as string[]) || []
|
|
||||||
)
|
|
||||||
|
|
||||||
// Fetch store info
|
|
||||||
const allStores = await getAllStoresForCache()
|
|
||||||
const store = product.storeId
|
|
||||||
? allStores.find(s => s.id === product.storeId) || null
|
|
||||||
: null
|
|
||||||
|
|
||||||
// Fetch delivery slots for this product
|
|
||||||
const allDeliverySlots = await getAllDeliverySlotsForCache()
|
|
||||||
const productSlots = allDeliverySlots.filter(s => s.productId === id)
|
|
||||||
|
|
||||||
// Fetch special deals for this product
|
|
||||||
const allSpecialDeals = await getAllSpecialDealsForCache()
|
|
||||||
const productDeals = allSpecialDeals.filter(d => d.productId === id)
|
|
||||||
|
|
||||||
// Fetch product tags for this product
|
|
||||||
const allProductTags = await getAllProductTagsForCache()
|
|
||||||
const productTagNames = allProductTags
|
|
||||||
.filter(t => t.productId === id)
|
|
||||||
.map(t => t.tagName)
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: product.id,
|
|
||||||
name: product.name,
|
|
||||||
shortDescription: product.shortDescription,
|
|
||||||
longDescription: product.longDescription,
|
|
||||||
price: product.price.toString(),
|
|
||||||
marketPrice: product.marketPrice?.toString() || null,
|
|
||||||
unitNotation: product.unit.shortNotation,
|
|
||||||
images: signedImages,
|
|
||||||
isOutOfStock: product.isOutOfStock,
|
|
||||||
store: store
|
|
||||||
? { id: store.id, name: store.name, description: store.description }
|
|
||||||
: null,
|
|
||||||
incrementStep: product.incrementStep,
|
|
||||||
productQuantity: product.productQuantity,
|
|
||||||
isFlashAvailable: product.isFlashAvailable,
|
|
||||||
flashPrice: product.flashPrice?.toString() || null,
|
|
||||||
deliverySlots: productSlots.map((s) => ({
|
|
||||||
id: s.id,
|
|
||||||
deliveryTime: s.deliveryTime,
|
|
||||||
freezeTime: s.freezeTime,
|
|
||||||
isCapacityFull: s.isCapacityFull,
|
|
||||||
})),
|
|
||||||
specialDeals: productDeals.map((d) => ({
|
|
||||||
quantity: d.quantity.toString(),
|
|
||||||
price: d.price.toString(),
|
|
||||||
validTill: d.validTill,
|
|
||||||
})),
|
|
||||||
productTags: productTagNames,
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error getting product ${id}:`, error)
|
console.error('Error getting product by ID:', error)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -250,17 +191,17 @@ export async function getAllProducts(): Promise<Product[]> {
|
||||||
const allDeliverySlots = await getAllDeliverySlotsForCache()
|
const allDeliverySlots = await getAllDeliverySlotsForCache()
|
||||||
const deliverySlotsMap = new Map<number, DeliverySlotData[]>()
|
const deliverySlotsMap = new Map<number, DeliverySlotData[]>()
|
||||||
for (const slot of allDeliverySlots) {
|
for (const slot of allDeliverySlots) {
|
||||||
if (!deliverySlotsMap.has(slot.productId))
|
if (!deliverySlotsMap.has(slot.skuId))
|
||||||
deliverySlotsMap.set(slot.productId, [])
|
deliverySlotsMap.set(slot.skuId, [])
|
||||||
deliverySlotsMap.get(slot.productId)!.push(slot)
|
deliverySlotsMap.get(slot.skuId)!.push(slot)
|
||||||
}
|
}
|
||||||
|
|
||||||
const allSpecialDeals = await getAllSpecialDealsForCache()
|
const allSpecialDeals = await getAllSpecialDealsForCache()
|
||||||
const specialDealsMap = new Map<number, SpecialDealData[]>()
|
const specialDealsMap = new Map<number, SpecialDealData[]>()
|
||||||
for (const deal of allSpecialDeals) {
|
for (const deal of allSpecialDeals) {
|
||||||
if (!specialDealsMap.has(deal.productId))
|
if (!specialDealsMap.has(deal.skuId))
|
||||||
specialDealsMap.set(deal.productId, [])
|
specialDealsMap.set(deal.skuId, [])
|
||||||
specialDealsMap.get(deal.productId)!.push(deal)
|
specialDealsMap.get(deal.skuId)!.push(deal)
|
||||||
}
|
}
|
||||||
|
|
||||||
const allProductTags = await getAllProductTagsForCache()
|
const allProductTags = await getAllProductTagsForCache()
|
||||||
|
|
@ -281,7 +222,7 @@ export async function getAllProducts(): Promise<Product[]> {
|
||||||
: null
|
: null
|
||||||
const deliverySlots = deliverySlotsMap.get(product.id) || []
|
const deliverySlots = deliverySlotsMap.get(product.id) || []
|
||||||
const specialDeals = specialDealsMap.get(product.id) || []
|
const specialDeals = specialDealsMap.get(product.id) || []
|
||||||
const productTags = productTagsMap.get(product.id) || []
|
const productTags = productTagsMap.get(product.productId) || []
|
||||||
|
|
||||||
products.push({
|
products.push({
|
||||||
id: product.id,
|
id: product.id,
|
||||||
|
|
@ -290,7 +231,7 @@ export async function getAllProducts(): Promise<Product[]> {
|
||||||
longDescription: product.longDescription,
|
longDescription: product.longDescription,
|
||||||
price: product.price.toString(),
|
price: product.price.toString(),
|
||||||
marketPrice: product.marketPrice?.toString() || null,
|
marketPrice: product.marketPrice?.toString() || null,
|
||||||
unitNotation: product.unitShortNotation,
|
unitNotation: product.unitNotation,
|
||||||
images: signedImages,
|
images: signedImages,
|
||||||
isOutOfStock: product.isOutOfStock,
|
isOutOfStock: product.isOutOfStock,
|
||||||
store: store
|
store: store
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { router, publicProcedure } from '@/src/trpc/trpc-index'
|
import { router, publicProcedure } from '@/src/trpc/trpc-index'
|
||||||
import {
|
import {
|
||||||
getSuspendedProductIds,
|
getSuspendedSkuIds,
|
||||||
getNextDeliveryDateWithCapacity,
|
getNextDeliveryDateWithCapacity,
|
||||||
getStoresSummary,
|
getStoresSummary,
|
||||||
getAllSkusSummary as getAllSkusSummaryInDb,
|
getAllSkusSummary as getAllSkusSummaryInDb,
|
||||||
|
|
@ -30,10 +30,10 @@ export async function scaffoldProducts() {
|
||||||
.where(eq(productInfo.isSuspended, true));
|
.where(eq(productInfo.isSuspended, true));
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const suspendedProductIds = new Set(await getSuspendedProductIds());
|
const suspendedSkuIds = new Set(await getSuspendedSkuIds());
|
||||||
|
|
||||||
// Filter out suspended products
|
// Filter out suspended products
|
||||||
products = products.filter(product => !suspendedProductIds.has(product.id));
|
products = products.filter(product => !suspendedSkuIds.has(product.id));
|
||||||
|
|
||||||
// Format products to match the expected response structure
|
// Format products to match the expected response structure
|
||||||
const formattedProducts = await Promise.all(
|
const formattedProducts = await Promise.all(
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ import type {
|
||||||
const placeOrderUtil = async (params: {
|
const placeOrderUtil = async (params: {
|
||||||
userId: number;
|
userId: number;
|
||||||
selectedItems: Array<{
|
selectedItems: Array<{
|
||||||
productId: number;
|
skuId: number;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
slotId: number | null;
|
slotId: number | null;
|
||||||
}>;
|
}>;
|
||||||
|
|
@ -86,7 +86,7 @@ const placeOrderUtil = async (params: {
|
||||||
const ordersBySlot = new Map<
|
const ordersBySlot = new Map<
|
||||||
number | null,
|
number | null,
|
||||||
Array<{
|
Array<{
|
||||||
productId: number;
|
skuId: number;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
slotId: number | null;
|
slotId: number | null;
|
||||||
product: Awaited<ReturnType<typeof getOrderProductById>>;
|
product: Awaited<ReturnType<typeof getOrderProductById>>;
|
||||||
|
|
@ -94,9 +94,9 @@ const placeOrderUtil = async (params: {
|
||||||
>();
|
>();
|
||||||
|
|
||||||
for (const item of selectedItems) {
|
for (const item of selectedItems) {
|
||||||
const product = await getOrderProductById(item.productId);
|
const product = await getOrderProductById(item.skuId);
|
||||||
if (!product) {
|
if (!product) {
|
||||||
throw new ApiError(`Product ${item.productId} not found`, 400);
|
throw new ApiError(`Product ${item.skuId} not found`, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ordersBySlot.has(item.slotId)) {
|
if (!ordersBySlot.has(item.slotId)) {
|
||||||
|
|
@ -107,9 +107,9 @@ const placeOrderUtil = async (params: {
|
||||||
|
|
||||||
if (params.isFlash) {
|
if (params.isFlash) {
|
||||||
for (const item of selectedItems) {
|
for (const item of selectedItems) {
|
||||||
const product = await getOrderProductById(item.productId);
|
const product = await getOrderProductById(item.skuId);
|
||||||
if (!product?.isFlashAvailable) {
|
if (!product?.isFlashAvailable) {
|
||||||
throw new ApiError(`Product ${item.productId} is not available for flash delivery`, 400);
|
throw new ApiError(`Product ${item.skuId} is not available for flash delivery`, 400);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -198,7 +198,7 @@ const placeOrderUtil = async (params: {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
orderId: 0,
|
orderId: 0,
|
||||||
productId: item.productId,
|
skuId: item.skuId,
|
||||||
quantity: item.quantity.toString(),
|
quantity: item.quantity.toString(),
|
||||||
price: priceString,
|
price: priceString,
|
||||||
discountedPrice: priceString,
|
discountedPrice: priceString,
|
||||||
|
|
@ -225,7 +225,7 @@ const placeOrderUtil = async (params: {
|
||||||
|
|
||||||
await deleteUserCartItemsForOrder(
|
await deleteUserCartItemsForOrder(
|
||||||
userId,
|
userId,
|
||||||
selectedItems.map((item) => item.productId)
|
selectedItems.map((item) => item.skuId)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (appliedCoupon && createdOrders.length > 0) {
|
if (appliedCoupon && createdOrders.length > 0) {
|
||||||
|
|
@ -252,7 +252,7 @@ export const orderRouter = router({
|
||||||
z.object({
|
z.object({
|
||||||
selectedItems: z.array(
|
selectedItems: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
productId: z.number().int().positive(),
|
skuId: z.number().int().positive(),
|
||||||
quantity: z.number().int().positive(),
|
quantity: z.number().int().positive(),
|
||||||
slotId: z.union([z.number().int(), z.null()]),
|
slotId: z.union([z.number().int(), z.null()]),
|
||||||
})
|
})
|
||||||
|
|
@ -373,13 +373,14 @@ export const orderRouter = router({
|
||||||
|
|
||||||
const items = await Promise.all(
|
const items = await Promise.all(
|
||||||
order.orderItems.map(async (item) => {
|
order.orderItems.map(async (item) => {
|
||||||
const signedImages = item.product.images
|
const signedImages = item.sku?.images
|
||||||
? scaffoldAssetUrl(
|
? scaffoldAssetUrl(
|
||||||
item.product.images as string[]
|
item.sku.images as string[]
|
||||||
)
|
)
|
||||||
: [];
|
: [];
|
||||||
return {
|
return {
|
||||||
productName: item.product.name,
|
productName: item.sku?.product?.name || 'Unknown',
|
||||||
|
skuName: item.sku?.name || null,
|
||||||
quantity: parseFloat(item.quantity),
|
quantity: parseFloat(item.quantity),
|
||||||
price: parseFloat(item.price.toString()),
|
price: parseFloat(item.price.toString()),
|
||||||
discountedPrice: parseFloat(
|
discountedPrice: parseFloat(
|
||||||
|
|
@ -512,13 +513,14 @@ export const orderRouter = router({
|
||||||
|
|
||||||
const items = await Promise.all(
|
const items = await Promise.all(
|
||||||
order.orderItems.map(async (item) => {
|
order.orderItems.map(async (item) => {
|
||||||
const signedImages = item.product.images
|
const signedImages = item.sku?.images
|
||||||
? scaffoldAssetUrl(
|
? scaffoldAssetUrl(
|
||||||
item.product.images as string[]
|
item.sku.images as string[]
|
||||||
)
|
)
|
||||||
: [];
|
: [];
|
||||||
return {
|
return {
|
||||||
productName: item.product.name,
|
productName: item.sku?.product?.name || 'Unknown',
|
||||||
|
skuName: item.sku?.name || null,
|
||||||
quantity: parseFloat(item.quantity),
|
quantity: parseFloat(item.quantity),
|
||||||
price: parseFloat(item.price.toString()),
|
price: parseFloat(item.price.toString()),
|
||||||
discountedPrice: parseFloat(
|
discountedPrice: parseFloat(
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ const FlashAddToCartDialog = () => {
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (isOpen && product) {
|
if (isOpen && product) {
|
||||||
addToCart.mutate(
|
addToCart.mutate(
|
||||||
{ productId: product.id, quantity: 1, slotId: 0 },
|
{ skuId: product.id, quantity: 1, slotId: 0 },
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
clearAddedFlashProduct();
|
clearAddedFlashProduct();
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ interface Banner {
|
||||||
name: string;
|
name: string;
|
||||||
imageUrl: string;
|
imageUrl: string;
|
||||||
description?: string | null;
|
description?: string | null;
|
||||||
productIds?: number[] | null;
|
skuIds?: number[] | null;
|
||||||
redirectUrl?: string | null;
|
redirectUrl?: string | null;
|
||||||
serialNum?: number | null;
|
serialNum?: number | null;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
|
@ -61,13 +61,13 @@ export default function BannerCarousel() {
|
||||||
if (error || !banners || banners.length === 0) return null;
|
if (error || !banners || banners.length === 0) return null;
|
||||||
|
|
||||||
const handleBannerPress = (banner: Banner) => {
|
const handleBannerPress = (banner: Banner) => {
|
||||||
if (banner.productIds && banner.productIds.length > 0) {
|
if (banner.skuIds && banner.skuIds.length > 0) {
|
||||||
// Navigate to the first product's detail page
|
// Navigate to the first product's detail page
|
||||||
router.push(`/(drawer)/(tabs)/home/product-detail/${banner.productIds[0]}`);
|
router.push(`/(drawer)/(tabs)/home/product-detail/${banner.skuIds[0]}`);
|
||||||
} else if (banner.redirectUrl) {
|
} else if (banner.redirectUrl) {
|
||||||
// Handle external URL - could open in browser or handle deep links
|
// Handle external URL - could open in browser or handle deep links
|
||||||
}
|
}
|
||||||
// If no productIds or redirectUrl, banner is just for display
|
// If no skuIds or redirectUrl, banner is just for display
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
const handleScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||||
|
|
|
||||||
|
|
@ -128,10 +128,10 @@ const PaymentAndOrderComponent: React.FC<PaymentAndOrderProps> = ({
|
||||||
|
|
||||||
const availableItems = cartItems
|
const availableItems = cartItems
|
||||||
.filter(item => {
|
.filter(item => {
|
||||||
if (productSlotsMap[item.productId]?.isOutOfStock) return false;
|
if (productSlotsMap[item.skuId]?.isOutOfStock) return false;
|
||||||
// For flash delivery, check if product supports flash delivery
|
// For flash delivery, check if product supports flash delivery
|
||||||
if (isFlashDelivery) {
|
if (isFlashDelivery) {
|
||||||
return flashEligibleProductIds.has(item.productId);
|
return flashEligibleProductIds.has(item.skuId);
|
||||||
}
|
}
|
||||||
// For regular delivery, only include items with assigned slots
|
// For regular delivery, only include items with assigned slots
|
||||||
return selectedSlots[item.id];
|
return selectedSlots[item.id];
|
||||||
|
|
@ -150,7 +150,7 @@ const PaymentAndOrderComponent: React.FC<PaymentAndOrderProps> = ({
|
||||||
selectedItems: availableItems.map(itemId => {
|
selectedItems: availableItems.map(itemId => {
|
||||||
const item = cartItems.find(cartItem => cartItem.id === itemId);
|
const item = cartItems.find(cartItem => cartItem.id === itemId);
|
||||||
return {
|
return {
|
||||||
productId: item.productId,
|
skuId: item.skuId,
|
||||||
quantity: item.quantity,
|
quantity: item.quantity,
|
||||||
slotId: isFlashDelivery ? null : selectedSlots[itemId]
|
slotId: isFlashDelivery ? null : selectedSlots[itemId]
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ const ProductCard: React.FC<ProductCardProps> = ({
|
||||||
}) || {};
|
}) || {};
|
||||||
|
|
||||||
// Find current quantity from cart data
|
// Find current quantity from cart data
|
||||||
const cartItem = cartData?.items?.find((cartItem: any) => cartItem.productId === item.id);
|
const cartItem = cartData?.items?.find((cartItem: any) => cartItem.skuId === item.id);
|
||||||
const quantity = cartItem?.quantity || 0;
|
const quantity = cartItem?.quantity || 0;
|
||||||
|
|
||||||
// Get slots data from central store
|
// Get slots data from central store
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ const ProductDetail: React.FC<ProductDetailProps> = ({ productId, isFlashDeliver
|
||||||
}, [slotsData, productDetail])
|
}, [slotsData, productDetail])
|
||||||
|
|
||||||
// Find current quantity from cart data
|
// Find current quantity from cart data
|
||||||
const cartItem = productDetail ? cartData?.data?.items?.find((item: any) => item.productId === productDetail.id) : null;
|
const cartItem = productDetail ? cartData?.data?.items?.find((item: any) => item.skuId === productDetail.id) : null;
|
||||||
const quantity = cartItem?.quantity || 0;
|
const quantity = cartItem?.quantity || 0;
|
||||||
|
|
||||||
const handleQuantityChange = (newQuantity: number) => {
|
const handleQuantityChange = (newQuantity: number) => {
|
||||||
|
|
@ -143,7 +143,7 @@ const ProductDetail: React.FC<ProductDetailProps> = ({ productId, isFlashDeliver
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSlotAddToCart = (productId: number, selectedSlotId: number) => {
|
const handleSlotAddToCart = (productId: number, selectedSlotId: number) => {
|
||||||
const cartItem = cartData.data?.items?.find((item: any) => item.productId === productId);
|
const cartItem = cartData.data?.items?.find((item: any) => item.skuId === productId);
|
||||||
setIsLoadingDialogOpen(true);
|
setIsLoadingDialogOpen(true);
|
||||||
if (cartItem) {
|
if (cartItem) {
|
||||||
removeFromCart.mutate(
|
removeFromCart.mutate(
|
||||||
|
|
|
||||||
|
|
@ -252,7 +252,7 @@ const CompactProductCard = ({
|
||||||
refetchCart: true,
|
refetchCart: true,
|
||||||
}, cartType);
|
}, cartType);
|
||||||
|
|
||||||
const cartItem = cartData?.items?.find((cartItem: any) => cartItem.productId === item.id);
|
const cartItem = cartData?.items?.find((cartItem: any) => cartItem.skuId === item.id);
|
||||||
const quantity = cartItem?.quantity || 0;
|
const quantity = cartItem?.quantity || 0;
|
||||||
const isOutOfStock = productSlotsMap[item.id]?.isOutOfStock;
|
const isOutOfStock = productSlotsMap[item.id]?.isOutOfStock;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) {
|
||||||
} = useGetCart({ refetchOnWindowFocus: true }, cartType);
|
} = useGetCart({ refetchOnWindowFocus: true }, cartType);
|
||||||
|
|
||||||
// Extract product IDs from cart items
|
// Extract product IDs from cart items
|
||||||
const productIds = cartData?.items.map(item => item.productId) || [];
|
const productIds = cartData?.items.map(item => item.skuId) || [];
|
||||||
|
|
||||||
// Get cart slots for the products in cart
|
// Get cart slots for the products in cart
|
||||||
const { data: slotsData, refetch: refetchSlots, isLoading: isSlotsLoading } = trpc.user.cart.getCartSlots.useQuery(
|
const { data: slotsData, refetch: refetchSlots, isLoading: isSlotsLoading } = trpc.user.cart.getCartSlots.useQuery(
|
||||||
|
|
@ -106,9 +106,9 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) {
|
||||||
const baseTotalPrice = useMemo(
|
const baseTotalPrice = useMemo(
|
||||||
() =>
|
() =>
|
||||||
cartItems
|
cartItems
|
||||||
.filter((item) => !productSlotsMap[item.productId]?.isOutOfStock)
|
.filter((item) => !productSlotsMap[item.skuId]?.isOutOfStock)
|
||||||
.reduce((sum, item) => {
|
.reduce((sum, item) => {
|
||||||
const product = productsById[item.productId];
|
const product = productsById[item.skuId];
|
||||||
const price = product?.price || 0;
|
const price = product?.price || 0;
|
||||||
return sum + price * (quantities[item.id] || item.quantity);
|
return sum + price * (quantities[item.id] || item.quantity);
|
||||||
}, 0),
|
}, 0),
|
||||||
|
|
@ -208,9 +208,9 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) {
|
||||||
);
|
);
|
||||||
|
|
||||||
const totalPrice = cartItems
|
const totalPrice = cartItems
|
||||||
.filter((item) => !productSlotsMap[item.productId]?.isOutOfStock)
|
.filter((item) => !productSlotsMap[item.skuId]?.isOutOfStock)
|
||||||
.reduce((sum, item) => {
|
.reduce((sum, item) => {
|
||||||
const product = productsById[item.productId];
|
const product = productsById[item.skuId];
|
||||||
const quantity = quantities[item.id] || item.quantity;
|
const quantity = quantities[item.id] || item.quantity;
|
||||||
const price = isFlashDelivery ? (product?.flashPrice ?? product?.price ?? 0) : (product?.price || 0);
|
const price = isFlashDelivery ? (product?.flashPrice ?? product?.price ?? 0) : (product?.price || 0);
|
||||||
return sum + Number(price) * quantity;
|
return sum + Number(price) * quantity;
|
||||||
|
|
@ -282,7 +282,7 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) {
|
||||||
|
|
||||||
const finalTotalWithDelivery = finalTotal + deliveryCharge;
|
const finalTotalWithDelivery = finalTotal + deliveryCharge;
|
||||||
|
|
||||||
const hasAvailableItems = cartItems.some(item => !productSlotsMap[item.productId]?.isOutOfStock);
|
const hasAvailableItems = cartItems.some(item => !productSlotsMap[item.skuId]?.isOutOfStock);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initial: Record<number, number> = {};
|
const initial: Record<number, number> = {};
|
||||||
|
|
@ -308,7 +308,7 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) {
|
||||||
if (isFlashDelivery) {
|
if (isFlashDelivery) {
|
||||||
newSelectedSlots[item.id] = 0;
|
newSelectedSlots[item.id] = 0;
|
||||||
} else {
|
} else {
|
||||||
const productSlots = slotsData?.[item.productId];
|
const productSlots = slotsData?.[item.skuId];
|
||||||
if (!productSlots || productSlots.length === 0) return;
|
if (!productSlots || productSlots.length === 0) return;
|
||||||
|
|
||||||
const now = dayjs();
|
const now = dayjs();
|
||||||
|
|
@ -416,11 +416,11 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) {
|
||||||
<>
|
<>
|
||||||
<View style={tw`bg-white rounded-2xl shadow-sm mb-4 border border-gray-100`}>
|
<View style={tw`bg-white rounded-2xl shadow-sm mb-4 border border-gray-100`}>
|
||||||
{cartItems.map((item, index) => {
|
{cartItems.map((item, index) => {
|
||||||
const productSlots = getAvailableSlotsForProduct(item.productId);
|
const productSlots = getAvailableSlotsForProduct(item.skuId);
|
||||||
const selectedSlotForItem = selectedSlots[item.id];
|
const selectedSlotForItem = selectedSlots[item.id];
|
||||||
const isFlashEligible = isFlashDelivery ? flashEligibleProductIds.has(item.productId) : true;
|
const isFlashEligible = isFlashDelivery ? flashEligibleProductIds.has(item.skuId) : true;
|
||||||
const product = productsById[item.productId];
|
const product = productsById[item.skuId];
|
||||||
const productSlotInfo = productSlotsMap[item.productId];
|
const productSlotInfo = productSlotsMap[item.skuId];
|
||||||
// const isAvailable = (productSlots.length > 0 || isFlashDelivery) && !item.product?.isOutOfStock && isFlashEligible;
|
// const isAvailable = (productSlots.length > 0 || isFlashDelivery) && !item.product?.isOutOfStock && isFlashEligible;
|
||||||
let isAvailable = true;
|
let isAvailable = true;
|
||||||
|
|
||||||
|
|
@ -687,7 +687,7 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) {
|
||||||
<MyText style={tw`text-xs font-bold text-red-600`}>
|
<MyText style={tw`text-xs font-bold text-red-600`}>
|
||||||
{productSlotInfo?.isOutOfStock
|
{productSlotInfo?.isOutOfStock
|
||||||
? "Out of Stock"
|
? "Out of Stock"
|
||||||
: isFlashDelivery && !flashEligibleProductIds.has(item.productId)
|
: isFlashDelivery && !flashEligibleProductIds.has(item.skuId)
|
||||||
? "Not available for flash delivery. Please remove"
|
? "Not available for flash delivery. Please remove"
|
||||||
: "No delivery slots available"}
|
: "No delivery slots available"}
|
||||||
</MyText>
|
</MyText>
|
||||||
|
|
@ -919,10 +919,10 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) {
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
const availableItems = cartItems
|
const availableItems = cartItems
|
||||||
.filter(item => {
|
.filter(item => {
|
||||||
if (productSlotsMap[item.productId]?.isOutOfStock) return false;
|
if (productSlotsMap[item.skuId]?.isOutOfStock) return false;
|
||||||
if (isFlashDelivery) {
|
if (isFlashDelivery) {
|
||||||
// Check if product supports flash delivery
|
// Check if product supports flash delivery
|
||||||
return flashEligibleProductIds.has(item.productId);
|
return flashEligibleProductIds.has(item.skuId);
|
||||||
}
|
}
|
||||||
return selectedSlots[item.id]; // Regular delivery requires slot selection
|
return selectedSlots[item.id]; // Regular delivery requires slot selection
|
||||||
})
|
})
|
||||||
|
|
@ -930,8 +930,8 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) {
|
||||||
|
|
||||||
if (availableItems.length === 0) {
|
if (availableItems.length === 0) {
|
||||||
// Determine why no items are available
|
// Determine why no items are available
|
||||||
const outOfStockItems = cartItems.filter(item => productSlotsMap[item.productId]?.isOutOfStock);
|
const outOfStockItems = cartItems.filter(item => productSlotsMap[item.skuId]?.isOutOfStock);
|
||||||
const inStockItems = cartItems.filter(item => !productSlotsMap[item.productId]?.isOutOfStock);
|
const inStockItems = cartItems.filter(item => !productSlotsMap[item.skuId]?.isOutOfStock);
|
||||||
|
|
||||||
let errorTitle = "Cannot Proceed";
|
let errorTitle = "Cannot Proceed";
|
||||||
let errorMessage = "";
|
let errorMessage = "";
|
||||||
|
|
@ -943,7 +943,7 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) {
|
||||||
} else if (isFlashDelivery) {
|
} else if (isFlashDelivery) {
|
||||||
// Check if any items are flash-eligible
|
// Check if any items are flash-eligible
|
||||||
const flashEligibleItems = inStockItems.filter(item =>
|
const flashEligibleItems = inStockItems.filter(item =>
|
||||||
flashEligibleProductIds.has(item.productId)
|
flashEligibleProductIds.has(item.skuId)
|
||||||
);
|
);
|
||||||
if (flashEligibleItems.length === 0) {
|
if (flashEligibleItems.length === 0) {
|
||||||
errorTitle = "1 Hr Delivery Unavailable";
|
errorTitle = "1 Hr Delivery Unavailable";
|
||||||
|
|
@ -970,7 +970,7 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) {
|
||||||
|
|
||||||
// Check if there are items without slots (for regular delivery)
|
// Check if there are items without slots (for regular delivery)
|
||||||
if (!isFlashDelivery && availableItems.length < cartItems.length) {
|
if (!isFlashDelivery && availableItems.length < cartItems.length) {
|
||||||
const itemsWithoutSlots = cartItems.filter(item => !selectedSlots[item.id] && !productSlotsMap[item.productId]?.isOutOfStock);
|
const itemsWithoutSlots = cartItems.filter(item => !selectedSlots[item.id] && !productSlotsMap[item.skuId]?.isOutOfStock);
|
||||||
if (itemsWithoutSlots.length > 0) {
|
if (itemsWithoutSlots.length > 0) {
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
"Delivery Slot Required",
|
"Delivery Slot Required",
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ const CheckoutPage: React.FC<CheckoutPageProps> = ({ isFlashDelivery = false })
|
||||||
const selectedItems = cartItems.filter(item => {
|
const selectedItems = cartItems.filter(item => {
|
||||||
// For flash delivery, check if product supports flash delivery
|
// For flash delivery, check if product supports flash delivery
|
||||||
if (isFlashDelivery) {
|
if (isFlashDelivery) {
|
||||||
return flashEligibleProductIds.has(item.productId);
|
return flashEligibleProductIds.has(item.skuId);
|
||||||
}
|
}
|
||||||
// For regular delivery, only include items with assigned slots
|
// For regular delivery, only include items with assigned slots
|
||||||
return selectedSlots[item.id];
|
return selectedSlots[item.id];
|
||||||
|
|
@ -132,10 +132,10 @@ const CheckoutPage: React.FC<CheckoutPageProps> = ({ isFlashDelivery = false })
|
||||||
|
|
||||||
|
|
||||||
const totalPrice = selectedItems
|
const totalPrice = selectedItems
|
||||||
.filter((item) => !productSlotsMap[item.productId]?.isOutOfStock)
|
.filter((item) => !productSlotsMap[item.skuId]?.isOutOfStock)
|
||||||
.reduce(
|
.reduce(
|
||||||
(sum, item) => {
|
(sum, item) => {
|
||||||
const product = productsById[item.productId];
|
const product = productsById[item.skuId];
|
||||||
const price = isFlashDelivery ? (product?.flashPrice ?? product?.price ?? 0) : (product?.price || 0);
|
const price = isFlashDelivery ? (product?.flashPrice ?? product?.price ?? 0) : (product?.price || 0);
|
||||||
return sum + price * item.quantity;
|
return sum + price * item.quantity;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -122,17 +122,17 @@ const FloatingCartBar: React.FC<FloatingCartBarProps> = ({
|
||||||
const itemsToUpdate = cartItems.filter(item => {
|
const itemsToUpdate = cartItems.filter(item => {
|
||||||
if (isFlashDelivery || !item.slotId) return false;
|
if (isFlashDelivery || !item.slotId) return false;
|
||||||
|
|
||||||
const availableSlots = productSlotsMap[item.productId]?.slots || [];
|
const availableSlots = productSlotsMap[item.skuId]?.slots || [];
|
||||||
const isSlotAvailable = availableSlots.some((slot) => slot.id === item.slotId);
|
const isSlotAvailable = availableSlots.some((slot) => slot.id === item.slotId);
|
||||||
return !isSlotAvailable;
|
return !isSlotAvailable;
|
||||||
});
|
});
|
||||||
|
|
||||||
itemsToUpdate.forEach((item) => {
|
itemsToUpdate.forEach((item) => {
|
||||||
const availableSlots = productSlotsMap[item.productId]?.slots || [];
|
const availableSlots = productSlotsMap[item.skuId]?.slots || [];
|
||||||
if (availableSlots.length > 0 && !isFlashDelivery) {
|
if (availableSlots.length > 0 && !isFlashDelivery) {
|
||||||
const nearestSlotId = availableSlots[0].id;
|
const nearestSlotId = availableSlots[0].id;
|
||||||
removeFromCart.mutate({ itemId: item.id });
|
removeFromCart.mutate({ itemId: item.id });
|
||||||
addToCartHook.addToCart(item.productId, item.quantity, nearestSlotId);
|
addToCartHook.addToCart(item.skuId, item.quantity, nearestSlotId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
@ -143,7 +143,7 @@ const FloatingCartBar: React.FC<FloatingCartBarProps> = ({
|
||||||
// Calculate total cart value and free delivery info
|
// Calculate total cart value and free delivery info
|
||||||
const totalCartValue = cartItems.reduce(
|
const totalCartValue = cartItems.reduce(
|
||||||
(sum, item) => {
|
(sum, item) => {
|
||||||
const product = productsById[item.productId];
|
const product = productsById[item.skuId];
|
||||||
const basePrice = product?.price ?? 0;
|
const basePrice = product?.price ?? 0;
|
||||||
const price = isFlashDelivery ? (product?.flashPrice ?? basePrice) : basePrice;
|
const price = isFlashDelivery ? (product?.flashPrice ?? basePrice) : basePrice;
|
||||||
return sum + price * item.quantity;
|
return sum + price * item.quantity;
|
||||||
|
|
@ -268,20 +268,20 @@ const FloatingCartBar: React.FC<FloatingCartBarProps> = ({
|
||||||
<View style={tw`flex-row items-center`}>
|
<View style={tw`flex-row items-center`}>
|
||||||
<View style={tw`items-center`}>
|
<View style={tw`items-center`}>
|
||||||
<Image
|
<Image
|
||||||
source={{ uri: productsById[item.productId]?.images?.[0] }}
|
source={{ uri: productsById[item.skuId]?.images?.[0] }}
|
||||||
style={tw`w-8 h-8 rounded-lg bg-slate-50 border border-slate-100`}
|
style={tw`w-8 h-8 rounded-lg bg-slate-50 border border-slate-100`}
|
||||||
/>
|
/>
|
||||||
<MyText style={tw`text-gray-500 text-[9px] font-medium mt-1`}>
|
<MyText style={tw`text-gray-500 text-[9px] font-medium mt-1`}>
|
||||||
<MyText style={tw`text-[#f81260] font-semibold`}>{formatQuantity(productsById[item.productId]?.productQuantity || 1, productsById[item.productId]?.unitNotation || '').display}</MyText>
|
<MyText style={tw`text-[#f81260] font-semibold`}>{formatQuantity(productsById[item.skuId]?.productQuantity || 1, productsById[item.skuId]?.unitNotation || '').display}</MyText>
|
||||||
</MyText>
|
</MyText>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={tw`flex-1 ml-4`}>
|
<View style={tw`flex-1 ml-4`}>
|
||||||
<View style={tw`flex-row items-center justify-between mb-1`}>
|
<View style={tw`flex-row items-center justify-between mb-1`}>
|
||||||
<ProductNameWithQuantity
|
<ProductNameWithQuantity
|
||||||
name={productsById[item.productId]?.name || ''}
|
name={productsById[item.skuId]?.name || ''}
|
||||||
productQuantity={productsById[item.productId]?.productQuantity || 0}
|
productQuantity={productsById[item.skuId]?.productQuantity || 0}
|
||||||
unitNotation={productsById[item.productId]?.unitNotation || ''}
|
unitNotation={productsById[item.skuId]?.unitNotation || ''}
|
||||||
/>
|
/>
|
||||||
<MiniQuantifier
|
<MiniQuantifier
|
||||||
value={quantities[item.id] || item.quantity}
|
value={quantities[item.id] || item.quantity}
|
||||||
|
|
@ -293,17 +293,17 @@ const FloatingCartBar: React.FC<FloatingCartBarProps> = ({
|
||||||
updateCartItem.mutate({ itemId: item.id, quantity: value });
|
updateCartItem.mutate({ itemId: item.id, quantity: value });
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
step={productsById[item.productId]?.incrementStep || 1}
|
step={productsById[item.skuId]?.incrementStep || 1}
|
||||||
showUnits={true}
|
showUnits={true}
|
||||||
// unit={productsById[item.productId]?.unitNotation}
|
// unit={productsById[item.skuId]?.unitNotation}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View style={tw`flex-row items-center justify-between`}>
|
<View style={tw`flex-row items-center justify-between`}>
|
||||||
{item.slotId && slotsData && productSlotsMap[item.productId] && (
|
{item.slotId && slotsData && productSlotsMap[item.skuId] && (
|
||||||
<BottomDropdown
|
<BottomDropdown
|
||||||
label="Select Delivery Slot"
|
label="Select Delivery Slot"
|
||||||
value={item.slotId}
|
value={item.slotId}
|
||||||
options={(productSlotsMap[item.productId]?.slots || []).map((slot) => {
|
options={(productSlotsMap[item.skuId]?.slots || []).map((slot) => {
|
||||||
return {
|
return {
|
||||||
label: slot ? formatTimeRange(slot.deliveryTime) : "N/A",
|
label: slot ? formatTimeRange(slot.deliveryTime) : "N/A",
|
||||||
value: slot.id,
|
value: slot.id,
|
||||||
|
|
@ -313,7 +313,7 @@ const FloatingCartBar: React.FC<FloatingCartBarProps> = ({
|
||||||
const newSlot = slotsData.slots.find(s => s.id === val);
|
const newSlot = slotsData.slots.find(s => s.id === val);
|
||||||
if (!newSlot) return;
|
if (!newSlot) return;
|
||||||
|
|
||||||
const productId = item.productId;
|
const productId = item.skuId;
|
||||||
const quantity = item.quantity;
|
const quantity = item.quantity;
|
||||||
const itemId = item.id;
|
const itemId = item.id;
|
||||||
const slotId = typeof val === 'number' ? val : Number(val);
|
const slotId = typeof val === 'number' ? val : Number(val);
|
||||||
|
|
@ -340,7 +340,7 @@ const FloatingCartBar: React.FC<FloatingCartBarProps> = ({
|
||||||
)}
|
)}
|
||||||
<MyText style={tw`text-slate-900 text-sm font-bold`}>
|
<MyText style={tw`text-slate-900 text-sm font-bold`}>
|
||||||
₹{(() => {
|
₹{(() => {
|
||||||
const product = productsById[item.productId];
|
const product = productsById[item.skuId];
|
||||||
const basePrice = product?.price ?? 0;
|
const basePrice = product?.price ?? 0;
|
||||||
const price = isFlashDelivery ? (product?.flashPrice ?? basePrice) : basePrice;
|
const price = isFlashDelivery ? (product?.flashPrice ?? basePrice) : basePrice;
|
||||||
return price * item.quantity;
|
return price * item.quantity;
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ const getCartStorageKey = (cartType: CartType = "regular"): string => {
|
||||||
|
|
||||||
interface LocalCartItem {
|
interface LocalCartItem {
|
||||||
id: number;
|
id: number;
|
||||||
productId: number;
|
skuId: number;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
slotId: number;
|
slotId: number;
|
||||||
addedAt: string;
|
addedAt: string;
|
||||||
|
|
@ -35,7 +35,7 @@ interface ProductSummary {
|
||||||
|
|
||||||
export interface CartItem {
|
export interface CartItem {
|
||||||
id: number;
|
id: number;
|
||||||
productId: number;
|
skuId: number;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
addedAt: string;
|
addedAt: string;
|
||||||
subtotal: number;
|
subtotal: number;
|
||||||
|
|
@ -66,7 +66,7 @@ interface UseGetCartReturn {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AddToCartVariables {
|
interface AddToCartVariables {
|
||||||
productId: number;
|
skuId: number;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
slotId: number;
|
slotId: number;
|
||||||
}
|
}
|
||||||
|
|
@ -94,8 +94,8 @@ interface UseAddToCartReturn {
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
error: Error | null;
|
error: Error | null;
|
||||||
data: LocalCartItem[] | undefined;
|
data: LocalCartItem[] | undefined;
|
||||||
addToCart: (productId: number, quantity?: number, slotId?: number, onSettled?: (data: LocalCartItem[] | undefined, error: Error | null) => void) => void;
|
addToCart: (skuId: number, quantity?: number, slotId?: number, onSettled?: (data: LocalCartItem[] | undefined, error: Error | null) => void) => void;
|
||||||
addToCartAsync: (productId: number, quantity?: number, slotId?: number) => Promise<LocalCartItem[]>;
|
addToCartAsync: (skuId: number, quantity?: number, slotId?: number) => Promise<LocalCartItem[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UseUpdateCartItemReturn {
|
interface UseUpdateCartItemReturn {
|
||||||
|
|
@ -135,9 +135,9 @@ const getNextCartItemId = (items: LocalCartItem[]): number => {
|
||||||
return maxId + 1;
|
return maxId + 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
const addToLocalCart = async (productId: number, quantity: number, slotId: number | undefined, cartType: CartType = "regular"): Promise<LocalCartItem[]> => {
|
const addToLocalCart = async (skuId: number, quantity: number, slotId: number | undefined, cartType: CartType = "regular"): Promise<LocalCartItem[]> => {
|
||||||
const items = await getLocalCart(cartType);
|
const items = await getLocalCart(cartType);
|
||||||
const existingIndex = items.findIndex(item => item.productId === productId);
|
const existingIndex = items.findIndex(item => item.skuId === skuId);
|
||||||
|
|
||||||
if (existingIndex >= 0) {
|
if (existingIndex >= 0) {
|
||||||
items[existingIndex].quantity += quantity;
|
items[existingIndex].quantity += quantity;
|
||||||
|
|
@ -148,7 +148,7 @@ const addToLocalCart = async (productId: number, quantity: number, slotId: numbe
|
||||||
const newId = getNextCartItemId(items);
|
const newId = getNextCartItemId(items);
|
||||||
const cartItem: LocalCartItem = {
|
const cartItem: LocalCartItem = {
|
||||||
id: newId,
|
id: newId,
|
||||||
productId,
|
skuId,
|
||||||
quantity,
|
quantity,
|
||||||
slotId: slotId ?? 0,
|
slotId: slotId ?? 0,
|
||||||
addedAt: new Date().toISOString(),
|
addedAt: new Date().toISOString(),
|
||||||
|
|
@ -211,14 +211,14 @@ export function useGetCart(options: UseGetCartOptions = {}, cartType: CartType =
|
||||||
|
|
||||||
const items: CartItem[] = cartItems
|
const items: CartItem[] = cartItems
|
||||||
.map((cartItem): CartItem | null => {
|
.map((cartItem): CartItem | null => {
|
||||||
const productBasic = productMap[cartItem.productId];
|
const productBasic = productMap[cartItem.skuId];
|
||||||
const productAvailability = productSlotsMap[cartItem.productId];
|
const productAvailability = productSlotsMap[cartItem.skuId];
|
||||||
|
|
||||||
if (!productBasic || !productAvailability) return null;
|
if (!productBasic || !productAvailability) return null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: cartItem.id,
|
id: cartItem.id,
|
||||||
productId: cartItem.productId,
|
skuId: cartItem.skuId,
|
||||||
quantity: cartItem.quantity,
|
quantity: cartItem.quantity,
|
||||||
addedAt: cartItem.addedAt,
|
addedAt: cartItem.addedAt,
|
||||||
subtotal: Number(productBasic.price) * cartItem.quantity,
|
subtotal: Number(productBasic.price) * cartItem.quantity,
|
||||||
|
|
@ -256,8 +256,8 @@ export function useAddToCart(options: MutationOptions<LocalCartItem[], AddToCart
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const mutation: UseMutationResult<LocalCartItem[], Error, AddToCartVariables> = useMutation({
|
const mutation: UseMutationResult<LocalCartItem[], Error, AddToCartVariables> = useMutation({
|
||||||
mutationFn: async ({ productId, quantity, slotId }: AddToCartVariables): Promise<LocalCartItem[]> => {
|
mutationFn: async ({ skuId, quantity, slotId }: AddToCartVariables): Promise<LocalCartItem[]> => {
|
||||||
return await addToLocalCart(productId, quantity, slotId, cartType);
|
return await addToLocalCart(skuId, quantity, slotId, cartType);
|
||||||
},
|
},
|
||||||
onSuccess: (data: LocalCartItem[], variables: AddToCartVariables) => {
|
onSuccess: (data: LocalCartItem[], variables: AddToCartVariables) => {
|
||||||
queryClient.invalidateQueries({ queryKey: [`local-cart-${cartType}`] });
|
queryClient.invalidateQueries({ queryKey: [`local-cart-${cartType}`] });
|
||||||
|
|
@ -274,11 +274,11 @@ export function useAddToCart(options: MutationOptions<LocalCartItem[], AddToCart
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const addToCart = (productId: number, quantity = 1, slotId?: number, onSettled?: (data: LocalCartItem[] | undefined, error: Error | null) => void): void => {
|
const addToCart = (skuId: number, quantity = 1, slotId?: number, onSettled?: (data: LocalCartItem[] | undefined, error: Error | null) => void): void => {
|
||||||
if (slotId == null) {
|
if (slotId == null) {
|
||||||
throw new Error('slotId is required for adding to cart');
|
throw new Error('slotId is required for adding to cart');
|
||||||
}
|
}
|
||||||
mutation.mutate({ productId, quantity, slotId }, {
|
mutation.mutate({ skuId, quantity, slotId }, {
|
||||||
onSettled: (data: LocalCartItem[] | undefined, error: Error | null) => {
|
onSettled: (data: LocalCartItem[] | undefined, error: Error | null) => {
|
||||||
onSettled?.(data, error);
|
onSettled?.(data, error);
|
||||||
}
|
}
|
||||||
|
|
@ -292,11 +292,11 @@ export function useAddToCart(options: MutationOptions<LocalCartItem[], AddToCart
|
||||||
error: mutation.error,
|
error: mutation.error,
|
||||||
data: mutation.data,
|
data: mutation.data,
|
||||||
addToCart,
|
addToCart,
|
||||||
addToCartAsync: (productId: number, quantity = 1, slotId?: number): Promise<LocalCartItem[]> => {
|
addToCartAsync: (skuId: number, quantity = 1, slotId?: number): Promise<LocalCartItem[]> => {
|
||||||
if (slotId == null) {
|
if (slotId == null) {
|
||||||
throw new Error('slotId is required for adding to cart');
|
throw new Error('slotId is required for adding to cart');
|
||||||
}
|
}
|
||||||
return mutation.mutateAsync({ productId, quantity, slotId });
|
return mutation.mutateAsync({ skuId, quantity, slotId });
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,7 @@ export default function AddToCartDialog() {
|
||||||
} else {
|
} else {
|
||||||
const slotId = selectedSlotId ?? availableSlotIds[0] ?? 0;
|
const slotId = selectedSlotId ?? availableSlotIds[0] ?? 0;
|
||||||
addToCart.mutate(
|
addToCart.mutate(
|
||||||
{ productId: product.id, quantity, slotId },
|
{ skuId: product.id, quantity, slotId },
|
||||||
{ onSuccess: () => clearAddedToCartProduct() }
|
{ onSuccess: () => clearAddedToCartProduct() }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -222,7 +222,18 @@ ALTER TABLE `coupons` RENAME COLUMN `product_ids` TO `sku_ids`;
|
||||||
ALTER TABLE `reserved_coupons` RENAME COLUMN `product_ids` TO `sku_ids`;
|
ALTER TABLE `reserved_coupons` RENAME COLUMN `product_ids` TO `sku_ids`;
|
||||||
ALTER TABLE `vendor_snippets` RENAME COLUMN `product_ids` TO `sku_ids`;
|
ALTER TABLE `vendor_snippets` RENAME COLUMN `product_ids` TO `sku_ids`;
|
||||||
|
|
||||||
-- 8. Clean up helper table.
|
-- 8. Update popularItems in key_val_store from product IDs to SKU IDs.
|
||||||
|
UPDATE `key_val_store`
|
||||||
|
SET `value` = (
|
||||||
|
SELECT json_group_array(`m`.`sku_id`)
|
||||||
|
FROM json_each(`key_val_store`.`value`) AS `je`
|
||||||
|
JOIN `__product_to_sku` `m` ON `m`.`product_id` = `je`.`value`
|
||||||
|
)
|
||||||
|
WHERE `key` = 'popularItems'
|
||||||
|
AND `value` IS NOT NULL
|
||||||
|
AND `value` LIKE '[%';
|
||||||
|
|
||||||
|
-- 9. Clean up helper table.
|
||||||
DROP TABLE `__product_to_sku`;
|
DROP TABLE `__product_to_sku`;
|
||||||
|
|
||||||
-- PRAGMA foreign_keys=ON;
|
-- PRAGMA foreign_keys=ON;
|
||||||
|
|
|
||||||
|
|
@ -311,7 +311,7 @@ export {
|
||||||
cancelOrderTransaction as cancelUserOrderTransaction,
|
cancelOrderTransaction as cancelUserOrderTransaction,
|
||||||
updateOrderNotes as updateUserOrderNotes,
|
updateOrderNotes as updateUserOrderNotes,
|
||||||
getRecentlyDeliveredOrderIds as getUserRecentlyDeliveredOrderIds,
|
getRecentlyDeliveredOrderIds as getUserRecentlyDeliveredOrderIds,
|
||||||
getProductIdsFromOrders as getUserProductIdsFromOrders,
|
getSkuIdsFromOrders as getUserProductIdsFromOrders,
|
||||||
getProductsForRecentOrders as getUserProductsForRecentOrders,
|
getProductsForRecentOrders as getUserProductsForRecentOrders,
|
||||||
// Post-order handler helpers
|
// Post-order handler helpers
|
||||||
getOrdersByIdsWithFullData,
|
getOrdersByIdsWithFullData,
|
||||||
|
|
@ -364,7 +364,7 @@ export {
|
||||||
|
|
||||||
// Common API Helpers
|
// Common API Helpers
|
||||||
export {
|
export {
|
||||||
getSuspendedProductIds,
|
getSuspendedSkuIds,
|
||||||
getNextDeliveryDateWithCapacity,
|
getNextDeliveryDateWithCapacity,
|
||||||
} from './src/user-apis/product'
|
} from './src/user-apis/product'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -792,12 +792,12 @@ export async function updateProductPrices(updates: Array<{
|
||||||
}
|
}
|
||||||
|
|
||||||
const productIds = updates.map((update) => update.productId)
|
const productIds = updates.map((update) => update.productId)
|
||||||
const existingProducts = await db.query.productInfo.findMany({
|
const existingSkus = await db.query.productSkus.findMany({
|
||||||
where: inArray(productInfo.id, productIds),
|
where: inArray(productSkus.id, productIds),
|
||||||
columns: { id: true },
|
columns: { id: true },
|
||||||
}) as Array<{ id: number }>
|
}) as Array<{ id: number }>
|
||||||
|
|
||||||
const existingIds = new Set(existingProducts.map((product: { id: number }) => product.id))
|
const existingIds = new Set(existingSkus.map((sku: { id: number }) => sku.id))
|
||||||
const invalidIds = productIds.filter((id) => !existingIds.has(id))
|
const invalidIds = productIds.filter((id) => !existingIds.has(id))
|
||||||
|
|
||||||
if (invalidIds.length > 0) {
|
if (invalidIds.length > 0) {
|
||||||
|
|
@ -806,7 +806,7 @@ export async function updateProductPrices(updates: Array<{
|
||||||
|
|
||||||
const updatePromises = updates.map((update) => {
|
const updatePromises = updates.map((update) => {
|
||||||
const { productId, price, marketPrice, flashPrice, isFlashAvailable } = update
|
const { productId, price, marketPrice, flashPrice, isFlashAvailable } = update
|
||||||
const updateData: Partial<Pick<ProductInfoInsert, 'price' | 'marketPrice' | 'flashPrice' | 'isFlashAvailable'>> = {}
|
const updateData: any = {}
|
||||||
|
|
||||||
if (price !== undefined) updateData.price = price.toString()
|
if (price !== undefined) updateData.price = price.toString()
|
||||||
if (marketPrice !== undefined) updateData.marketPrice = marketPrice === null ? null : marketPrice.toString()
|
if (marketPrice !== undefined) updateData.marketPrice = marketPrice === null ? null : marketPrice.toString()
|
||||||
|
|
@ -814,9 +814,9 @@ export async function updateProductPrices(updates: Array<{
|
||||||
if (isFlashAvailable !== undefined) updateData.isFlashAvailable = isFlashAvailable
|
if (isFlashAvailable !== undefined) updateData.isFlashAvailable = isFlashAvailable
|
||||||
|
|
||||||
return db
|
return db
|
||||||
.update(productInfo)
|
.update(productSkus)
|
||||||
.set(updateData)
|
.set(updateData)
|
||||||
.where(eq(productInfo.id, productId))
|
.where(eq(productSkus.id, productId))
|
||||||
})
|
})
|
||||||
|
|
||||||
await Promise.all(updatePromises)
|
await Promise.all(updatePromises)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@ import { db } from '../db/db_index'
|
||||||
import {
|
import {
|
||||||
homeBanners,
|
homeBanners,
|
||||||
productInfo,
|
productInfo,
|
||||||
units,
|
productSkus,
|
||||||
|
skuFeatures,
|
||||||
deliverySlotInfo,
|
deliverySlotInfo,
|
||||||
specialDeals,
|
specialDeals,
|
||||||
storeInfo,
|
storeInfo,
|
||||||
|
|
@ -24,7 +25,7 @@ export interface BannerData {
|
||||||
name: string
|
name: string
|
||||||
imageUrl: string | null
|
imageUrl: string | null
|
||||||
serialNum: number | null
|
serialNum: number | null
|
||||||
productIds: number[] | null
|
skuIds: number[] | null
|
||||||
createdAt: Date
|
createdAt: Date
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -41,7 +42,9 @@ export async function getAllBannersForCache(): Promise<BannerData[]> {
|
||||||
|
|
||||||
export interface ProductBasicData {
|
export interface ProductBasicData {
|
||||||
id: number
|
id: number
|
||||||
|
productId: number
|
||||||
name: string
|
name: string
|
||||||
|
skuName: string | null
|
||||||
shortDescription: string | null
|
shortDescription: string | null
|
||||||
longDescription: string | null
|
longDescription: string | null
|
||||||
price: string
|
price: string
|
||||||
|
|
@ -49,7 +52,7 @@ export interface ProductBasicData {
|
||||||
images: unknown
|
images: unknown
|
||||||
isOutOfStock: boolean
|
isOutOfStock: boolean
|
||||||
storeId: number | null
|
storeId: number | null
|
||||||
unitShortNotation: string
|
unitNotation: string
|
||||||
incrementStep: number
|
incrementStep: number
|
||||||
productQuantity: number
|
productQuantity: number
|
||||||
isFlashAvailable: boolean
|
isFlashAvailable: boolean
|
||||||
|
|
@ -63,7 +66,7 @@ export interface StoreBasicData {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DeliverySlotData {
|
export interface DeliverySlotData {
|
||||||
productId: number
|
skuId: number
|
||||||
id: number
|
id: number
|
||||||
deliveryTime: Date
|
deliveryTime: Date
|
||||||
freezeTime: Date
|
freezeTime: Date
|
||||||
|
|
@ -71,44 +74,64 @@ export interface DeliverySlotData {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SpecialDealData {
|
export interface SpecialDealData {
|
||||||
productId: number
|
skuId: number
|
||||||
quantity: string
|
quantity: string
|
||||||
price: string
|
price: string
|
||||||
validTill: Date
|
validTill: Date
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getAllSpecialDealsForCache(): Promise<SpecialDealData[]> {
|
||||||
|
const results = await db
|
||||||
|
.select({
|
||||||
|
skuId: specialDeals.skuId,
|
||||||
|
quantity: specialDeals.quantity,
|
||||||
|
price: specialDeals.price,
|
||||||
|
validTill: specialDeals.validTill,
|
||||||
|
})
|
||||||
|
.from(specialDeals)
|
||||||
|
.where(gt(specialDeals.validTill, sql`CURRENT_TIMESTAMP`))
|
||||||
|
|
||||||
|
return results.map((deal) => ({
|
||||||
|
...deal,
|
||||||
|
quantity: String(deal.quantity ?? '0'),
|
||||||
|
price: String(deal.price ?? '0'),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
export interface ProductTagData {
|
export interface ProductTagData {
|
||||||
productId: number
|
productId: number
|
||||||
tagName: string
|
tagName: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAllProductsForCache(): Promise<ProductBasicData[]> {
|
export async function getAllProductsForCache(): Promise<ProductBasicData[]> {
|
||||||
const results = await db
|
const skus = await db.query.productSkus.findMany({
|
||||||
.select({
|
with: {
|
||||||
id: productInfo.id,
|
product: true,
|
||||||
name: productInfo.name,
|
features: true,
|
||||||
shortDescription: productInfo.shortDescription,
|
},
|
||||||
longDescription: productInfo.longDescription,
|
})
|
||||||
price: productInfo.price,
|
|
||||||
marketPrice: productInfo.marketPrice,
|
|
||||||
images: productInfo.images,
|
|
||||||
isOutOfStock: productInfo.isOutOfStock,
|
|
||||||
storeId: productInfo.storeId,
|
|
||||||
unitShortNotation: units.shortNotation,
|
|
||||||
incrementStep: productInfo.incrementStep,
|
|
||||||
productQuantity: productInfo.productQuantity,
|
|
||||||
isFlashAvailable: productInfo.isFlashAvailable,
|
|
||||||
flashPrice: productInfo.flashPrice,
|
|
||||||
})
|
|
||||||
.from(productInfo)
|
|
||||||
.innerJoin(units, eq(productInfo.unitId, units.id))
|
|
||||||
|
|
||||||
return results.map((product) => ({
|
return skus.map((sku) => {
|
||||||
...product,
|
const features = sku.features || []
|
||||||
price: String(product.price ?? '0'),
|
return {
|
||||||
marketPrice: product.marketPrice ? String(product.marketPrice) : null,
|
id: sku.id,
|
||||||
flashPrice: product.flashPrice ? String(product.flashPrice) : null,
|
productId: sku.productId,
|
||||||
}))
|
name: sku.product?.name ?? 'Unknown',
|
||||||
|
skuName: sku.name ?? null,
|
||||||
|
shortDescription: sku.product?.shortDescription ?? null,
|
||||||
|
longDescription: sku.product?.longDescription ?? null,
|
||||||
|
price: String(sku.price ?? '0'),
|
||||||
|
marketPrice: sku.marketPrice ? String(sku.marketPrice) : null,
|
||||||
|
images: sku.images,
|
||||||
|
isOutOfStock: sku.isOutOfStock,
|
||||||
|
storeId: sku.product?.storeId ?? null,
|
||||||
|
unitNotation: features.map((f) => f.featureValue).join(' '),
|
||||||
|
incrementStep: sku.product?.incrementStep ?? 1,
|
||||||
|
productQuantity: 1,
|
||||||
|
isFlashAvailable: sku.isFlashAvailable,
|
||||||
|
flashPrice: sku.flashPrice ? String(sku.flashPrice) : null,
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAllStoresForCache(): Promise<StoreBasicData[]> {
|
export async function getAllStoresForCache(): Promise<StoreBasicData[]> {
|
||||||
|
|
@ -126,13 +149,12 @@ export async function getAllDeliverySlotsForCache(): Promise<DeliverySlotData[]>
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
// Flatten slots with their product IDs
|
|
||||||
const result: DeliverySlotData[] = []
|
const result: DeliverySlotData[] = []
|
||||||
for (const slot of slots) {
|
for (const slot of slots) {
|
||||||
const productIds = slot.productIds || []
|
const skuIds = slot.skuIds || []
|
||||||
for (const productId of productIds) {
|
for (const skuId of skuIds) {
|
||||||
result.push({
|
result.push({
|
||||||
productId,
|
skuId,
|
||||||
id: slot.id,
|
id: slot.id,
|
||||||
deliveryTime: slot.deliveryTime,
|
deliveryTime: slot.deliveryTime,
|
||||||
freezeTime: slot.freezeTime,
|
freezeTime: slot.freezeTime,
|
||||||
|
|
@ -144,24 +166,6 @@ export async function getAllDeliverySlotsForCache(): Promise<DeliverySlotData[]>
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAllSpecialDealsForCache(): Promise<SpecialDealData[]> {
|
|
||||||
const results = await db
|
|
||||||
.select({
|
|
||||||
productId: specialDeals.productId,
|
|
||||||
quantity: specialDeals.quantity,
|
|
||||||
price: specialDeals.price,
|
|
||||||
validTill: specialDeals.validTill,
|
|
||||||
})
|
|
||||||
.from(specialDeals)
|
|
||||||
.where(gt(specialDeals.validTill, sql`CURRENT_TIMESTAMP`))
|
|
||||||
|
|
||||||
return results.map((deal) => ({
|
|
||||||
...deal,
|
|
||||||
quantity: String(deal.quantity ?? '0'),
|
|
||||||
price: String(deal.price ?? '0'),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getAllProductTagsForCache(): Promise<ProductTagData[]> {
|
export async function getAllProductTagsForCache(): Promise<ProductTagData[]> {
|
||||||
return db
|
return db
|
||||||
.select({
|
.select({
|
||||||
|
|
@ -224,23 +228,26 @@ export interface SlotWithProductsData {
|
||||||
isCapacityFull: boolean
|
isCapacityFull: boolean
|
||||||
products: Array<{
|
products: Array<{
|
||||||
id: number
|
id: number
|
||||||
|
productId: number
|
||||||
name: string
|
name: string
|
||||||
|
skuName: string | null
|
||||||
productQuantity: number
|
productQuantity: number
|
||||||
shortDescription: string | null
|
shortDescription: string | null
|
||||||
price: string
|
price: string
|
||||||
marketPrice: string | null
|
marketPrice: string | null
|
||||||
unit: { shortNotation: string } | null
|
unitNotation: string
|
||||||
store: { id: number; name: string; description: string | null } | null
|
store: { id: number; name: string; description: string | null } | null
|
||||||
images: unknown
|
images: unknown
|
||||||
isOutOfStock: boolean
|
isOutOfStock: boolean
|
||||||
storeId: number | null
|
storeId: number | null
|
||||||
|
isFlashAvailable: boolean
|
||||||
|
flashPrice: string | null
|
||||||
}>
|
}>
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAllSlotsWithProductsForCache(): Promise<SlotWithProductsData[]> {
|
export async function getAllSlotsWithProductsForCache(): Promise<SlotWithProductsData[]> {
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
|
|
||||||
// Get all active future slots
|
|
||||||
const slots = await db.query.deliverySlotInfo.findMany({
|
const slots = await db.query.deliverySlotInfo.findMany({
|
||||||
where: and(
|
where: and(
|
||||||
eq(deliverySlotInfo.isActive, true),
|
eq(deliverySlotInfo.isActive, true),
|
||||||
|
|
@ -249,68 +256,64 @@ export async function getAllSlotsWithProductsForCache(): Promise<SlotWithProduct
|
||||||
orderBy: asc(deliverySlotInfo.deliveryTime),
|
orderBy: asc(deliverySlotInfo.deliveryTime),
|
||||||
})
|
})
|
||||||
|
|
||||||
// Get all unique product IDs from all slots
|
const allSkuIds = new Set<number>()
|
||||||
const allProductIds = new Set<number>()
|
|
||||||
for (const slot of slots) {
|
for (const slot of slots) {
|
||||||
for (const productId of (slot.productIds || [])) {
|
for (const skuId of (slot.skuIds || [])) {
|
||||||
allProductIds.add(productId)
|
allSkuIds.add(skuId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch all products in one query
|
const skuIdsArray = Array.from(allSkuIds)
|
||||||
const productIdsArray = Array.from(allProductIds)
|
const skuIdSet = new Set(skuIdsArray)
|
||||||
const productIdSet = new Set(productIdsArray);
|
|
||||||
// const productsData = productIdsArray.length > 0
|
let skusData: any[] = []
|
||||||
// ? await db.query.productInfo.findMany({
|
if (skuIdsArray.length > 0) {
|
||||||
// where: inArray(productInfo.id, productIdsArray),
|
skusData = await db.query.productSkus.findMany({
|
||||||
// with: {
|
with: {
|
||||||
// unit: true,
|
product: {
|
||||||
// store: true,
|
with: { store: true },
|
||||||
// },
|
|
||||||
// })
|
|
||||||
// : []
|
|
||||||
let productsData = productIdsArray.length > 0
|
|
||||||
? await db.query.productInfo.findMany({
|
|
||||||
// where: inArray(productInfo.id, productIdsArray),
|
|
||||||
with: {
|
|
||||||
unit: true,
|
|
||||||
store: true,
|
|
||||||
},
|
},
|
||||||
})
|
features: true,
|
||||||
: []
|
},
|
||||||
|
})
|
||||||
|
skusData = skusData.filter((item: any) => skuIdSet.has(item.id))
|
||||||
|
}
|
||||||
|
|
||||||
productsData = productsData.filter(item => productIdSet.has(item.id))
|
const skuMap = new Map(skusData.map((s: any) => [s.id, s]))
|
||||||
|
|
||||||
// Create a map for quick lookup
|
return slots.map((slot) => ({
|
||||||
const productMap = new Map(productsData.map(p => [p.id, p]))
|
|
||||||
|
|
||||||
// Build the result
|
|
||||||
return slots.map(slot => ({
|
|
||||||
id: slot.id,
|
id: slot.id,
|
||||||
deliveryTime: slot.deliveryTime,
|
deliveryTime: slot.deliveryTime,
|
||||||
freezeTime: slot.freezeTime,
|
freezeTime: slot.freezeTime,
|
||||||
isActive: slot.isActive,
|
isActive: slot.isActive,
|
||||||
isCapacityFull: slot.isCapacityFull,
|
isCapacityFull: slot.isCapacityFull,
|
||||||
products: (slot.productIds || [])
|
products: (slot.skuIds || [])
|
||||||
.map(productId => productMap.get(productId))
|
.map((skuId: number) => skuMap.get(skuId))
|
||||||
.filter((p): p is NonNullable<typeof p> => p != null)
|
.filter((p): p is NonNullable<typeof p> => p != null)
|
||||||
.map(product => ({
|
.map((sku: any) => {
|
||||||
id: product.id,
|
const features = sku.features || []
|
||||||
name: product.name,
|
return {
|
||||||
productQuantity: product.productQuantity,
|
id: sku.id,
|
||||||
shortDescription: product.shortDescription,
|
productId: sku.productId,
|
||||||
price: String(product.price ?? '0'),
|
name: sku.product?.name ?? 'Unknown',
|
||||||
marketPrice: product.marketPrice ? String(product.marketPrice) : null,
|
skuName: sku.name ?? null,
|
||||||
unit: product.unit ? { shortNotation: product.unit.shortNotation } : null,
|
productQuantity: 1,
|
||||||
store: product.store ? {
|
shortDescription: sku.product?.shortDescription ?? null,
|
||||||
id: product.store.id,
|
price: String(sku.price ?? '0'),
|
||||||
name: product.store.name,
|
marketPrice: sku.marketPrice ? String(sku.marketPrice) : null,
|
||||||
description: product.store.description
|
unitNotation: features.map((f: any) => f.featureValue).join(' '),
|
||||||
} : null,
|
store: sku.product?.store ? {
|
||||||
images: product.images,
|
id: sku.product.store.id,
|
||||||
isOutOfStock: product.isOutOfStock,
|
name: sku.product.store.name,
|
||||||
storeId: product.storeId,
|
description: sku.product.store.description
|
||||||
})),
|
} : null,
|
||||||
|
images: sku.images,
|
||||||
|
isOutOfStock: sku.isOutOfStock,
|
||||||
|
storeId: sku.product?.storeId ?? null,
|
||||||
|
isFlashAvailable: sku.isFlashAvailable,
|
||||||
|
flashPrice: sku.flashPrice ? String(sku.flashPrice) : null,
|
||||||
|
}
|
||||||
|
}),
|
||||||
})) as SlotWithProductsData[]
|
})) as SlotWithProductsData[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import {
|
||||||
orderStatus,
|
orderStatus,
|
||||||
addresses,
|
addresses,
|
||||||
productInfo,
|
productInfo,
|
||||||
|
productSkus,
|
||||||
paymentInfoTable,
|
paymentInfoTable,
|
||||||
coupons,
|
coupons,
|
||||||
couponUsage,
|
couponUsage,
|
||||||
|
|
@ -258,9 +259,9 @@ export async function getAddressByIdAndUser(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getProductById(productId: number) {
|
export async function getProductById(skuId: number) {
|
||||||
return db.query.productInfo.findFirst({
|
return db.query.productSkus.findFirst({
|
||||||
where: eq(productInfo.id, productId),
|
where: eq(productSkus.id, skuId),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -341,12 +342,12 @@ export async function placeOrderTransaction(params: {
|
||||||
|
|
||||||
export async function deleteCartItemsForOrder(
|
export async function deleteCartItemsForOrder(
|
||||||
userId: number,
|
userId: number,
|
||||||
productIds: number[]
|
skuIds: number[]
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await db.delete(cartItems).where(
|
await db.delete(cartItems).where(
|
||||||
and(
|
and(
|
||||||
eq(cartItems.userId, userId),
|
eq(cartItems.userId, userId),
|
||||||
inArray(cartItems.productId, productIds)
|
inArray(cartItems.skuId, skuIds)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -375,10 +376,16 @@ export async function getOrdersWithRelations(
|
||||||
with: {
|
with: {
|
||||||
orderItems: {
|
orderItems: {
|
||||||
with: {
|
with: {
|
||||||
product: {
|
sku: {
|
||||||
|
with: {
|
||||||
|
product: {
|
||||||
|
columns: {
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
columns: {
|
columns: {
|
||||||
id: true,
|
id: true,
|
||||||
name: true,
|
|
||||||
images: true,
|
images: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -451,10 +458,16 @@ export async function getOrderByIdWithRelations(
|
||||||
with: {
|
with: {
|
||||||
orderItems: {
|
orderItems: {
|
||||||
with: {
|
with: {
|
||||||
product: {
|
sku: {
|
||||||
|
with: {
|
||||||
|
product: {
|
||||||
|
columns: {
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
columns: {
|
columns: {
|
||||||
id: true,
|
id: true,
|
||||||
name: true,
|
|
||||||
images: true,
|
images: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -610,15 +623,15 @@ export async function getRecentlyDeliveredOrderIds(
|
||||||
return recentOrders.map((order) => order.id)
|
return recentOrders.map((order) => order.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getProductIdsFromOrders(
|
export async function getSkuIdsFromOrders(
|
||||||
orderIds: number[]
|
orderIds: number[]
|
||||||
): Promise<number[]> {
|
): Promise<number[]> {
|
||||||
const orderItemsResult = await db
|
const orderItemsResult = await db
|
||||||
.select({ productId: orderItems.productId })
|
.select({ skuId: orderItems.skuId })
|
||||||
.from(orderItems)
|
.from(orderItems)
|
||||||
.where(inArray(orderItems.orderId, orderIds))
|
.where(inArray(orderItems.orderId, orderIds))
|
||||||
|
|
||||||
return [...new Set(orderItemsResult.map((item) => item.productId))]
|
return [...new Set(orderItemsResult.map((item) => item.skuId))]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RecentProductData {
|
export interface RecentProductData {
|
||||||
|
|
@ -714,14 +727,17 @@ export async function getOrdersByIdsWithFullData(
|
||||||
},
|
},
|
||||||
orderItems: {
|
orderItems: {
|
||||||
with: {
|
with: {
|
||||||
product: {
|
sku: {
|
||||||
columns: {
|
columns: {
|
||||||
name: true,
|
name: true,
|
||||||
productQuantity: true,
|
price: true,
|
||||||
},
|
},
|
||||||
with: {
|
with: {
|
||||||
unit: true
|
product: {
|
||||||
}
|
columns: { name: true },
|
||||||
|
},
|
||||||
|
features: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { db } from '../db/db_index'
|
import { db } from '../db/db_index'
|
||||||
import { deliverySlotInfo, productInfo, productReviews, productTags, specialDeals, storeInfo, units, users } from '../db/schema'
|
import { deliverySlotInfo, productInfo, productSkus, productReviews, productTags, specialDeals, storeInfo, units, users } from '../db/schema'
|
||||||
import { and, desc, eq, gt, sql } from 'drizzle-orm'
|
import { and, desc, eq, gt, sql } from 'drizzle-orm'
|
||||||
import type { UserProductDetailData, UserProductReview } from '@packages/shared'
|
import type { UserProductDetailData, UserProductReview } from '@packages/shared'
|
||||||
|
|
||||||
|
|
@ -8,42 +8,27 @@ const getStringArray = (value: unknown): string[] | null => {
|
||||||
return value.map((item) => String(item))
|
return value.map((item) => String(item))
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getProductDetailById(productId: number): Promise<UserProductDetailData | null> {
|
export async function getProductDetailById(skuId: number): Promise<UserProductDetailData | null> {
|
||||||
const productData = await db
|
const sku = await db.query.productSkus.findFirst({
|
||||||
.select({
|
where: eq(productSkus.id, skuId),
|
||||||
id: productInfo.id,
|
with: {
|
||||||
name: productInfo.name,
|
product: true,
|
||||||
shortDescription: productInfo.shortDescription,
|
features: true,
|
||||||
longDescription: productInfo.longDescription,
|
},
|
||||||
price: productInfo.price,
|
})
|
||||||
marketPrice: productInfo.marketPrice,
|
|
||||||
images: productInfo.images,
|
|
||||||
isOutOfStock: productInfo.isOutOfStock,
|
|
||||||
storeId: productInfo.storeId,
|
|
||||||
unitShortNotation: units.shortNotation,
|
|
||||||
incrementStep: productInfo.incrementStep,
|
|
||||||
productQuantity: productInfo.productQuantity,
|
|
||||||
isFlashAvailable: productInfo.isFlashAvailable,
|
|
||||||
flashPrice: productInfo.flashPrice,
|
|
||||||
})
|
|
||||||
.from(productInfo)
|
|
||||||
.innerJoin(units, eq(productInfo.unitId, units.id))
|
|
||||||
.where(eq(productInfo.id, productId))
|
|
||||||
.limit(1)
|
|
||||||
|
|
||||||
if (productData.length === 0) {
|
if (!sku) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const product = productData[0]
|
const features = sku.features || []
|
||||||
|
const product = sku.product
|
||||||
|
|
||||||
const storeData = product.storeId ? await db.query.storeInfo.findFirst({
|
const storeData = product?.storeId ? await db.query.storeInfo.findFirst({
|
||||||
where: eq(storeInfo.id, product.storeId),
|
where: eq(storeInfo.id, product.storeId),
|
||||||
columns: { id: true, name: true, description: true },
|
columns: { id: true, name: true, description: true },
|
||||||
}) : null
|
}) : null
|
||||||
|
|
||||||
// Note: deliverySlots are now fetched from cache in the frontend via useSlots()
|
|
||||||
// This avoids expensive database joins on every product detail view
|
|
||||||
const specialDealsData = await db
|
const specialDealsData = await db
|
||||||
.select({
|
.select({
|
||||||
quantity: specialDeals.quantity,
|
quantity: specialDeals.quantity,
|
||||||
|
|
@ -53,32 +38,32 @@ export async function getProductDetailById(productId: number): Promise<UserProdu
|
||||||
.from(specialDeals)
|
.from(specialDeals)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(specialDeals.productId, productId),
|
eq(specialDeals.skuId, skuId),
|
||||||
gt(specialDeals.validTill, sql`CURRENT_TIMESTAMP`)
|
gt(specialDeals.validTill, sql`CURRENT_TIMESTAMP`)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.orderBy(specialDeals.quantity)
|
.orderBy(specialDeals.quantity)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: product.id,
|
id: sku.id,
|
||||||
name: product.name,
|
name: product?.name ?? 'Unknown',
|
||||||
shortDescription: product.shortDescription ?? null,
|
shortDescription: product?.shortDescription ?? null,
|
||||||
longDescription: product.longDescription ?? null,
|
longDescription: product?.longDescription ?? null,
|
||||||
price: String(product.price ?? '0'),
|
price: String(sku.price ?? '0'),
|
||||||
marketPrice: product.marketPrice ? String(product.marketPrice) : null,
|
marketPrice: sku.marketPrice ? String(sku.marketPrice) : null,
|
||||||
unitNotation: product.unitShortNotation,
|
unitNotation: features.map((f) => f.featureValue).join(' '),
|
||||||
images: getStringArray(product.images),
|
images: getStringArray(sku.images),
|
||||||
isOutOfStock: product.isOutOfStock,
|
isOutOfStock: sku.isOutOfStock,
|
||||||
store: storeData ? {
|
store: storeData ? {
|
||||||
id: storeData.id,
|
id: storeData.id,
|
||||||
name: storeData.name,
|
name: storeData.name,
|
||||||
description: storeData.description ?? null,
|
description: storeData.description ?? null,
|
||||||
} : null,
|
} : null,
|
||||||
incrementStep: product.incrementStep,
|
incrementStep: product?.incrementStep ?? 1,
|
||||||
productQuantity: product.productQuantity,
|
productQuantity: 1,
|
||||||
isFlashAvailable: product.isFlashAvailable,
|
isFlashAvailable: sku.isFlashAvailable,
|
||||||
flashPrice: product.flashPrice?.toString() || null,
|
flashPrice: sku.flashPrice?.toString() || null,
|
||||||
deliverySlots: [], // Fetched from cache in frontend via useSlots()
|
deliverySlots: [],
|
||||||
specialDeals: specialDealsData.map((deal) => ({
|
specialDeals: specialDealsData.map((deal) => ({
|
||||||
quantity: String(deal.quantity ?? '0'),
|
quantity: String(deal.quantity ?? '0'),
|
||||||
price: String(deal.price ?? '0'),
|
price: String(deal.price ?? '0'),
|
||||||
|
|
@ -126,9 +111,9 @@ export async function getProductReviews(productId: number, limit: number, offset
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getProductById(productId: number) {
|
export async function getProductById(skuId: number) {
|
||||||
return db.query.productInfo.findFirst({
|
return db.query.productSkus.findFirst({
|
||||||
where: eq(productInfo.id, productId),
|
where: eq(productSkus.id, skuId),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -221,20 +206,20 @@ export async function getAllProductsWithUnits(tagId?: number): Promise<ProductSu
|
||||||
/**
|
/**
|
||||||
* Get all suspended product IDs
|
* Get all suspended product IDs
|
||||||
*/
|
*/
|
||||||
export async function getSuspendedProductIds(): Promise<number[]> {
|
export async function getSuspendedSkuIds(): Promise<number[]> {
|
||||||
const suspendedProducts = await db
|
const suspendedSkus = await db
|
||||||
.select({ id: productInfo.id })
|
.select({ id: productSkus.id })
|
||||||
.from(productInfo)
|
.from(productSkus)
|
||||||
.where(eq(productInfo.isSuspended, true))
|
.where(eq(productSkus.isSuspended, true))
|
||||||
|
|
||||||
return suspendedProducts.map(sp => sp.id)
|
return suspendedSkus.map(sp => sp.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get next delivery date for a product (with capacity check)
|
* Get next delivery date for a product (with capacity check)
|
||||||
* This version filters by both isActive AND isCapacityFull
|
* This version filters by both isActive AND isCapacityFull
|
||||||
*/
|
*/
|
||||||
export async function getNextDeliveryDateWithCapacity(productId: number): Promise<Date | null> {
|
export async function getNextDeliveryDateWithCapacity(skuId: number): Promise<Date | null> {
|
||||||
const slots = await db.query.deliverySlotInfo.findMany({
|
const slots = await db.query.deliverySlotInfo.findMany({
|
||||||
where: and(
|
where: and(
|
||||||
eq(deliverySlotInfo.isActive, true),
|
eq(deliverySlotInfo.isActive, true),
|
||||||
|
|
@ -244,10 +229,9 @@ export async function getNextDeliveryDateWithCapacity(productId: number): Promis
|
||||||
orderBy: desc(deliverySlotInfo.deliveryTime),
|
orderBy: desc(deliverySlotInfo.deliveryTime),
|
||||||
})
|
})
|
||||||
|
|
||||||
// Find the first slot that contains this product
|
|
||||||
for (const slot of slots) {
|
for (const slot of slots) {
|
||||||
const productIds = slot.productIds || []
|
const skuIds = slot.skuIds || []
|
||||||
if (productIds.includes(productId)) {
|
if (skuIds.includes(skuId)) {
|
||||||
return slot.deliveryTime
|
return slot.deliveryTime
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { db } from '../db/db_index'
|
import { db } from '../db/db_index'
|
||||||
import { deliverySlotInfo, productInfo } from '../db/schema'
|
import { deliverySlotInfo, productSkus } from '../db/schema'
|
||||||
import { asc, eq } from 'drizzle-orm'
|
import { asc, eq } from 'drizzle-orm'
|
||||||
import type { InferSelectModel } from 'drizzle-orm'
|
import type { InferSelectModel } from 'drizzle-orm'
|
||||||
import type { UserDeliverySlot, UserSlotAvailability } from '@packages/shared'
|
import type { UserDeliverySlot, UserSlotAvailability } from '@packages/shared'
|
||||||
|
|
@ -27,20 +27,17 @@ export async function getActiveSlotsList(): Promise<UserDeliverySlot[]> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getProductAvailability(): Promise<UserSlotAvailability[]> {
|
export async function getProductAvailability(): Promise<UserSlotAvailability[]> {
|
||||||
const products = await db
|
const skus = await db.query.productSkus.findMany({
|
||||||
.select({
|
where: eq(productSkus.isSuspended, false),
|
||||||
id: productInfo.id,
|
with: {
|
||||||
name: productInfo.name,
|
product: { columns: { name: true } },
|
||||||
isOutOfStock: productInfo.isOutOfStock,
|
},
|
||||||
isFlashAvailable: productInfo.isFlashAvailable,
|
})
|
||||||
})
|
|
||||||
.from(productInfo)
|
|
||||||
.where(eq(productInfo.isSuspended, false))
|
|
||||||
|
|
||||||
return products.map((product) => ({
|
return skus.map((sku) => ({
|
||||||
id: product.id,
|
id: sku.id,
|
||||||
name: product.name,
|
name: sku.product?.name ?? 'Unknown',
|
||||||
isOutOfStock: product.isOutOfStock,
|
isOutOfStock: sku.isOutOfStock,
|
||||||
isFlashAvailable: product.isFlashAvailable,
|
isFlashAvailable: sku.isFlashAvailable,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,10 @@
|
||||||
import { db } from '../db/db_index'
|
import { db } from '../db/db_index'
|
||||||
import { productInfo, storeInfo, units } from '../db/schema'
|
import { productInfo, productSkus, storeInfo } from '../db/schema'
|
||||||
import { and, eq, sql } from 'drizzle-orm'
|
import { and, eq, inArray, sql } from 'drizzle-orm'
|
||||||
import type { InferSelectModel } from 'drizzle-orm'
|
import type { InferSelectModel } from 'drizzle-orm'
|
||||||
import type { UserStoreDetailData, UserStoreProductData, UserStoreSummaryData, StoreSummary } from '@packages/shared'
|
import type { UserStoreDetailData, UserStoreProductData, UserStoreSummaryData, StoreSummary } from '@packages/shared'
|
||||||
|
|
||||||
type StoreRow = InferSelectModel<typeof storeInfo>
|
type StoreRow = InferSelectModel<typeof storeInfo>
|
||||||
type StoreProductRow = {
|
|
||||||
id: number
|
|
||||||
name: string
|
|
||||||
shortDescription: string | null
|
|
||||||
price: string | null
|
|
||||||
marketPrice: string | null
|
|
||||||
images: unknown
|
|
||||||
isOutOfStock: boolean
|
|
||||||
incrementStep: number
|
|
||||||
unitShortNotation: string
|
|
||||||
productQuantity: number
|
|
||||||
}
|
|
||||||
|
|
||||||
const getStringArray = (value: unknown): string[] | null => {
|
const getStringArray = (value: unknown): string[] | null => {
|
||||||
if (!Array.isArray(value)) return null
|
if (!Array.isArray(value)) return null
|
||||||
|
|
@ -24,32 +12,59 @@ const getStringArray = (value: unknown): string[] | null => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getStoreSummaries(): Promise<UserStoreSummaryData[]> {
|
export async function getStoreSummaries(): Promise<UserStoreSummaryData[]> {
|
||||||
|
// Count SKUs per store, filtering by suspended SKUs
|
||||||
const storesData = await db
|
const storesData = await db
|
||||||
.select({
|
.select({
|
||||||
id: storeInfo.id,
|
id: storeInfo.id,
|
||||||
name: storeInfo.name,
|
name: storeInfo.name,
|
||||||
description: storeInfo.description,
|
description: storeInfo.description,
|
||||||
imageUrl: storeInfo.imageUrl,
|
imageUrl: storeInfo.imageUrl,
|
||||||
productCount: sql<number>`count(${productInfo.id})`.as('productCount'),
|
productCount: sql<number>`count(${productSkus.id})`.as('productCount'),
|
||||||
})
|
})
|
||||||
.from(storeInfo)
|
.from(storeInfo)
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
productInfo,
|
productInfo,
|
||||||
and(eq(productInfo.storeId, storeInfo.id), eq(productInfo.isSuspended, false))
|
eq(productInfo.storeId, storeInfo.id)
|
||||||
|
)
|
||||||
|
.leftJoin(
|
||||||
|
productSkus,
|
||||||
|
and(
|
||||||
|
eq(productSkus.productId, productInfo.id),
|
||||||
|
eq(productSkus.isSuspended, false)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
.groupBy(storeInfo.id)
|
.groupBy(storeInfo.id)
|
||||||
|
|
||||||
const storesWithDetails = await Promise.all(
|
const storesWithDetails = await Promise.all(
|
||||||
storesData.map(async (store) => {
|
storesData.map(async (store) => {
|
||||||
const sampleProducts = await db
|
let sampleProducts: any[] = []
|
||||||
.select({
|
// Get sample SKUs from this store
|
||||||
id: productInfo.id,
|
if (store.productCount > 0) {
|
||||||
name: productInfo.name,
|
const storeProductIds = await db
|
||||||
images: productInfo.images,
|
.select({ id: productInfo.id })
|
||||||
})
|
.from(productInfo)
|
||||||
.from(productInfo)
|
.where(eq(productInfo.storeId, store.id))
|
||||||
.where(and(eq(productInfo.storeId, store.id), eq(productInfo.isSuspended, false)))
|
|
||||||
.limit(3)
|
const productIdArr = storeProductIds.map((p) => p.id)
|
||||||
|
if (productIdArr.length > 0) {
|
||||||
|
const skus = await db.query.productSkus.findMany({
|
||||||
|
where: and(
|
||||||
|
inArray(productSkus.productId, productIdArr),
|
||||||
|
eq(productSkus.isSuspended, false)
|
||||||
|
),
|
||||||
|
with: {
|
||||||
|
product: { columns: { name: true } },
|
||||||
|
},
|
||||||
|
columns: { id: true, images: true, name: true },
|
||||||
|
limit: 3,
|
||||||
|
})
|
||||||
|
sampleProducts = skus.map((sku) => ({
|
||||||
|
id: sku.id,
|
||||||
|
name: sku.product?.name ?? sku.name ?? 'Unknown',
|
||||||
|
images: getStringArray(sku.images),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: store.id,
|
id: store.id,
|
||||||
|
|
@ -57,11 +72,7 @@ export async function getStoreSummaries(): Promise<UserStoreSummaryData[]> {
|
||||||
description: store.description ?? null,
|
description: store.description ?? null,
|
||||||
imageUrl: store.imageUrl ?? null,
|
imageUrl: store.imageUrl ?? null,
|
||||||
productCount: store.productCount || 0,
|
productCount: store.productCount || 0,
|
||||||
sampleProducts: sampleProducts.map((product) => ({
|
sampleProducts,
|
||||||
id: product.id,
|
|
||||||
name: product.name,
|
|
||||||
images: getStringArray(product.images),
|
|
||||||
})),
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
@ -84,36 +95,42 @@ export async function getStoreDetail(storeId: number): Promise<UserStoreDetailDa
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const productsData = await db
|
const storeProductIds = await db
|
||||||
.select({
|
.select({ id: productInfo.id })
|
||||||
id: productInfo.id,
|
|
||||||
name: productInfo.name,
|
|
||||||
shortDescription: productInfo.shortDescription,
|
|
||||||
price: productInfo.price,
|
|
||||||
marketPrice: productInfo.marketPrice,
|
|
||||||
images: productInfo.images,
|
|
||||||
isOutOfStock: productInfo.isOutOfStock,
|
|
||||||
incrementStep: productInfo.incrementStep,
|
|
||||||
unitShortNotation: units.shortNotation,
|
|
||||||
productQuantity: productInfo.productQuantity,
|
|
||||||
})
|
|
||||||
.from(productInfo)
|
.from(productInfo)
|
||||||
.innerJoin(units, eq(productInfo.unitId, units.id))
|
.where(eq(productInfo.storeId, storeId))
|
||||||
.where(and(eq(productInfo.storeId, storeId), eq(productInfo.isSuspended, false)))
|
|
||||||
|
|
||||||
const products = productsData.map((product: StoreProductRow): UserStoreProductData => ({
|
const productIdArr = storeProductIds.map((p) => p.id)
|
||||||
id: product.id,
|
|
||||||
name: product.name,
|
const skus = productIdArr.length > 0
|
||||||
shortDescription: product.shortDescription ?? null,
|
? await db.query.productSkus.findMany({
|
||||||
price: String(product.price ?? '0'),
|
where: and(
|
||||||
marketPrice: product.marketPrice ? String(product.marketPrice) : null,
|
inArray(productSkus.productId, productIdArr),
|
||||||
incrementStep: product.incrementStep,
|
eq(productSkus.isSuspended, false)
|
||||||
unit: product.unitShortNotation,
|
),
|
||||||
unitNotation: product.unitShortNotation,
|
with: {
|
||||||
images: getStringArray(product.images),
|
product: true,
|
||||||
isOutOfStock: product.isOutOfStock,
|
features: true,
|
||||||
productQuantity: product.productQuantity,
|
},
|
||||||
}))
|
})
|
||||||
|
: []
|
||||||
|
|
||||||
|
const products: UserStoreProductData[] = skus.map((sku) => {
|
||||||
|
const features = sku.features || []
|
||||||
|
return {
|
||||||
|
id: sku.id,
|
||||||
|
name: sku.product?.name ?? 'Unknown',
|
||||||
|
shortDescription: sku.product?.shortDescription ?? null,
|
||||||
|
price: String(sku.price ?? '0'),
|
||||||
|
marketPrice: sku.marketPrice ? String(sku.marketPrice) : null,
|
||||||
|
incrementStep: sku.product?.incrementStep ?? 1,
|
||||||
|
unit: features.map((f) => f.featureValue).join(' '),
|
||||||
|
unitNotation: features.map((f) => f.featureValue).join(' '),
|
||||||
|
images: getStringArray(sku.images),
|
||||||
|
isOutOfStock: sku.isOutOfStock,
|
||||||
|
productQuantity: 1,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
store: {
|
store: {
|
||||||
|
|
@ -126,10 +143,6 @@ export async function getStoreDetail(storeId: number): Promise<UserStoreDetailDa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get simple store summary (id, name, description only)
|
|
||||||
* Used for common API endpoints
|
|
||||||
*/
|
|
||||||
export async function getStoresSummary(): Promise<StoreSummary[]> {
|
export async function getStoresSummary(): Promise<StoreSummary[]> {
|
||||||
return db.query.storeInfo.findMany({
|
return db.query.storeInfo.findMany({
|
||||||
columns: {
|
columns: {
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,7 @@ export interface UserBanner {
|
||||||
name: string;
|
name: string;
|
||||||
imageUrl: string;
|
imageUrl: string;
|
||||||
description: string | null;
|
description: string | null;
|
||||||
productIds: number[] | null;
|
skuIds: number[] | null;
|
||||||
redirectUrl: string | null;
|
redirectUrl: string | null;
|
||||||
serialNum: number | null;
|
serialNum: number | null;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue