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")
|
@app.route("/dataset/list")
|
||||||
def 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:
|
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')]
|
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 = []
|
images = []
|
||||||
for f in files[:50]: # Limitar a últimas 50
|
for f in page_files:
|
||||||
parts = f.replace('.jpg', '').split('_')
|
parts = f.replace('.jpg', '').split('_')
|
||||||
plate = parts[0] if parts else 'Unknown'
|
plate = parts[0] if parts else 'Unknown'
|
||||||
images.append({
|
images.append({
|
||||||
@@ -243,7 +263,13 @@ def dataset_list():
|
|||||||
'url': f'/dataset/images/{f}'
|
'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:
|
except Exception as e:
|
||||||
return {"images": [], "total": 0, "error": str(e)}
|
return {"images": [], "total": 0, "error": str(e)}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import io from 'socket.io-client';
|
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 { useTranslation } from 'react-i18next';
|
||||||
import LanguageSelector from '../components/LanguageSelector';
|
import LanguageSelector from '../components/LanguageSelector';
|
||||||
|
|
||||||
@@ -31,6 +31,9 @@ function AdminDashboard({ token }) {
|
|||||||
const [showDatasetModal, setShowDatasetModal] = useState(false);
|
const [showDatasetModal, setShowDatasetModal] = useState(false);
|
||||||
const [datasetImages, setDatasetImages] = useState([]);
|
const [datasetImages, setDatasetImages] = useState([]);
|
||||||
const [selectedImage, setSelectedImage] = useState(null);
|
const [selectedImage, setSelectedImage] = useState(null);
|
||||||
|
const [datasetPage, setDatasetPage] = useState(1);
|
||||||
|
const [datasetTotalPages, setDatasetTotalPages] = useState(1);
|
||||||
|
const [datasetTotal, setDatasetTotal] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchData();
|
fetchData();
|
||||||
@@ -110,20 +113,31 @@ function AdminDashboard({ token }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchDatasetImages = async () => {
|
const fetchDatasetImages = async (page = 1) => {
|
||||||
try {
|
try {
|
||||||
const res = await axios.get('/dataset/list');
|
const res = await axios.get(`/dataset/list?page=${page}&per_page=50`);
|
||||||
setDatasetImages(res.data.images || []);
|
setDatasetImages(res.data.images || []);
|
||||||
|
setDatasetPage(res.data.page || 1);
|
||||||
|
setDatasetTotalPages(res.data.total_pages || 1);
|
||||||
|
setDatasetTotal(res.data.total || 0);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error fetching dataset images');
|
console.error('Error fetching dataset images');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const openDatasetModal = () => {
|
const openDatasetModal = () => {
|
||||||
fetchDatasetImages();
|
setDatasetPage(1);
|
||||||
|
fetchDatasetImages(1);
|
||||||
setShowDatasetModal(true);
|
setShowDatasetModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDatasetPageChange = (newPage) => {
|
||||||
|
if (newPage >= 1 && newPage <= datasetTotalPages) {
|
||||||
|
setDatasetPage(newPage);
|
||||||
|
fetchDatasetImages(newPage);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleSearchRut = (e) => {
|
const handleSearchRut = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const normalizedRut = searchRut.replace(/\./g, '').toUpperCase();
|
const normalizedRut = searchRut.replace(/\./g, '').toUpperCase();
|
||||||
@@ -512,7 +526,7 @@ function AdminDashboard({ token }) {
|
|||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Database className="text-emerald-400" size={24} />
|
<Database className="text-emerald-400" size={24} />
|
||||||
<h2 className="text-xl font-bold text-white">Dataset de Capturas</h2>
|
<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>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => { setShowDatasetModal(false); setSelectedImage(null); }}
|
onClick={() => { setShowDatasetModal(false); setSelectedImage(null); }}
|
||||||
@@ -572,6 +586,34 @@ function AdminDashboard({ token }) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user