add image switch and image delete

This commit is contained in:
2026-01-29 12:35:21 -03:00
parent a6243413a1
commit 5d85dc0714
2 changed files with 150 additions and 13 deletions

View File

@@ -31,9 +31,11 @@ function AdminDashboard({ token }) {
const [showDatasetModal, setShowDatasetModal] = useState(false);
const [datasetImages, setDatasetImages] = useState([]);
const [selectedImage, setSelectedImage] = useState(null);
const [selectedImageIndex, setSelectedImageIndex] = useState(-1);
const [datasetPage, setDatasetPage] = useState(1);
const [datasetTotalPages, setDatasetTotalPages] = useState(1);
const [datasetTotal, setDatasetTotal] = useState(0);
const [deletingImage, setDeletingImage] = useState(false);
useEffect(() => {
fetchData();
@@ -67,6 +69,35 @@ function AdminDashboard({ token }) {
};
}, [token]);
// Keyboard navigation for dataset gallery
useEffect(() => {
const handleKeyDown = (e) => {
if (!showDatasetModal || !selectedImage) return;
if (e.key === 'ArrowLeft' && selectedImageIndex > 0) {
e.preventDefault();
setSelectedImageIndex(prev => {
const newIndex = prev - 1;
setSelectedImage(datasetImages[newIndex]);
return newIndex;
});
} else if (e.key === 'ArrowRight' && selectedImageIndex < datasetImages.length - 1) {
e.preventDefault();
setSelectedImageIndex(prev => {
const newIndex = prev + 1;
setSelectedImage(datasetImages[newIndex]);
return newIndex;
});
} else if (e.key === 'Escape') {
setSelectedImage(null);
setSelectedImageIndex(-1);
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [showDatasetModal, selectedImage, selectedImageIndex, datasetImages]);
useEffect(() => {
if (viewMode === 'history' && activeTab === 'monitor') {
fetchHistory(selectedDate);
@@ -138,6 +169,52 @@ function AdminDashboard({ token }) {
}
};
const selectImageByIndex = (index) => {
if (index >= 0 && index < datasetImages.length) {
setSelectedImage(datasetImages[index]);
setSelectedImageIndex(index);
}
};
const navigateImage = (direction) => {
const newIndex = selectedImageIndex + direction;
if (newIndex >= 0 && newIndex < datasetImages.length) {
selectImageByIndex(newIndex);
}
};
const deleteCurrentImage = async () => {
if (!selectedImage || deletingImage) return;
if (!confirm(`¿Eliminar imagen de ${selectedImage.plate}?`)) return;
setDeletingImage(true);
try {
await axios.delete(`/dataset/images/${selectedImage.filename}`);
// Remover de la lista local
const newImages = datasetImages.filter((_, idx) => idx !== selectedImageIndex);
setDatasetImages(newImages);
setDatasetTotal(prev => prev - 1);
setDatasetCount(prev => prev - 1);
// Navegar a la siguiente imagen o cerrar si no hay más
if (newImages.length === 0) {
setSelectedImage(null);
setSelectedImageIndex(-1);
} else if (selectedImageIndex >= newImages.length) {
selectImageByIndex(newImages.length - 1);
} else {
setSelectedImage(newImages[selectedImageIndex]);
}
} catch (err) {
console.error('Error deleting image:', err);
alert('Error al eliminar la imagen');
} finally {
setDeletingImage(false);
}
};
const handleSearchRut = (e) => {
e.preventDefault();
const normalizedRut = searchRut.replace(/\./g, '').toUpperCase();
@@ -539,23 +616,67 @@ function AdminDashboard({ token }) {
{/* Content */}
<div className="flex-1 overflow-auto p-4">
{selectedImage ? (
/* Image Preview */
/* Image Preview with Navigation */
<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"
/>
{/* Top Controls */}
<div className="w-full flex items-center justify-between">
<button
onClick={() => { setSelectedImage(null); setSelectedImageIndex(-1); }}
className="flex items-center gap-2 text-slate-400 hover:text-white transition-colors"
>
Volver a galería
</button>
<div className="text-slate-400 text-sm">
{selectedImageIndex + 1} de {datasetImages.length}
</div>
<button
onClick={deleteCurrentImage}
disabled={deletingImage}
className="flex items-center gap-2 px-3 py-2 bg-red-600 hover:bg-red-500 disabled:bg-red-800 disabled:cursor-not-allowed rounded-lg transition-colors"
>
<Trash2 size={16} />
{deletingImage ? 'Eliminando...' : 'Eliminar'}
</button>
</div>
{/* Image with Navigation Arrows */}
<div className="relative flex items-center gap-4 w-full justify-center">
{/* Previous Button */}
<button
onClick={() => navigateImage(-1)}
disabled={selectedImageIndex <= 0}
className="p-3 bg-slate-800 hover:bg-slate-700 disabled:opacity-30 disabled:cursor-not-allowed rounded-full transition-colors"
>
<ChevronLeft size={24} />
</button>
{/* Image */}
<img
src={selectedImage.url}
alt={selectedImage.plate}
className="max-w-[70%] max-h-[55vh] rounded-lg border border-slate-700"
/>
{/* Next Button */}
<button
onClick={() => navigateImage(1)}
disabled={selectedImageIndex >= datasetImages.length - 1}
className="p-3 bg-slate-800 hover:bg-slate-700 disabled:opacity-30 disabled:cursor-not-allowed rounded-full transition-colors"
>
<ChevronRight size={24} />
</button>
</div>
{/* Plate Info */}
<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>
{/* Keyboard hint */}
<div className="text-slate-500 text-xs">
Usa para navegar
</div>
</div>
) : (
/* Image Grid */
@@ -563,7 +684,7 @@ function AdminDashboard({ token }) {
{datasetImages.map((img, idx) => (
<div
key={idx}
onClick={() => setSelectedImage(img)}
onClick={() => selectImageByIndex(idx)}
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