add preview dataset

This commit is contained in:
2026-01-12 23:16:10 -03:00
parent 000009595d
commit c2fb62ab7a
2 changed files with 127 additions and 9 deletions

View File

@@ -220,12 +220,39 @@ def health():
def dataset_count():
"""Endpoint para ver cuántas capturas hay en el dataset"""
try:
files = os.listdir(DATASET_DIR)
plates = len([f for f in files if f.endswith('_plate.jpg')])
return {"plates_captured": plates, "total_files": len(files)}
files = [f for f in os.listdir(DATASET_DIR) if f.endswith('.jpg')]
return {"plates_captured": len(files), "total_files": len(files)}
except:
return {"plates_captured": 0, "total_files": 0}
@app.route("/dataset/list")
def dataset_list():
"""Lista todas las imágenes del dataset"""
try:
files = [f for f in os.listdir(DATASET_DIR) if f.endswith('.jpg')]
# Ordenar por fecha (más recientes primero)
files.sort(reverse=True)
images = []
for f in files[:50]: # Limitar a últimas 50
parts = f.replace('.jpg', '').split('_')
plate = parts[0] if parts else 'Unknown'
images.append({
'filename': f,
'plate': plate,
'url': f'/dataset/images/{f}'
})
return {"images": images, "total": len(files)}
except Exception as e:
return {"images": [], "total": 0, "error": str(e)}
@app.route("/dataset/images/<filename>")
def dataset_image(filename):
"""Sirve una imagen específica del dataset"""
from flask import send_from_directory
return send_from_directory(DATASET_DIR, filename)
if __name__ == "__main__":
t = threading.Thread(target=camera_loop, daemon=True)
t.start()

View File

@@ -1,7 +1,7 @@
import { useState, useEffect } from 'react';
import axios from 'axios';
import io from 'socket.io-client';
import { Users, CheckCircle, XCircle, Shield, Trash2, Camera, AlertCircle, Database } from 'lucide-react';
import { Users, CheckCircle, XCircle, Shield, Trash2, Camera, AlertCircle, Database, X, Image } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import LanguageSelector from '../components/LanguageSelector';
@@ -28,6 +28,9 @@ function AdminDashboard({ token }) {
// Dataset State
const [datasetCount, setDatasetCount] = useState(0);
const [showDatasetModal, setShowDatasetModal] = useState(false);
const [datasetImages, setDatasetImages] = useState([]);
const [selectedImage, setSelectedImage] = useState(null);
useEffect(() => {
fetchData();
@@ -107,6 +110,20 @@ function AdminDashboard({ token }) {
}
};
const fetchDatasetImages = async () => {
try {
const res = await axios.get('/dataset/list');
setDatasetImages(res.data.images || []);
} catch (err) {
console.error('Error fetching dataset images');
}
};
const openDatasetModal = () => {
fetchDatasetImages();
setShowDatasetModal(true);
};
const handleSearchRut = (e) => {
e.preventDefault();
const normalizedRut = searchRut.replace(/\./g, '').toUpperCase();
@@ -221,12 +238,15 @@ function AdminDashboard({ token }) {
<Camera /> {t('monitor_area')}
</h2>
<div className="flex items-center gap-4">
{/* Dataset Counter */}
<div className="flex items-center gap-2 bg-gradient-to-r from-emerald-900/50 to-teal-900/50 px-4 py-2 rounded-lg border border-emerald-700/50">
{/* Dataset Counter - Clickable */}
<button
onClick={openDatasetModal}
className="flex items-center gap-2 bg-gradient-to-r from-emerald-900/50 to-teal-900/50 px-4 py-2 rounded-lg border border-emerald-700/50 hover:from-emerald-800/50 hover:to-teal-800/50 transition-all cursor-pointer"
>
<Database size={18} className="text-emerald-400" />
<span className="text-emerald-300 font-mono font-bold">{datasetCount}</span>
<span className="text-emerald-400/70 text-sm">capturas</span>
</div>
</button>
<div className="flex bg-slate-800 rounded p-1">
<button onClick={() => setViewMode('live')} className={`px-3 py-1 rounded ${viewMode === 'live' ? 'bg-blue-600' : ''}`}>Live</button>
<button onClick={() => setViewMode('history')} className={`px-3 py-1 rounded ${viewMode === 'history' ? 'bg-blue-600' : ''}`}>History</button>
@@ -482,10 +502,81 @@ function AdminDashboard({ token }) {
</div>
)}
</div>
{/* Dataset Gallery Modal */}
{showDatasetModal && (
<div className="fixed inset-0 bg-black/80 backdrop-blur-sm z-50 flex items-center justify-center p-4">
<div className="bg-slate-900 rounded-2xl border border-slate-700 w-full max-w-6xl max-h-[90vh] overflow-hidden flex flex-col">
{/* Header */}
<div className="flex items-center justify-between p-4 border-b border-slate-700">
<div className="flex items-center gap-3">
<Database className="text-emerald-400" size={24} />
<h2 className="text-xl font-bold text-white">Dataset de Capturas</h2>
<span className="bg-emerald-600 px-2 py-1 rounded text-sm font-mono">{datasetImages.length} imágenes</span>
</div>
<button
onClick={() => { setShowDatasetModal(false); setSelectedImage(null); }}
className="text-slate-400 hover:text-white p-2 hover:bg-slate-800 rounded-lg transition-colors"
>
<X size={24} />
</button>
</div>
{/* Content */}
<div className="flex-1 overflow-auto p-4">
{selectedImage ? (
/* Image Preview */
<div className="flex flex-col items-center gap-4">
<button
onClick={() => setSelectedImage(null)}
className="self-start flex items-center gap-2 text-slate-400 hover:text-white transition-colors"
>
Volver a galería
</button>
<img
src={selectedImage.url}
alt={selectedImage.plate}
className="max-w-full max-h-[60vh] rounded-lg border border-slate-700"
/>
<div className="bg-slate-800 px-4 py-2 rounded-lg">
<span className="text-slate-400">Patente: </span>
<span className="font-mono font-bold text-emerald-400 text-lg">{selectedImage.plate}</span>
</div>
</div>
) : (
/* Image Grid */
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4">
{datasetImages.map((img, idx) => (
<div
key={idx}
onClick={() => setSelectedImage(img)}
className="cursor-pointer group relative aspect-video bg-slate-800 rounded-lg overflow-hidden border border-slate-700 hover:border-emerald-500 transition-all"
>
<img
src={img.url}
alt={img.plate}
className="w-full h-full object-cover group-hover:scale-105 transition-transform"
loading="lazy"
/>
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/80 to-transparent p-2">
<span className="font-mono text-sm text-emerald-300 font-bold">{img.plate}</span>
</div>
</div>
))}
{datasetImages.length === 0 && (
<div className="col-span-full text-center py-12 text-slate-500">
<Image size={48} className="mx-auto mb-4 opacity-50" />
<p>No hay capturas en el dataset</p>
</div>
)}
</div>
)}
</div>
</div>
</div>
)}
</div>
);
}
export default AdminDashboard;