diff --git a/src/components/establishments/CommerceReservations.tsx b/src/components/establishments/CommerceReservations.tsx new file mode 100644 index 0000000..69ae0ec --- /dev/null +++ b/src/components/establishments/CommerceReservations.tsx @@ -0,0 +1,146 @@ +import React, { useState, useEffect } from 'react'; +import { Calendar, Users, Clock, CheckCircle, XCircle } from 'lucide-react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { useToast } from '@/hooks/use-toast'; +import { apiClient } from '@/services/adminApi'; + +const CommerceReservations = () => { + const [reservations, setReservations] = useState([]); + const [filter, setFilter] = useState('all'); + const { toast } = useToast(); + + useEffect(() => { + loadReservations(); + }, []); + + const loadReservations = async () => { + try { + const response = await apiClient.get('/commerce/reservations'); + setReservations(Array.isArray(response) ? response : (response as any)?.reservations || []); + } catch (error) { + console.error('Error loading reservations:', error); + } + }; + + const handleUpdateReservation = async (id: number, status: string) => { + try { + await apiClient.patch(`/commerce/reservations/${id}`, { status }); + toast({ title: 'Éxito', description: 'Reservación actualizada' }); + loadReservations(); + } catch (error) { + toast({ title: 'Error', description: 'No se pudo actualizar', variant: 'destructive' }); + } + }; + + const handleCancelReservation = async (id: number) => { + try { + await apiClient.patch(`/commerce/reservations/${id}/cancel`); + toast({ title: 'Éxito', description: 'Reservación cancelada' }); + loadReservations(); + } catch (error) { + toast({ title: 'Error', description: 'No se pudo cancelar', variant: 'destructive' }); + } + }; + + const filteredReservations = reservations.filter(res => + filter === 'all' || res.status === filter + ); + + const getStatusBadge = (status: string) => { + const variants: Record = { + pending: 'secondary', + confirmed: 'default', + cancelled: 'destructive', + completed: 'outline' + }; + return {status}; + }; + + return ( +
+
+

Gestión de Reservaciones

+ +
+ +
+ {filteredReservations.map((reservation) => ( + + +
+ +
+ + {reservation.establishment?.name || 'Establecimiento'} + +

+ Reserva #{reservation.id} +

+
+
+
+ {getStatusBadge(reservation.status)} + {reservation.status === 'pending' && ( + <> + + + + )} +
+
+ +
+
+ + {new Date(reservation.date).toLocaleDateString()} +
+
+ + {reservation.time} +
+
+ + {reservation.guests} personas +
+
+ {reservation.specialRequests && ( +

+ Notas: {reservation.specialRequests} +

+ )} +
+
+ ))} +
+
+ ); +}; + +export default CommerceReservations; diff --git a/src/components/establishments/EstablishmentsList.tsx b/src/components/establishments/EstablishmentsList.tsx new file mode 100644 index 0000000..ea0a6f9 --- /dev/null +++ b/src/components/establishments/EstablishmentsList.tsx @@ -0,0 +1,309 @@ +import React, { useState, useEffect } from 'react'; +import { Plus, Edit, Trash2, Store, MapPin, Phone, Globe } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; +import { Input } from '@/components/ui/input'; +import { Textarea } from '@/components/ui/textarea'; +import { Label } from '@/components/ui/label'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Badge } from '@/components/ui/badge'; +import { useToast } from '@/hooks/use-toast'; +import { apiClient } from '@/services/adminApi'; + +const EstablishmentsList = () => { + const [establishments, setEstablishments] = useState([]); + const [loading, setLoading] = useState(false); + const [editingId, setEditingId] = useState(null); + const [searchTerm, setSearchTerm] = useState(''); + const { toast } = useToast(); + + const [formData, setFormData] = useState({ + name: '', + description: '', + type: 'restaurant', + address: '', + phone: '', + email: '', + website: '', + category: '', + coordinates: { x: 0, y: 0 }, + openingHours: { + monday: '9:00-18:00', + tuesday: '9:00-18:00', + wednesday: '9:00-18:00', + thursday: '9:00-18:00', + friday: '9:00-18:00', + saturday: '9:00-18:00', + sunday: 'Closed' + } + }); + + const loadEstablishments = async () => { + setLoading(true); + try { + const response = await apiClient.get('/commerce/establishments'); + setEstablishments(Array.isArray(response) ? response : (response as any)?.establishments || []); + } catch (error) { + console.error('Error loading establishments:', error); + toast({ title: 'Error', description: 'No se pudieron cargar los establecimientos', variant: 'destructive' }); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + loadEstablishments(); + }, []); + + const handleSubmit = async () => { + try { + if (editingId) { + await apiClient.patch(`/commerce/establishments/${editingId}`, formData); + toast({ title: 'Éxito', description: 'Establecimiento actualizado correctamente' }); + } else { + await apiClient.post('/commerce/establishments', formData); + toast({ title: 'Éxito', description: 'Establecimiento creado correctamente' }); + } + resetForm(); + loadEstablishments(); + } catch (error: any) { + toast({ title: 'Error', description: error?.message || 'No se pudo guardar el establecimiento', variant: 'destructive' }); + } + }; + + const handleDelete = async (id: number) => { + if (!confirm('¿Estás seguro de eliminar este establecimiento?')) return; + try { + await apiClient.delete(`/commerce/establishments/${id}`); + toast({ title: 'Éxito', description: 'Establecimiento eliminado correctamente' }); + loadEstablishments(); + } catch (error) { + toast({ title: 'Error', description: 'No se pudo eliminar el establecimiento', variant: 'destructive' }); + } + }; + + const handleEdit = (establishment: any) => { + setEditingId(establishment.id); + setFormData({ + name: establishment.name || '', + description: establishment.description || '', + type: establishment.type || 'restaurant', + address: establishment.address || '', + phone: establishment.phone || '', + email: establishment.email || '', + website: establishment.website || '', + category: establishment.category || '', + coordinates: establishment.coordinates || { x: 0, y: 0 }, + openingHours: establishment.openingHours || formData.openingHours + }); + }; + + const resetForm = () => { + setEditingId(null); + setFormData({ + name: '', + description: '', + type: 'restaurant', + address: '', + phone: '', + email: '', + website: '', + category: '', + coordinates: { x: 0, y: 0 }, + openingHours: { + monday: '9:00-18:00', + tuesday: '9:00-18:00', + wednesday: '9:00-18:00', + thursday: '9:00-18:00', + friday: '9:00-18:00', + saturday: '9:00-18:00', + sunday: 'Closed' + } + }); + }; + + const filteredEstablishments = establishments.filter(est => + est.name?.toLowerCase().includes(searchTerm.toLowerCase()) || + est.category?.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + return ( +
+
+
+ setSearchTerm(e.target.value)} + /> +
+ + + + + + + {editingId ? 'Editar' : 'Crear'} Establecimiento + +
+
+
+ + setFormData({ ...formData, name: e.target.value })} + placeholder="Nombre del establecimiento" + /> +
+
+ + +
+
+
+ +