245 lines
No EOL
10 KiB
TypeScript
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>
|
|
|
|
);
|
|
} |