diff --git a/alpr-service/main.py b/alpr-service/main.py index cdd6c2e..24fb72f 100644 --- a/alpr-service/main.py +++ b/alpr-service/main.py @@ -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/") +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() diff --git a/frontend/src/pages/AdminDashboard.jsx b/frontend/src/pages/AdminDashboard.jsx index 3c86e51..0cf5582 100644 --- a/frontend/src/pages/AdminDashboard.jsx +++ b/frontend/src/pages/AdminDashboard.jsx @@ -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 }) { {t('monitor_area')}
- {/* Dataset Counter */} -
+ {/* Dataset Counter - Clickable */} +
+
@@ -482,10 +502,81 @@ function AdminDashboard({ token }) {
)}
+ + {/* Dataset Gallery Modal */} + {showDatasetModal && ( +
+
+ {/* Header */} +
+
+ +

Dataset de Capturas

+ {datasetImages.length} imágenes +
+ +
+ + {/* Content */} +
+ {selectedImage ? ( + /* Image Preview */ +
+ + {selectedImage.plate} +
+ Patente: + {selectedImage.plate} +
+
+ ) : ( + /* Image Grid */ +
+ {datasetImages.map((img, idx) => ( +
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.plate} +
+ {img.plate} +
+
+ ))} + {datasetImages.length === 0 && ( +
+ +

No hay capturas en el dataset

+
+ )} +
+ )} +
+
+
+ )} ); } export default AdminDashboard; - -