freshyo/apps/admin-ui/app/(drawer)/slots/slot-details.tsx
2026-01-24 00:13:15 +05:30

245 lines
No EOL
10 KiB
TypeScript

import React, { useState } from 'react';
import { View, ScrollView, TouchableOpacity, Alert, Share } from 'react-native';
import { theme, AppContainer, MyText, tw, MyTouchableOpacity, BottomDialog } from 'common-ui';
import { trpc } from '../../../src/trpc-client';
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
import dayjs from "dayjs";
import { useRouter, useLocalSearchParams } from 'expo-router';
import { LinearGradient } from 'expo-linear-gradient';
export default function SlotDetails() {
const router = useRouter();
const { slotId } = useLocalSearchParams();
const { data: slotData, isLoading, error } = trpc.admin.slots.getSlotById.useQuery({
id: parseInt(slotId as string),
});
const slot = slotData?.slot;
const products = slot?.products || [];
const vendorSnippets = slot?.vendorSnippets || [];
// Dialog state for snippet products
const [dialogOpen, setDialogOpen] = useState(false);
const [dialogProducts, setDialogProducts] = useState<any[]>([]);
if (isLoading) {
return (
<AppContainer>
<View style={tw`flex-1 justify-center items-center`}>
<MyText style={tw`text-gray-600`}>Loading slot details...</MyText>
</View>
</AppContainer>
);
}
if (error || !slot) {
return (
<AppContainer>
<View style={tw`flex-1 justify-center items-center`}>
<MyText style={tw`text-red-600`}>Error loading slot details</MyText>
</View>
</AppContainer>
);
}
return (
<View style={tw`flex-1 bg-white px-4 relative`}>
<ScrollView
style={tw`flex-1`}
contentContainerStyle={tw`pb-32`}
showsVerticalScrollIndicator={false}
>
{/* Header */}
<View style={tw`mb-6`}>
<MyText style={tw`text-2xl font-black text-gray-900 mb-2`}>
Slot #{slot.id}
</MyText>
<View style={tw`flex-row items-center justify-between`}>
<View style={tw`flex-1`}>
<MyText style={tw`text-sm text-gray-600 mb-1`}>
Delivery: {dayjs(slot.deliveryTime).format('MMM DD, YYYY hh:mm A')}
</MyText>
<MyText style={tw`text-sm text-gray-600`}>
Freeze: {dayjs(slot.freezeTime).format('MMM DD, YYYY hh:mm A')}
</MyText>
</View>
<View style={tw`px-3 py-1 rounded-full ${slot.isActive ? 'bg-green-100' : 'bg-red-100'}`}>
<MyText style={tw`text-xs font-bold ${slot.isActive ? 'text-green-700' : 'text-red-700'}`}>
{slot.isActive ? 'Active' : 'Inactive'}
</MyText>
</View>
</View>
</View>
{/* Vendor Snippets Section */}
<View style={tw`mb-8`}>
<View style={tw`flex-row items-center justify-between mb-4`}>
<MyText style={tw`text-lg font-bold text-gray-900`}>Vendor Snippets</MyText>
<View style={tw`bg-gray-100 px-2 py-1 rounded-full`}>
<MyText style={tw`text-xs font-medium text-gray-600`}>{vendorSnippets.length} snippets</MyText>
</View>
</View>
{vendorSnippets.length === 0 ? (
<View style={tw`bg-gray-50 p-6 rounded-lg items-center`}>
<MaterialIcons name="code" size={32} color="#9CA3AF" />
<MyText style={tw`text-gray-500 text-center mt-2`}>No vendor snippets for this slot</MyText>
</View>
) : (
<View style={tw`space-y-2`}>
{vendorSnippets.map((snippet, index) => {
const isExpired = snippet.validTill && dayjs(snippet.validTill).isBefore(dayjs());
return (
<View key={snippet.id} style={tw`bg-gray-50 p-4 rounded-lg`}>
<View style={tw`flex-row items-center justify-between mb-3`}>
<View style={tw`flex-1`}>
<MyText style={tw`font-bold text-gray-900`} numberOfLines={1}>
{snippet.snippetCode}
</MyText>
</View>
<View style={tw`flex-row items-center`}>
<View style={tw`px-2 py-1 rounded-full mr-2 ${isExpired ? 'bg-red-100' : 'bg-green-100'}`}>
<MyText style={tw`text-xs font-medium ${isExpired ? 'text-red-700' : 'text-green-700'}`}>
{isExpired ? 'Expired' : 'Active'}
</MyText>
</View>
</View>
</View>
<View style={tw`flex-row items-center justify-between`}>
<View style={tw`flex-row items-center`}>
<TouchableOpacity
onPress={() => {
const snippetProducts = products.filter(p => snippet.productIds.includes(p.id));
setDialogProducts(snippetProducts);
setDialogOpen(true);
}}
style={tw`flex-row items-center mr-4`}
>
<MaterialIcons name="shopping-bag" size={14} color="#94A3B8" />
<MyText style={tw`text-xs font-medium text-blue-600 ml-1 underline`}>
{snippet.productIds.length} products
</MyText>
</TouchableOpacity>
<MyTouchableOpacity
onPress={async () => {
try {
await Share.share({
message: snippet.accessUrl,
});
} catch (error) {
Alert.alert('Error', 'Failed to share link');
}
}}
style={tw`flex-row items-center`}
>
<MaterialIcons name="link" size={14} color={theme.colors.brand500} />
<MyText style={tw`text-xs font-medium text-brand600 ml-1`}>
Share
</MyText>
</MyTouchableOpacity>
</View>
{snippet.validTill && (
<View style={tw`flex-row items-center`}>
<MaterialIcons name="schedule" size={14} color="#94A3B8" />
<MyText style={tw`text-xs font-medium text-gray-600 ml-1`}>
Expires {dayjs(snippet.validTill).format('MMM DD')}
</MyText>
</View>
)}
</View>
{snippet.createdAt && (
<View style={tw`mt-2 pt-2 border-t border-gray-200`}>
<MyText style={tw`text-xs text-gray-500`}>
Created {dayjs(snippet.createdAt).format('MMM DD, YYYY')}
</MyText>
</View>
)}
</View>
);
})}
</View>
)}
</View>
{/* Products Section */}
<View style={tw`mb-8`}>
<View style={tw`flex-row items-center justify-between mb-4`}>
<MyText style={tw`text-lg font-bold text-gray-900`}>Products</MyText>
<View style={tw`bg-gray-100 px-2 py-1 rounded-full`}>
<MyText style={tw`text-xs font-medium text-gray-600`}>{products.length} items</MyText>
</View>
</View>
{products.length === 0 ? (
<View style={tw`bg-gray-50 p-6 rounded-lg items-center`}>
<MaterialIcons name="inventory" size={32} color="#9CA3AF" />
<MyText style={tw`text-gray-500 text-center mt-2`}>No products in this slot</MyText>
</View>
) : (
<View style={tw`flex-row flex-wrap gap-2 py-2`}>
{products.map((product) => (
<View key={product.id} style={tw`bg-blue-50 px-3 py-2 rounded-full border border-blue-200`}>
<MyText style={tw`text-sm font-medium text-blue-700`} numberOfLines={1}>
{product.name}
</MyText>
</View>
))}
</View>
)}
</View>
</ScrollView>
{/* FAB for Edit Slot */}
<MyTouchableOpacity
onPress={() => router.push(`/edit-slot/${slot.id}` as any)}
activeOpacity={0.95}
style={{ position: 'absolute', bottom: 32, right: 24, zIndex: 100 }}
>
<LinearGradient
colors={['#F83758', '#E91E63']}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
style={tw`w-16 h-16 rounded-[24px] items-center justify-center shadow-lg shadow-pink300`}
>
<MaterialIcons name="edit" size={24} color="white" />
</LinearGradient>
</MyTouchableOpacity>
<BottomDialog open={dialogOpen} onClose={() => setDialogOpen(false)}>
<View style={tw`py-4`}>
<MyText style={tw`text-lg font-semibold mb-4 text-center`}>Snippet Products</MyText>
<ScrollView style={tw`max-h-80`}>
{dialogProducts.length === 0 ? (
<View style={tw`py-8 px-4 items-center`}>
<MyText style={tw`text-gray-500 text-center`}>
No products found for this snippet
</MyText>
</View>
) : (
dialogProducts.map(product => (
<View key={product.id} style={tw`py-3 px-4 border-b border-gray-100`}>
<MyText style={tw`text-base font-medium text-gray-900`}>
{product.name}
</MyText>
{product.shortDescription && (
<MyText style={tw`text-sm text-gray-600 mt-1`}>
{product.shortDescription}
</MyText>
)}
</View>
))
)}
</ScrollView>
</View>
</BottomDialog>
</View>
);
}