This commit is contained in:
shafi54 2026-02-01 21:01:05 +05:30
parent edbc506062
commit 86de06078f
9 changed files with 3749 additions and 36 deletions

View file

@ -1,22 +1,43 @@
import React from 'react';
import { View } from 'react-native';
import { View, Text } from 'react-native';
import { AppContainer } from 'common-ui';
import SlotForm from '../../../components/SlotForm';
import { useRouter } from 'expo-router';
import { useRouter, useLocalSearchParams } from 'expo-router';
import { trpc } from '../../../src/trpc-client';
export default function AddSlot() {
const router = useRouter();
const { baseslot } = useLocalSearchParams();
const baseSlotId = baseslot ? parseInt(baseslot as string) : null;
const { refetch } = trpc.admin.slots.getAll.useQuery();
const { data: baseSlotData, isLoading } = trpc.admin.slots.getSlotById.useQuery(
{ id: baseSlotId! },
{ enabled: !!baseSlotId }
);
const handleSlotAdded = () => {
refetch();
router.back();
};
if (isLoading && baseSlotId) {
return (
<AppContainer>
<SlotForm onSlotAdded={handleSlotAdded} />
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Loading base slot...</Text>
</View>
</AppContainer>
);
}
return (
<AppContainer>
<SlotForm
onSlotAdded={handleSlotAdded}
initialProductIds={baseSlotData?.slot?.products?.map(p => p.id) || []}
initialGroupIds={baseSlotData?.slot?.groupIds || []}
/>
</AppContainer>
);
}

View file

@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { View, TouchableOpacity, FlatList } from 'react-native';
import { MaterialCommunityIcons, Entypo } from '@expo/vector-icons';
import { View, TouchableOpacity, FlatList, Alert } from 'react-native';
import { AppContainer, MyText, tw, MyFlatList , BottomDialog, MyTouchableOpacity } from 'common-ui';
import { trpc } from '../../../src/trpc-client';
import { useRouter } from 'expo-router';
@ -20,6 +20,7 @@ const SlotItemComponent: React.FC<SlotItemProps> = ({
setDialogProducts,
setDialogOpen,
}) => {
const [menuOpen, setMenuOpen] = useState(false);
const slotProducts = slot.products?.map((p: any) => p.name).filter(Boolean) || [];
const displayProducts = slotProducts.slice(0, 2).join(', ');
@ -57,9 +58,44 @@ const SlotItemComponent: React.FC<SlotItemProps> = ({
<View style={tw`px-3 py-1 rounded-full ${statusColor.split(' ')[0]}`}>
<MyText style={tw`text-xs font-bold ${statusColor.split(' ')[1]}`}>{statusText}</MyText>
</View>
<TouchableOpacity
onPress={() => setMenuOpen(true)}
style={tw`ml-2 p-1`}
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
>
<Entypo name="dots-three-vertical" size={20} color="#9CA3AF" />
</TouchableOpacity>
</View>
</View>
{/* Replicate Menu Dialog */}
<BottomDialog open={menuOpen} onClose={() => setMenuOpen(false)}>
<View style={tw`p-4`}>
<MyText style={tw`text-lg font-bold mb-4`}>Slot #{slot.id} Actions</MyText>
<TouchableOpacity
onPress={() => {
setMenuOpen(false);
router.push(`/add-slot?baseslot=${slot.id}` as any);
}}
style={tw`py-4 border-b border-gray-200`}
>
<View style={tw`flex-row items-center`}>
<MaterialCommunityIcons name="content-copy" size={20} color="#4B5563" style={tw`mr-3`} />
<MyText style={tw`text-base text-gray-800`}>Replicate Slot</MyText>
</View>
</TouchableOpacity>
<TouchableOpacity
onPress={() => setMenuOpen(false)}
style={tw`py-4 mt-2`}
>
<View style={tw`flex-row items-center`}>
<MaterialCommunityIcons name="close" size={20} color="#EF4444" style={tw`mr-3`} />
<MyText style={tw`text-base text-red-500`}>Cancel</MyText>
</View>
</TouchableOpacity>
</View>
</BottomDialog>
{/* Divider */}
<View style={tw`h-[1px] bg-gray-100 mb-4`} />

View file

@ -20,6 +20,7 @@ interface SlotFormProps {
initialIsActive?: boolean;
slotId?: number;
initialProductIds?: number[];
initialGroupIds?: number[];
}
export default function SlotForm({
@ -29,13 +30,26 @@ export default function SlotForm({
initialIsActive = true,
slotId,
initialProductIds = [],
initialGroupIds = [],
}: SlotFormProps) {
const { data: slotData } = trpc.admin.slots.getSlotById.useQuery(
{ id: slotId! },
{ enabled: !!slotId }
);
const vendorSnippetsFromSlot = (slotData?.slot?.vendorSnippets || []).map((snippet: any) => ({
name: snippet.name || '',
groupIds: snippet.groupIds || [],
productIds: snippet.productIds || [],
validTill: snippet.validTill || undefined,
})) as VendorSnippet[];
const initialValues = {
deliveryTime: initialDeliveryTime || null,
freezeTime: initialFreezeTime || null,
selectedGroupIds: [] as number[],
selectedProductIds: initialProductIds,
vendorSnippetList: [] as VendorSnippet[],
deliveryTime: initialDeliveryTime || (slotData?.slot?.deliveryTime ? new Date(slotData.slot.deliveryTime) : null),
freezeTime: initialFreezeTime || (slotData?.slot?.freezeTime ? new Date(slotData.slot.freezeTime) : null),
selectedGroupIds: initialGroupIds.length > 0 ? initialGroupIds : (slotData?.slot?.groupIds || []),
selectedProductIds: initialProductIds.length > 0 ? initialProductIds : (slotData?.slot?.products?.map((p: any) => p.id) || []),
vendorSnippetList: vendorSnippetsFromSlot,
};
const { mutate: createSlot, isPending: isCreating } = trpc.admin.slots.createSlot.useMutation();
@ -63,21 +77,15 @@ export default function SlotForm({
deliveryTime: values.deliveryTime.toISOString(),
freezeTime: values.freezeTime.toISOString(),
isActive: initialIsActive,
groupIds: values.selectedGroupIds,
productIds: values.selectedProductIds,
vendorSnippets: values.vendorSnippetList.map(snippet => ({
vendorSnippets: values.vendorSnippetList.map((snippet: VendorSnippet) => ({
name: snippet.name,
productIds: snippet.productIds,
validTill: snippet.validTill,
})),
};
console.log({snippetList: values.vendorSnippetList})
values.vendorSnippetList.forEach((snippet, index) => {
console.log({snippet})
});
if (isEditMode && slotId) {
updateSlot(
@ -160,7 +168,7 @@ export default function SlotForm({
{({ push, remove }) => (
<View style={tw`mb-4`}>
<Text style={tw`text-lg font-semibold mb-4`}>Vendor Snippets</Text>
{values.vendorSnippetList.map((snippet, index) => (
{values.vendorSnippetList.map((snippet: VendorSnippet, index: number) => (
<View key={index} style={tw`bg-gray-50 p-4 rounded-lg mb-4`}>
<View style={tw`mb-4`}>
<MyTextInput

View file

@ -0,0 +1 @@
ALTER TABLE "mf"."delivery_slot_info" ADD COLUMN "group_ids" jsonb;

File diff suppressed because it is too large Load diff

View file

@ -491,6 +491,13 @@
"when": 1769718702463,
"tag": "0069_violet_smiling_tiger",
"breakpoints": true
},
{
"idx": 70,
"version": "7",
"when": 1769958949864,
"tag": "0070_known_ares",
"breakpoints": true
}
]
}

View file

@ -192,6 +192,7 @@ export const deliverySlotInfo = mf.table('delivery_slot_info', {
isActive: boolean('is_active').notNull().default(true),
isFlash: boolean('is_flash').notNull().default(false),
deliverySequence: jsonb('delivery_sequence').$defaultFn(() => {}),
groupIds: jsonb('group_ids').$defaultFn(() => []),
});
export const vendorSnippets = mf.table('vendor_snippets', {

View file

@ -2,7 +2,7 @@ import { router, protectedProcedure } from "../trpc-index";
import { TRPCError } from "@trpc/server";
import { z } from "zod";
import { db } from "../../db/db_index";
import { deliverySlotInfo, productSlots, productInfo, vendorSnippets } from "../../db/schema";
import { deliverySlotInfo, productSlots, productInfo, vendorSnippets, productGroupInfo } from "../../db/schema";
import { eq, inArray, and, desc } from "drizzle-orm";
import { ApiError } from "../../lib/api-error";
import { appUrl } from "../../lib/env-exporter";
@ -26,6 +26,7 @@ const createSlotSchema = z.object({
productIds: z.array(z.number().int().positive()).min(1),
validTill: z.string().optional(),
})).optional(),
groupIds: z.array(z.number()).optional(),
});
const getSlotByIdSchema = z.object({
@ -43,6 +44,7 @@ const updateSlotSchema = z.object({
productIds: z.array(z.number().int().positive()).min(1),
validTill: z.string().optional(),
})).optional(),
groupIds: z.array(z.number()).optional(),
});
const deleteSlotSchema = z.object({
@ -229,7 +231,7 @@ export const slotsRouter = router({
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
}
const { deliveryTime, freezeTime, isActive, productIds, vendorSnippets: snippets } = input;
const { deliveryTime, freezeTime, isActive, productIds, vendorSnippets: snippets, groupIds } = input;
// Validate required fields
if (!deliveryTime || !freezeTime) {
@ -244,6 +246,7 @@ export const slotsRouter = router({
deliveryTime: new Date(deliveryTime),
freezeTime: new Date(freezeTime),
isActive: isActive !== undefined ? isActive : true,
groupIds: groupIds !== undefined ? groupIds : [],
})
.returning();
@ -348,6 +351,7 @@ export const slotsRouter = router({
slot: {
...slot,
deliverySequence: slot.deliverySequence as number[],
groupIds: slot.groupIds as number[],
products: slot.productSlots.map((ps) => ps.product),
vendorSnippets: slot.vendorSnippets?.map(snippet => ({
...snippet,
@ -364,12 +368,22 @@ export const slotsRouter = router({
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
}
try{
const { id, deliveryTime, freezeTime, isActive, productIds, vendorSnippets: snippets } = input;
const { id, deliveryTime, freezeTime, isActive, productIds, vendorSnippets: snippets, groupIds } = input;
if (!deliveryTime || !freezeTime) {
throw new ApiError("Delivery time and orders close time are required", 400);
}
// Filter groupIds to only include valid (existing) groups
let validGroupIds = groupIds;
if (groupIds && groupIds.length > 0) {
const existingGroups = await db.query.productGroupInfo.findMany({
where: inArray(productGroupInfo.id, groupIds),
columns: { id: true },
});
validGroupIds = existingGroups.map(g => g.id);
}
return await db.transaction(async (tx) => {
const [updatedSlot] = await tx
.update(deliverySlotInfo)
@ -377,6 +391,7 @@ export const slotsRouter = router({
deliveryTime: new Date(deliveryTime),
freezeTime: new Date(freezeTime),
isActive: isActive !== undefined ? isActive : true,
groupIds: validGroupIds !== undefined ? validGroupIds : [],
})
.where(eq(deliverySlotInfo.id, id))
.returning();

View file

@ -54,7 +54,7 @@ function AnimatedFlag({ flagTexture, isCelebrating, time }: { flagTexture: THREE
// Increase segmentation for better cloth simulation
geometry.dispose();
const newGeometry = new THREE.PlaneGeometry(1.5, 0.75, 24, 12);
const newGeometry = new THREE.PlaneGeometry(2.5, 1.25, 32, 16);
flagMeshRef.current.geometry = newGeometry;
originalPositions.current = new Float32Array(newGeometry.attributes.position.array);
}
@ -77,15 +77,15 @@ function AnimatedFlag({ flagTexture, isCelebrating, time }: { flagTexture: THREE
const y = originalPos[i + 1];
const z = originalPos[i + 2];
// Distance from flag pole
const distanceFromPole = (x + 0.75) / 1.5;
// Distance from flag pole (adjusted for larger flag width of 2.5)
const distanceFromPole = (x + 1.25) / 2.5;
// Multiple wave layers
const wave1 = Math.sin(elapsed * flutterSpeed * 0.5 + distanceFromPole * 4) * 0.15 * distanceFromPole * windStrength;
const wave2 = Math.sin(elapsed * flutterSpeed + distanceFromPole * 8 + y * 3) * 0.08 * distanceFromPole * windStrength;
const wave3 = Math.sin(elapsed * flutterSpeed * 2 + distanceFromPole * 12 + y * 5) * 0.04 * distanceFromPole * windStrength;
const turbulence = noise(elapsed * 2 + distanceFromPole * 6) * 0.06 * distanceFromPole * windStrength;
const verticalFlutter = Math.sin(elapsed * flutterSpeed * 0.7 + distanceFromPole * 3) * 0.05 * (1 - Math.abs(y) / 0.375) * distanceFromPole;
const verticalFlutter = Math.sin(elapsed * flutterSpeed * 0.7 + distanceFromPole * 3) * 0.05 * (1 - Math.abs(y) / 0.625) * distanceFromPole;
const zOffset = (wave1 + wave2 + wave3 + turbulence) * (0.3 + distanceFromPole * 0.7);
const yOffset = verticalFlutter * windStrength;
@ -103,8 +103,8 @@ function AnimatedFlag({ flagTexture, isCelebrating, time }: { flagTexture: THREE
});
return (
<mesh ref={flagMeshRef} position={[0.75, 1.5, 0]}>
<planeGeometry args={[1.5, 0.75, 24, 12]} />
<mesh ref={flagMeshRef} position={[1.25, 1.8, 0]}>
<planeGeometry args={[2.5, 1.25, 32, 16]} />
<meshStandardMaterial
map={flagTexture}
side={THREE.DoubleSide}
@ -731,23 +731,23 @@ function Character({ color, isCelebrating, delay = 0, type, hairColor = '#2D1B0E
</group>
</group>
{/* FLAG */}
{/* FLAG - Larger and more prominent */}
{holdsFlag && (
<group position={[0.4, 1.4, 0.15]} rotation={[0, 0, -0.25]}>
<group position={[0.5, 1.5, 0.2]} rotation={[0, 0, -0.15]}>
<group position={[0, 0, 0]}>
<mesh>
<sphereGeometry args={[0.1, 16, 16]} />
<sphereGeometry args={[0.12, 16, 16]} />
<meshToonMaterial color={skinTone} />
</mesh>
</group>
<mesh position={[0, 1.1, 0]}>
<cylinderGeometry args={[0.018, 0.018, 2.4, 12]} />
<mesh position={[0, 1.4, 0]}>
<cylinderGeometry args={[0.022, 0.022, 2.8, 12]} />
<meshToonMaterial color="#5d4037" />
</mesh>
<mesh position={[0, 2.3, 0]}>
<sphereGeometry args={[0.05, 16, 16]} />
<mesh position={[0, 2.8, 0]}>
<sphereGeometry args={[0.08, 16, 16]} />
<meshStandardMaterial color="#f1c40f" metalness={0.5} roughness={0.3} />
</mesh>