Initial commit from remix
This commit is contained in:
238
src/components/admin/ServicesTab.tsx
Normal file
238
src/components/admin/ServicesTab.tsx
Normal file
@@ -0,0 +1,238 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Building,
|
||||
Hotel,
|
||||
UtensilsCrossed,
|
||||
Car,
|
||||
Store,
|
||||
Users,
|
||||
MapPin,
|
||||
Star,
|
||||
Phone,
|
||||
Mail,
|
||||
Plus,
|
||||
Search,
|
||||
Edit,
|
||||
Trash2,
|
||||
Eye,
|
||||
CheckCircle,
|
||||
XCircle,
|
||||
Clock,
|
||||
Map,
|
||||
Shield
|
||||
} from 'lucide-react';
|
||||
import { useAdminData } from '@/hooks/useAdminData';
|
||||
|
||||
interface ServicesTabProps {
|
||||
establishments: any[];
|
||||
isAdmin: boolean;
|
||||
isSuperAdmin: boolean;
|
||||
loadEstablishments: (type?: string) => void;
|
||||
}
|
||||
|
||||
const ServicesTab: React.FC<ServicesTabProps> = ({
|
||||
establishments,
|
||||
isAdmin,
|
||||
loadEstablishments
|
||||
}) => {
|
||||
const { updateEstablishment, deleteEstablishment } = useAdminData();
|
||||
const [activeFilter, setActiveFilter] = useState('all');
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
loadEstablishments(activeFilter === 'all' ? undefined : activeFilter);
|
||||
}, [activeFilter]);
|
||||
|
||||
const serviceTypes = [
|
||||
{ id: 'all', label: 'Todos', icon: Building, count: establishments.length },
|
||||
{ id: 'hotel', label: 'Hoteles', icon: Hotel, count: establishments.filter(e => e.type === 'hotel').length },
|
||||
{ id: 'restaurant', label: 'Restaurantes', icon: UtensilsCrossed, count: establishments.filter(e => e.type === 'restaurant').length },
|
||||
{ id: 'taxi', label: 'Taxis', icon: Car, count: establishments.filter(e => e.type === 'taxi').length },
|
||||
{ id: 'shop', label: 'Tiendas', icon: Store, count: establishments.filter(e => e.type === 'shop').length },
|
||||
{ id: 'guide', label: 'Guías Turísticos', icon: Map, count: establishments.filter(e => e.type === 'guide').length },
|
||||
{ id: 'security', label: 'Politur', icon: Shield, count: establishments.filter(e => e.type === 'security').length },
|
||||
];
|
||||
|
||||
const filteredEstablishments = establishments.filter(establishment =>
|
||||
establishment.name?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
establishment.description?.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
|
||||
const handleUpdateStatus = async (id: string, status: 'active' | 'suspended' | 'pending') => {
|
||||
const result = await updateEstablishment(id, { status });
|
||||
if (result.success) {
|
||||
loadEstablishments(activeFilter === 'all' ? undefined : activeFilter);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (id: string) => {
|
||||
if (window.confirm('¿Estás seguro de que quieres eliminar este establecimiento?')) {
|
||||
const result = await deleteEstablishment(id);
|
||||
if (result.success) {
|
||||
loadEstablishments(activeFilter === 'all' ? undefined : activeFilter);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex justify-between items-center">
|
||||
<h2 className="text-xl font-semibold text-gray-900">Proveedores de Servicios</h2>
|
||||
<div className="flex space-x-2">
|
||||
<button className="bg-orange-500 text-white px-4 py-2 rounded-lg hover:bg-orange-600 flex items-center space-x-2">
|
||||
<Plus className="w-4 h-4" />
|
||||
<span>Aprobar Pendientes</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Service Type Filters */}
|
||||
<div className="grid services-grid-mobile md:grid-cols-4 lg:grid-cols-7 gap-4">
|
||||
{serviceTypes.map((type) => {
|
||||
const Icon = type.icon;
|
||||
return (
|
||||
<button
|
||||
key={type.id}
|
||||
onClick={() => setActiveFilter(type.id)}
|
||||
className={`p-4 rounded-lg border-2 transition-colors ${
|
||||
activeFilter === type.id
|
||||
? 'border-orange-500 bg-orange-50 text-orange-700'
|
||||
: 'border-gray-200 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-center mb-2">
|
||||
<Icon className="w-6 h-6" />
|
||||
</div>
|
||||
<div className="text-sm font-medium">{type.label}</div>
|
||||
<div className="text-xs text-gray-500">{type.count} activos</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Search */}
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Buscar establecimientos..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="w-full pl-10 pr-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-orange-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Establishments Grid */}
|
||||
<div className="grid mobile-grid md:grid-cols-2 lg:grid-cols-3 gap-4 md:gap-6">
|
||||
{filteredEstablishments.map((establishment) => (
|
||||
<div key={establishment.id} className="bg-white rounded-lg shadow-md overflow-hidden">
|
||||
{/* Image */}
|
||||
<div className="h-48 bg-gray-200 relative">
|
||||
<img
|
||||
src={establishment.imageUrl || '/api/placeholder/400/300'}
|
||||
alt={establishment.name}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
<div className="absolute top-2 right-2">
|
||||
<span className={`px-2 py-1 text-xs font-semibold rounded-full ${
|
||||
establishment.status === 'active' ? 'bg-green-100 text-green-800' :
|
||||
establishment.status === 'pending' ? 'bg-yellow-100 text-yellow-800' :
|
||||
'bg-red-100 text-red-800'
|
||||
}`}>
|
||||
{establishment.status}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="p-4">
|
||||
<div className="flex items-start justify-between mb-2">
|
||||
<h3 className="text-lg font-semibold text-gray-900 truncate">
|
||||
{establishment.name}
|
||||
</h3>
|
||||
<div className="flex items-center space-x-1">
|
||||
<Star className="w-4 h-4 text-yellow-400 fill-current" />
|
||||
<span className="text-sm text-gray-600">{establishment.rating || 0}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-gray-600 mb-3 line-clamp-2">
|
||||
{establishment.description}
|
||||
</p>
|
||||
|
||||
<div className="space-y-2 mb-4">
|
||||
<div className="flex items-center space-x-2 text-sm text-gray-500">
|
||||
<MapPin className="w-4 h-4" />
|
||||
<span className="truncate">{establishment.location?.address}</span>
|
||||
</div>
|
||||
{establishment.owner && (
|
||||
<div className="flex items-center space-x-2 text-sm text-gray-500">
|
||||
<Users className="w-4 h-4" />
|
||||
<span>{establishment.owner.name}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex space-x-2">
|
||||
<button
|
||||
className="text-blue-600 hover:text-blue-900"
|
||||
title="Ver detalles"
|
||||
>
|
||||
<Eye className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
className="text-green-600 hover:text-green-900"
|
||||
title="Editar"
|
||||
>
|
||||
<Edit className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDelete(establishment.id)}
|
||||
className="text-red-600 hover:text-red-900"
|
||||
title="Eliminar"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{establishment.status === 'pending' && (
|
||||
<div className="flex space-x-1">
|
||||
<button
|
||||
onClick={() => handleUpdateStatus(establishment.id, 'active')}
|
||||
className="p-1 text-green-600 hover:text-green-900"
|
||||
title="Aprobar"
|
||||
>
|
||||
<CheckCircle className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleUpdateStatus(establishment.id, 'suspended')}
|
||||
className="p-1 text-red-600 hover:text-red-900"
|
||||
title="Rechazar"
|
||||
>
|
||||
<XCircle className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{filteredEstablishments.length === 0 && (
|
||||
<div className="text-center py-12">
|
||||
<Building className="w-12 h-12 text-gray-400 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">No hay establecimientos</h3>
|
||||
<p className="text-gray-500">No se encontraron establecimientos que coincidan con los filtros aplicados.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServicesTab;
|
||||
Reference in New Issue
Block a user