import React, { useState } from 'react'; import { View, ScrollView, TouchableOpacity, Alert, Image, RefreshControl } from 'react-native'; import { AppContainer, MyText, tw, MyTouchableOpacity } from 'common-ui'; import { trpc } from '../../../src/trpc-client'; import MaterialIcons from '@expo/vector-icons/MaterialIcons'; import { useRouter } from 'expo-router'; interface Banner { id: number; name: string; imageUrl: string; description: string | null; productIds: number[] | null; redirectUrl: string | null; serialNum: number | null; isActive: boolean; createdAt: string; lastUpdated: string; } export default function DashboardBanners() { const router = useRouter(); const [refreshing, setRefreshing] = useState(false); // Edit mode state const [editMode, setEditMode] = useState(false); const [selectedSlot, setSelectedSlot] = useState(null); const [slotAssignments, setSlotAssignments] = useState<{[slot: number]: number | null}>({}); const [originalAssignments, setOriginalAssignments] = useState<{[slot: number]: number | null}>({}); const [saving, setSaving] = useState(false); // Real API calls const { data: bannersData, isLoading, error, refetch } = trpc.admin.banner.getBanners.useQuery(); const deleteBannerMutation = trpc.admin.banner.deleteBanner.useMutation(); const updateBannerMutation = trpc.admin.banner.updateBanner.useMutation(); const emptySlotMutation = trpc.admin.banner.updateBanner.useMutation(); const banners = bannersData?.banners || []; // Initialize slot assignments when banners load React.useEffect(() => { const assignments: {[slot: number]: number | null} = {1: null, 2: null, 3: null, 4: null}; banners.forEach(banner => { if (banner.serialNum && banner.serialNum >= 1 && banner.serialNum <= 4) { assignments[banner.serialNum] = banner.id; } }); setSlotAssignments(assignments); setOriginalAssignments({...assignments}); }, [bannersData]); const onRefresh = async () => { setRefreshing(true); await refetch(); setRefreshing(false); }; // Slot and edit mode handlers const handleSlotClick = (slotNumber: number) => { setSelectedSlot(slotNumber); setEditMode(true); }; const handleBannerSelect = (bannerId: number) => { if (!editMode || selectedSlot === null) return; // Remove banner from any existing slot const newAssignments = {...slotAssignments}; Object.keys(newAssignments).forEach(slot => { if (newAssignments[parseInt(slot)] === bannerId) { newAssignments[parseInt(slot)] = null; } }); // Assign banner to selected slot newAssignments[selectedSlot] = bannerId; setSlotAssignments(newAssignments); }; const handleSave = async () => { setSaving(true); try { // Get banners that need to be updated const bannersToUpdate: {id: number, serialNum: number | null}[] = []; // Clear serial numbers for banners no longer in slots banners.forEach(banner => { const currentSlot = banner.serialNum; const assignedSlot = Object.keys(slotAssignments).find(slot => slotAssignments[parseInt(slot)] === banner.id ); if (currentSlot !== (assignedSlot ? parseInt(assignedSlot) : null)) { bannersToUpdate.push({ id: banner.id, serialNum: assignedSlot ? parseInt(assignedSlot) : null }); } }); // Update banners that gained slots Object.keys(slotAssignments).forEach(slot => { const slotNum = parseInt(slot); const bannerId = slotAssignments[slotNum]; if (bannerId) { const banner = banners.find(b => b.id === bannerId); if (banner && banner.serialNum !== slotNum) { bannersToUpdate.push({ id: bannerId, serialNum: slotNum }); } } }); // Execute updates await Promise.all( bannersToUpdate.map(({id, serialNum}) => updateBannerMutation.mutateAsync({ id, serialNum }) ) ); setOriginalAssignments({...slotAssignments}); setEditMode(false); setSelectedSlot(null); await refetch(); Alert.alert('Success', 'Slot assignments saved successfully'); } catch (error) { Alert.alert('Error', 'Failed to save slot assignments'); } finally { setSaving(false); } }; const handleCancel = () => { setSlotAssignments({...originalAssignments}); setEditMode(false); setSelectedSlot(null); }; const handleEmptySlot = async () => { if (!selectedSlot || !slotAssignments[selectedSlot]) return; const bannerId = slotAssignments[selectedSlot]; const banner = banners.find(b => b.id === bannerId); if (!banner) return; try { // Update banner's serialNum to null to empty the slot await emptySlotMutation.mutateAsync({ id: banner.id, serialNum: null }); // Update local state setSlotAssignments(prev => ({ ...prev, [selectedSlot]: null })); // Update original assignments for cancel functionality setOriginalAssignments(prev => ({ ...prev, [selectedSlot]: null })); Alert.alert('Success', `Slot ${selectedSlot} has been emptied`); } catch (error) { Alert.alert('Error', 'Failed to empty slot'); } }; // Helper function to get banner name by ID const getBannerNameById = (id: number | null) => { if (!id) return null; const banner = banners.find(b => b.id === id); return banner ? banner.name : null; }; const handleEdit = (banner: Banner) => { router.push(`/dashboard-banners/edit-banner/${banner.id}` as any); }; const handleDelete = (id: number) => { Alert.alert( 'Delete Banner', 'Are you sure you want to delete this banner?', [ { text: 'Cancel', style: 'cancel' }, { text: 'Delete', style: 'destructive', onPress: async () => { try { await deleteBannerMutation.mutateAsync({ id }); refetch(); Alert.alert('Success', 'Banner deleted'); } catch (error) { Alert.alert('Error', 'Failed to delete banner'); } } } ] ); }; const handleCreate = () => { router.push('/(drawer)/dashboard-banners/create-banner' as any); }; if (isLoading) { return ( Loading banners... ); } if (error) { return ( Error loading banners ); } return ( } > {/* Header */} {/* All Banners Add New Banner */} {/* Slots Row */} {editMode ? `Select banner for Slot ${selectedSlot}` : 'Banner Slots'} {[1, 2, 3, 4].map(slotNum => { const assignedBannerId = slotAssignments[slotNum]; const bannerName = getBannerNameById(assignedBannerId); const isSelected = editMode && selectedSlot === slotNum; return ( handleSlotClick(slotNum)} style={tw`flex-1 mx-1 p-3 rounded-lg border-2 ${ isSelected ? 'border-blue-500 bg-blue-50' : assignedBannerId ? 'border-green-300 bg-green-50' : 'border-gray-300 bg-white' }`} > Slot {slotNum} {bannerName || 'Empty'} ); })} {/* Action buttons in edit mode */} {editMode && ( {/* Save/Cancel buttons */} Cancel {saving ? 'Saving...' : 'Save Changes'} {/* Empty Slot button */} {emptySlotMutation.isPending ? 'Emptying...' : 'Empty Slot'} )} {/* Banners List */} {editMode && ( Tap a banner below to assign it to Slot {selectedSlot} )} {banners.length === 0 ? ( No Banners Yet Start by creating your first banner using the button above. ) : ( banners.map((banner) => { const isAssignedToSlot = Object.values(slotAssignments).includes(banner.id); const isAssignedToSelectedSlot = slotAssignments[selectedSlot || 0] === banner.id; const canInteract = editMode; return ( handleBannerSelect(banner.id) : undefined} disabled={!canInteract} style={tw`bg-white rounded-xl p-4 mb-3 shadow-sm border ${ canInteract && isAssignedToSelectedSlot ? 'border-blue-500 bg-blue-50' : canInteract && isAssignedToSlot ? 'border-green-300 bg-green-50' : canInteract ? 'border-gray-200' : 'border-gray-100' }`} > {banner.name} {canInteract && isAssignedToSelectedSlot && ( )} {canInteract && isAssignedToSlot && !isAssignedToSelectedSlot && ( )} {!editMode && ( <> handleEdit(banner)} style={tw`p-1 mr-1`}> handleDelete(banner.id)} style={tw`p-1`}> )} {banner.description && ( {banner.description} )} Created: {new Date(banner.createdAt).toLocaleDateString()} {banner.serialNum && banner.serialNum >= 1 && banner.serialNum <= 4 && ( Slot {banner.serialNum} )} ); }) )} {/* Floating Action Button */} ); }