diff --git a/src/App.tsx b/src/App.tsx index 16993aa..65a647d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -56,6 +56,8 @@ import PolReports from "./pages/dashboard/politur/Reports"; import GuideDashboard from "./pages/dashboard/guides/GuideDashboard"; import ItineraryBuilder from "./pages/dashboard/guides/ItineraryBuilder"; import ContentLibrary from "./pages/dashboard/guides/ContentLibrary"; +// Tourist App +import TouristApp from "./pages/TouristApp"; // Commerce pages (for retail stores) import CommerceStore from "./pages/dashboard/commerce/Store"; import CommercePOS from "./pages/dashboard/commerce/POSTerminal"; @@ -182,6 +184,11 @@ const AppRouter = () => ( } /> + + + + } /> {/* Protected Dashboard Routes */} { + const [isARActive, setIsARActive] = useState(false); + const [detectedMonument, setDetectedMonument] = useState(null); + const { toast } = useToast(); + + const startAR = async () => { + try { + const stream = await navigator.mediaDevices.getUserMedia({ + video: { facingMode: 'environment' } + }); + + setIsARActive(true); + + // Simulate monument detection after 2 seconds + setTimeout(() => { + setDetectedMonument('Catedral Primada de América'); + toast({ + title: "Monumento Detectado", + description: "Catedral Primada de América (1514)", + }); + }, 2000); + + } catch (error) { + toast({ + title: "Error", + description: "No se pudo acceder a la cámara. Por favor verifica los permisos.", + variant: "destructive" + }); + } + }; + + const stopAR = () => { + setIsARActive(false); + setDetectedMonument(null); + }; + + return ( +
+
+
+

Realidad Aumentada

+

Apunta tu cámara a monumentos para más información

+
+
+ + {!isARActive ? ( +
+ +

Experiencia AR

+

+ Descubre la historia de los monumentos apuntando tu cámara. + La IA reconocerá automáticamente los lugares históricos. +

+ +
+ ) : ( +
+ {/* Camera View Simulation */} +
+ {/* Simulated camera feed */} +
+
+ +

Vista de Cámara Activa

+

Apunta a un monumento

+
+
+ + {/* AR Overlay */} + {detectedMonument && ( +
+ {/* Detection Frame */} +
+
+
+
+
+
+
+ )} + + {/* Close Button */} + + + {/* Recording Indicator */} +
+
+ AR Activo +
+
+ + {/* Information Overlay */} + {detectedMonument && ( + +
+
+
+ +
+
+

{detectedMonument}

+

Monumento Histórico

+
+
+ +
+ +
+

+ Primera catedral de América, construcción iniciada en 1514. + Combina elementos góticos y barrocos. Declarada Patrimonio de la Humanidad por la UNESCO. +

+ +
+ + +
+
+
+ )} +
+ )} + + {/* Features Info */} +
+ +
+
+ +
+
+
Reconocimiento IA
+

Identifica monumentos automáticamente

+
+
+
+ + +
+
+ +
+
+
Info Histórica
+

Datos e historia en tiempo real

+
+
+
+ + +
+
+ +
+
+
Audio Guías
+

Narración profesional

+
+
+
+
+
+ ); +}; + +export default ARViewer; diff --git a/src/components/tourist/EmergencyButton.tsx b/src/components/tourist/EmergencyButton.tsx new file mode 100644 index 0000000..b14c23e --- /dev/null +++ b/src/components/tourist/EmergencyButton.tsx @@ -0,0 +1,149 @@ +import React, { useState } from 'react'; +import { AlertCircle, Phone, X, MapPin } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { useToast } from '@/hooks/use-toast'; + +const EmergencyButton = () => { + const [showDialog, setShowDialog] = useState(false); + const [emergencyType, setEmergencyType] = useState(null); + const { toast } = useToast(); + + const emergencyTypes = [ + { id: 'medical', label: 'Emergencia Médica', icon: '🏥', color: 'bg-red-500' }, + { id: 'security', label: 'Seguridad', icon: '🚨', color: 'bg-orange-500' }, + { id: 'accident', label: 'Accidente', icon: '🚗', color: 'bg-yellow-500' }, + { id: 'other', label: 'Otra Emergencia', icon: '⚠️', color: 'bg-gray-500' } + ]; + + const sendEmergencyAlert = (type: string) => { + // Get user location + if (navigator.geolocation) { + navigator.geolocation.getCurrentPosition( + (position) => { + const { latitude, longitude } = position.coords; + + toast({ + title: "¡Alerta Enviada!", + description: "POLITUR ha sido notificado de tu emergencia. Te contactarán pronto.", + }); + + // In production, this would send to the backend + console.log('Emergency Alert:', { + type, + location: { lat: latitude, lng: longitude }, + timestamp: new Date().toISOString() + }); + + setShowDialog(false); + setEmergencyType(null); + }, + (error) => { + toast({ + title: "Error de Ubicación", + description: "No se pudo obtener tu ubicación. Por favor activa el GPS.", + variant: "destructive" + }); + } + ); + } + }; + + return ( + <> + {/* Floating Emergency Button */} + + + {/* Emergency Dialog */} + + + + + + Emergencia + + + + {!emergencyType ? ( +
+

Selecciona el tipo de emergencia:

+ +
+ {emergencyTypes.map((type) => ( + + ))} +
+ +
+
+ +
+

Números de Emergencia:

+

POLITUR: *911

+

Emergencias: 911

+
+
+
+
+ ) : ( +
+
+

+ Estás a punto de enviar una alerta de emergencia a POLITUR. + Tu ubicación actual será compartida. +

+
+ + Compartiendo ubicación GPS... +
+
+ +
+ + +
+ +
+ +
+
+ )} +
+
+ + ); +}; + +export default EmergencyButton; diff --git a/src/components/tourist/InteractiveMap.tsx b/src/components/tourist/InteractiveMap.tsx new file mode 100644 index 0000000..165098e --- /dev/null +++ b/src/components/tourist/InteractiveMap.tsx @@ -0,0 +1,225 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { MapPin, Navigation, Layers } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { useToast } from '@/hooks/use-toast'; + +interface Attraction { + id: string; + name: string; + category: string; + distance: string; + rating: number; +} + +interface InteractiveMapProps { + attractions: Attraction[]; +} + +const InteractiveMap: React.FC = ({ attractions }) => { + const mapContainer = useRef(null); + const [userLocation, setUserLocation] = useState<{ lat: number; lng: number } | null>(null); + const { toast } = useToast(); + + useEffect(() => { + // Get user location + if (navigator.geolocation) { + navigator.geolocation.getCurrentPosition( + (position) => { + setUserLocation({ + lat: position.coords.latitude, + lng: position.coords.longitude + }); + + toast({ + title: "Ubicación Obtenida", + description: "Mostrando atracciones cerca de ti", + }); + }, + (error) => { + console.error('Error getting location:', error); + } + ); + } + + if (!mapContainer.current) return; + + // Simulated interactive map + const canvas = document.createElement('canvas'); + canvas.width = mapContainer.current.clientWidth; + canvas.height = 500; + canvas.style.width = '100%'; + canvas.style.height = '500px'; + canvas.style.borderRadius = '8px'; + + const ctx = canvas.getContext('2d'); + if (!ctx) return; + + // Draw map background + const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height); + gradient.addColorStop(0, '#e8f4f8'); + gradient.addColorStop(1, '#b8d4e0'); + ctx.fillStyle = gradient; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + // Draw grid + ctx.strokeStyle = '#d0e8f0'; + ctx.lineWidth = 1; + for (let i = 0; i < canvas.width; i += 40) { + ctx.beginPath(); + ctx.moveTo(i, 0); + ctx.lineTo(i, canvas.height); + ctx.stroke(); + } + for (let i = 0; i < canvas.height; i += 40) { + ctx.beginPath(); + ctx.moveTo(0, i); + ctx.lineTo(canvas.width, i); + ctx.stroke(); + } + + // Draw title + ctx.fillStyle = '#333'; + ctx.font = 'bold 20px Arial'; + ctx.textAlign = 'center'; + ctx.fillText('Mapa Interactivo', canvas.width / 2, 30); + ctx.font = '14px Arial'; + ctx.fillStyle = '#666'; + ctx.fillText('(Integración con Mapbox + IA próximamente)', canvas.width / 2, 55); + + // Draw user location (center) + if (userLocation) { + const centerX = canvas.width / 2; + const centerY = canvas.height / 2; + + // User marker + ctx.fillStyle = '#3b82f6'; + ctx.beginPath(); + ctx.arc(centerX, centerY, 12, 0, Math.PI * 2); + ctx.fill(); + ctx.strokeStyle = '#fff'; + ctx.lineWidth = 3; + ctx.stroke(); + + // Pulse effect + ctx.strokeStyle = '#3b82f6'; + ctx.lineWidth = 2; + ctx.globalAlpha = 0.3; + ctx.beginPath(); + ctx.arc(centerX, centerY, 20, 0, Math.PI * 2); + ctx.stroke(); + ctx.globalAlpha = 1; + + ctx.fillStyle = '#333'; + ctx.font = 'bold 12px Arial'; + ctx.textAlign = 'center'; + ctx.fillText('Tu ubicación', centerX, centerY + 35); + } + + // Draw attraction markers + attractions.forEach((attraction, index) => { + const angle = (index / attractions.length) * Math.PI * 2; + const radius = 120; + const x = canvas.width / 2 + Math.cos(angle) * radius; + const y = canvas.height / 2 + Math.sin(angle) * radius; + + // Marker + ctx.fillStyle = '#ef4444'; + ctx.beginPath(); + ctx.arc(x, y, 10, 0, Math.PI * 2); + ctx.fill(); + ctx.strokeStyle = '#fff'; + ctx.lineWidth = 2; + ctx.stroke(); + + // Label background + const labelWidth = ctx.measureText(attraction.name).width + 16; + ctx.fillStyle = 'rgba(255, 255, 255, 0.95)'; + ctx.fillRect(x - labelWidth / 2, y - 35, labelWidth, 24); + ctx.strokeStyle = '#ddd'; + ctx.lineWidth = 1; + ctx.strokeRect(x - labelWidth / 2, y - 35, labelWidth, 24); + + // Label text + ctx.fillStyle = '#333'; + ctx.font = '11px Arial'; + ctx.textAlign = 'center'; + ctx.fillText(attraction.name, x, y - 18); + }); + + mapContainer.current.innerHTML = ''; + mapContainer.current.appendChild(canvas); + + // Add click handler + canvas.addEventListener('click', (e) => { + const rect = canvas.getBoundingClientRect(); + const x = (e.clientX - rect.left) * (canvas.width / rect.width); + const y = (e.clientY - rect.top) * (canvas.height / rect.height); + + attractions.forEach((attraction, index) => { + const angle = (index / attractions.length) * Math.PI * 2; + const radius = 120; + const markerX = canvas.width / 2 + Math.cos(angle) * radius; + const markerY = canvas.height / 2 + Math.sin(angle) * radius; + const distance = Math.sqrt(Math.pow(x - markerX, 2) + Math.pow(y - markerY, 2)); + + if (distance < 15) { + toast({ + title: attraction.name, + description: `${attraction.category} - ${attraction.distance}`, + }); + } + }); + }); + + canvas.style.cursor = 'pointer'; + + }, [attractions, userLocation, toast]); + + const centerOnUser = () => { + if (navigator.geolocation) { + navigator.geolocation.getCurrentPosition( + (position) => { + setUserLocation({ + lat: position.coords.latitude, + lng: position.coords.longitude + }); + toast({ + title: "Centrando Mapa", + description: "Mostrando tu ubicación actual", + }); + } + ); + } + }; + + return ( +
+
+

Explora Cerca de Ti

+
+ + +
+
+ +
+ {/* Map will be rendered here */} +
+ +
+

+ 💡 Funciones IA próximamente: Reconocimiento automático de monumentos con la cámara, + navegación guiada por voz, y recomendaciones personalizadas basadas en tus preferencias. +

+
+
+ ); +}; + +export default InteractiveMap; diff --git a/src/pages/TouristApp.tsx b/src/pages/TouristApp.tsx new file mode 100644 index 0000000..80beede --- /dev/null +++ b/src/pages/TouristApp.tsx @@ -0,0 +1,255 @@ +import React, { useState } from 'react'; +import { MapPin, Camera, ShoppingBag, Calendar, User, Search, AlertCircle, Navigation, Star } from 'lucide-react'; +import { Card, CardContent } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Badge } from '@/components/ui/badge'; +import InteractiveMap from '@/components/tourist/InteractiveMap'; +import EmergencyButton from '@/components/tourist/EmergencyButton'; +import ARViewer from '@/components/tourist/ARViewer'; + +interface Attraction { + id: string; + name: string; + category: string; + distance: string; + rating: number; + image: string; + price?: number; +} + +const TouristApp = () => { + const [activeTab, setActiveTab] = useState<'explore' | 'map' | 'ar' | 'bookings' | 'shop'>('explore'); + const [searchQuery, setSearchQuery] = useState(''); + + const attractions: Attraction[] = [ + { + id: '1', + name: 'Zona Colonial', + category: 'Histórico', + distance: '2.3 km', + rating: 4.8, + image: '🏛️', + price: 25 + }, + { + id: '2', + name: 'Catedral Primada', + category: 'Monumento', + distance: '2.5 km', + rating: 4.9, + image: '⛪', + price: 15 + }, + { + id: '3', + name: 'Alcázar de Colón', + category: 'Museo', + distance: '2.8 km', + rating: 4.7, + image: '🏰', + price: 20 + } + ]; + + const quickActions = [ + { icon: Calendar, label: 'Reservar Hotel', color: 'bg-blue-500' }, + { icon: User, label: 'Contratar Guía', color: 'bg-purple-500' }, + { icon: Navigation, label: 'Taxi', color: 'bg-yellow-500' }, + { icon: ShoppingBag, label: 'Souvenirs', color: 'bg-green-500' } + ]; + + return ( +
+ {/* Header */} +
+
+
+

Explora República Dominicana

+ +
+ + {/* Search Bar */} +
+ + setSearchQuery(e.target.value)} + /> +
+
+
+ + {/* Emergency Button - Always Visible */} + + +
+ {/* Quick Actions */} + + +
+ {quickActions.map((action, index) => ( + + ))} +
+
+
+ + {/* Content Area */} + {activeTab === 'explore' && ( +
+
+

Cerca de ti

+ +
+ + {attractions.map((attraction) => ( + + +
+
+ {attraction.image} +
+
+
+
+

{attraction.name}

+ {attraction.category} +
+
+
+ + {attraction.rating} +
+ {attraction.distance} +
+
+
+ ${attraction.price} +
+ + +
+
+
+
+
+
+ ))} +
+ )} + + {activeTab === 'map' && ( + + + + + + )} + + {activeTab === 'ar' && ( + + + + + + )} + + {activeTab === 'bookings' && ( +
+ +

Mis Reservas

+

No tienes reservas activas

+ +
+ )} + + {activeTab === 'shop' && ( +
+ +

Tienda de Souvenirs

+

Descubre productos locales

+ +
+ )} +
+ + {/* Bottom Navigation */} +
+
+ + + + + + + + + +
+
+
+ ); +}; + +export default TouristApp;