Add pagination to dataset gallery (50 per page, newest first)
This commit is contained in:
@@ -227,14 +227,34 @@ def dataset_count():
|
||||
|
||||
@app.route("/dataset/list")
|
||||
def dataset_list():
|
||||
"""Lista todas las imágenes del dataset"""
|
||||
"""Lista las imágenes del dataset con paginación"""
|
||||
from flask import request
|
||||
|
||||
try:
|
||||
page = int(request.args.get('page', 1))
|
||||
per_page = int(request.args.get('per_page', 50))
|
||||
|
||||
files = [f for f in os.listdir(DATASET_DIR) if f.endswith('.jpg')]
|
||||
# Ordenar por fecha (más recientes primero)
|
||||
files.sort(reverse=True)
|
||||
|
||||
# Ordenar por fecha de modificación (más recientes primero)
|
||||
files_with_time = []
|
||||
for f in files:
|
||||
filepath = os.path.join(DATASET_DIR, f)
|
||||
mtime = os.path.getmtime(filepath)
|
||||
files_with_time.append((f, mtime))
|
||||
|
||||
files_with_time.sort(key=lambda x: x[1], reverse=True)
|
||||
sorted_files = [f[0] for f in files_with_time]
|
||||
|
||||
# Paginación
|
||||
total = len(sorted_files)
|
||||
total_pages = (total + per_page - 1) // per_page
|
||||
start = (page - 1) * per_page
|
||||
end = start + per_page
|
||||
page_files = sorted_files[start:end]
|
||||
|
||||
images = []
|
||||
for f in files[:50]: # Limitar a últimas 50
|
||||
for f in page_files:
|
||||
parts = f.replace('.jpg', '').split('_')
|
||||
plate = parts[0] if parts else 'Unknown'
|
||||
images.append({
|
||||
@@ -243,7 +263,13 @@ def dataset_list():
|
||||
'url': f'/dataset/images/{f}'
|
||||
})
|
||||
|
||||
return {"images": images, "total": len(files)}
|
||||
return {
|
||||
"images": images,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"per_page": per_page,
|
||||
"total_pages": total_pages
|
||||
}
|
||||
except Exception as e:
|
||||
return {"images": [], "total": 0, "error": str(e)}
|
||||
|
||||
|
||||
@@ -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, X, Image } from 'lucide-react';
|
||||
import { Users, CheckCircle, XCircle, Shield, Trash2, Camera, AlertCircle, Database, X, Image, ChevronLeft, ChevronRight } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import LanguageSelector from '../components/LanguageSelector';
|
||||
|
||||
@@ -31,6 +31,9 @@ function AdminDashboard({ token }) {
|
||||
const [showDatasetModal, setShowDatasetModal] = useState(false);
|
||||
const [datasetImages, setDatasetImages] = useState([]);
|
||||
const [selectedImage, setSelectedImage] = useState(null);
|
||||
const [datasetPage, setDatasetPage] = useState(1);
|
||||
const [datasetTotalPages, setDatasetTotalPages] = useState(1);
|
||||
const [datasetTotal, setDatasetTotal] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
@@ -110,20 +113,31 @@ function AdminDashboard({ token }) {
|
||||
}
|
||||
};
|
||||
|
||||
const fetchDatasetImages = async () => {
|
||||
const fetchDatasetImages = async (page = 1) => {
|
||||
try {
|
||||
const res = await axios.get('/dataset/list');
|
||||
const res = await axios.get(`/dataset/list?page=${page}&per_page=50`);
|
||||
setDatasetImages(res.data.images || []);
|
||||
setDatasetPage(res.data.page || 1);
|
||||
setDatasetTotalPages(res.data.total_pages || 1);
|
||||
setDatasetTotal(res.data.total || 0);
|
||||
} catch (err) {
|
||||
console.error('Error fetching dataset images');
|
||||
}
|
||||
};
|
||||
|
||||
const openDatasetModal = () => {
|
||||
fetchDatasetImages();
|
||||
setDatasetPage(1);
|
||||
fetchDatasetImages(1);
|
||||
setShowDatasetModal(true);
|
||||
};
|
||||
|
||||
const handleDatasetPageChange = (newPage) => {
|
||||
if (newPage >= 1 && newPage <= datasetTotalPages) {
|
||||
setDatasetPage(newPage);
|
||||
fetchDatasetImages(newPage);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearchRut = (e) => {
|
||||
e.preventDefault();
|
||||
const normalizedRut = searchRut.replace(/\./g, '').toUpperCase();
|
||||
@@ -512,7 +526,7 @@ function AdminDashboard({ token }) {
|
||||
<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>
|
||||
<span className="bg-emerald-600 px-2 py-1 rounded text-sm font-mono">{datasetTotal} imágenes</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => { setShowDatasetModal(false); setSelectedImage(null); }}
|
||||
@@ -572,6 +586,34 @@ function AdminDashboard({ token }) {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Pagination Footer */}
|
||||
{!selectedImage && datasetTotalPages > 1 && (
|
||||
<div className="flex items-center justify-center gap-4 p-4 border-t border-slate-700 bg-slate-800/50">
|
||||
<button
|
||||
onClick={() => handleDatasetPageChange(datasetPage - 1)}
|
||||
disabled={datasetPage === 1}
|
||||
className="flex items-center gap-1 px-3 py-2 rounded-lg bg-slate-700 hover:bg-slate-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
>
|
||||
<ChevronLeft size={18} />
|
||||
Anterior
|
||||
</button>
|
||||
<div className="flex items-center gap-2 text-slate-300">
|
||||
<span>Página</span>
|
||||
<span className="bg-emerald-600 px-3 py-1 rounded font-mono font-bold">{datasetPage}</span>
|
||||
<span>de</span>
|
||||
<span className="font-mono font-bold">{datasetTotalPages}</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleDatasetPageChange(datasetPage + 1)}
|
||||
disabled={datasetPage === datasetTotalPages}
|
||||
className="flex items-center gap-1 px-3 py-2 rounded-lg bg-slate-700 hover:bg-slate-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
>
|
||||
Siguiente
|
||||
<ChevronRight size={18} />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user