v1.0.67 Backend / v1.0.64 Frontend - Paginación de 10 elementos en todas las pestañas
Frontend (1.0.64): - 📄 Paginación en InspectionsTab (10 inspecciones/página) - 📄 Paginación en ChecklistsTab (10 checklists/página) - 📊 Paginación en ReportsTab (10 informes/página) - Auto-reset a página 1 cuando cambian filtros de búsqueda - Navegación inteligente con puntos suspensivos para rangos grandes - Muestra primera, última y páginas cercanas (actual ± 1) - Contador 'Mostrando X-Y de Z' en cada pestaña - Botones Anterior/Siguiente con estados deshabilitados - useEffect para sincronizar currentPage con filtros Mejoras de UX: - Navegación directa por número de página - Diseño consistente en las 3 pestañas - Controles responsive con hover states - Indicadores visuales claros de página actual Backend (1.0.67): - Sin cambios (mantiene versión actual) Documentación: - 📝 Agregada sección 'Control de Versiones' en README.md - Instrucciones detalladas para commits con versiones - Formato estándar para mensajes de commit - Tipos de commit (feat, fix, refactor, etc.) - Reglas de Semantic Versioning - Ubicación de archivos de versión"
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "checklist-frontend",
|
||||
"private": true,
|
||||
"version": "1.0.63",
|
||||
"version": "1.0.64",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1735,6 +1735,8 @@ function ChecklistsTab({ checklists, user, onChecklistCreated, onStartInspection
|
||||
const [mechanics, setMechanics] = useState([])
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [aiModeFilter, setAiModeFilter] = useState('all') // all, off, optional, required
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
const itemsPerPage = 10
|
||||
const [formData, setFormData] = useState({
|
||||
name: '',
|
||||
description: '',
|
||||
@@ -1783,6 +1785,17 @@ function ChecklistsTab({ checklists, user, onChecklistCreated, onStartInspection
|
||||
return matchesSearch && matchesAiMode
|
||||
})
|
||||
|
||||
// Calcular paginación
|
||||
const totalPages = Math.ceil(filteredChecklists.length / itemsPerPage)
|
||||
const startIndex = (currentPage - 1) * itemsPerPage
|
||||
const endIndex = startIndex + itemsPerPage
|
||||
const paginatedChecklists = filteredChecklists.slice(startIndex, endIndex)
|
||||
|
||||
// Reset a página 1 cuando cambian los filtros
|
||||
useEffect(() => {
|
||||
setCurrentPage(1)
|
||||
}, [searchTerm, aiModeFilter])
|
||||
|
||||
const handleCreate = async (e) => {
|
||||
e.preventDefault()
|
||||
setCreating(true)
|
||||
@@ -1969,7 +1982,7 @@ function ChecklistsTab({ checklists, user, onChecklistCreated, onStartInspection
|
||||
|
||||
{/* Contador de resultados */}
|
||||
<div className="text-sm text-gray-600">
|
||||
Mostrando {filteredChecklists.length} de {checklists.length} checklists
|
||||
Mostrando {startIndex + 1}-{Math.min(endIndex, filteredChecklists.length)} de {filteredChecklists.length} checklists
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -2010,7 +2023,8 @@ function ChecklistsTab({ checklists, user, onChecklistCreated, onStartInspection
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
filteredChecklists.map((checklist) => (
|
||||
<>
|
||||
{paginatedChecklists.map((checklist) => (
|
||||
<div key={checklist.id} className="border border-gray-200 rounded-lg p-4 hover:shadow-md transition">
|
||||
<div className="flex justify-between items-start gap-4">
|
||||
{/* Logo del Checklist */}
|
||||
@@ -2110,7 +2124,58 @@ function ChecklistsTab({ checklists, user, onChecklistCreated, onStartInspection
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
))}
|
||||
|
||||
{/* Controles de paginación */}
|
||||
{filteredChecklists.length > itemsPerPage && (
|
||||
<div className="flex items-center justify-center gap-2 mt-6">
|
||||
<button
|
||||
onClick={() => setCurrentPage(prev => Math.max(1, prev - 1))}
|
||||
disabled={currentPage === 1}
|
||||
className="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
← Anterior
|
||||
</button>
|
||||
|
||||
<div className="flex gap-1">
|
||||
{[...Array(totalPages)].map((_, index) => {
|
||||
const page = index + 1
|
||||
// Mostrar solo páginas cercanas a la actual
|
||||
if (
|
||||
page === 1 ||
|
||||
page === totalPages ||
|
||||
(page >= currentPage - 1 && page <= currentPage + 1)
|
||||
) {
|
||||
return (
|
||||
<button
|
||||
key={page}
|
||||
onClick={() => setCurrentPage(page)}
|
||||
className={`px-3 py-2 rounded-lg ${
|
||||
currentPage === page
|
||||
? 'bg-blue-600 text-white'
|
||||
: 'border border-gray-300 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
{page}
|
||||
</button>
|
||||
)
|
||||
} else if (page === currentPage - 2 || page === currentPage + 2) {
|
||||
return <span key={page} className="px-2 py-2">...</span>
|
||||
}
|
||||
return null
|
||||
})}
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => setCurrentPage(prev => Math.min(totalPages, prev + 1))}
|
||||
disabled={currentPage === totalPages}
|
||||
className="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
Siguiente →
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Modal Gestionar Preguntas */}
|
||||
@@ -3173,6 +3238,8 @@ function InspectionsTab({ inspections, user, onUpdate }) {
|
||||
const [selectedInspection, setSelectedInspection] = useState(null)
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [statusFilter, setStatusFilter] = useState('all') // all, completed, draft
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
const itemsPerPage = 10
|
||||
|
||||
// Filtrar inspecciones
|
||||
const filteredInspections = inspections.filter(inspection => {
|
||||
@@ -3192,6 +3259,17 @@ function InspectionsTab({ inspections, user, onUpdate }) {
|
||||
return matchesSearch && matchesStatus
|
||||
})
|
||||
|
||||
// Calcular paginación
|
||||
const totalPages = Math.ceil(filteredInspections.length / itemsPerPage)
|
||||
const startIndex = (currentPage - 1) * itemsPerPage
|
||||
const endIndex = startIndex + itemsPerPage
|
||||
const paginatedInspections = filteredInspections.slice(startIndex, endIndex)
|
||||
|
||||
// Reset a página 1 cuando cambian los filtros
|
||||
useEffect(() => {
|
||||
setCurrentPage(1)
|
||||
}, [searchTerm, statusFilter])
|
||||
|
||||
if (inspections.length === 0) {
|
||||
return (
|
||||
<div className="text-center py-12">
|
||||
@@ -3231,7 +3309,7 @@ function InspectionsTab({ inspections, user, onUpdate }) {
|
||||
|
||||
{/* Contador de resultados */}
|
||||
<div className="text-sm text-gray-600">
|
||||
Mostrando {filteredInspections.length} de {inspections.length} inspecciones
|
||||
Mostrando {startIndex + 1}-{Math.min(endIndex, filteredInspections.length)} de {filteredInspections.length} inspecciones
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3251,7 +3329,7 @@ function InspectionsTab({ inspections, user, onUpdate }) {
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{filteredInspections.map((inspection) => (
|
||||
{paginatedInspections.map((inspection) => (
|
||||
<div key={inspection.id} className="border border-gray-200 rounded-lg p-4 hover:shadow-md transition">
|
||||
<div className="flex justify-between items-start">
|
||||
<div>
|
||||
@@ -3296,6 +3374,56 @@ function InspectionsTab({ inspections, user, onUpdate }) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Controles de paginación */}
|
||||
{filteredInspections.length > itemsPerPage && (
|
||||
<div className="flex items-center justify-center gap-2 mt-6">
|
||||
<button
|
||||
onClick={() => setCurrentPage(prev => Math.max(1, prev - 1))}
|
||||
disabled={currentPage === 1}
|
||||
className="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
← Anterior
|
||||
</button>
|
||||
|
||||
<div className="flex gap-1">
|
||||
{[...Array(totalPages)].map((_, index) => {
|
||||
const page = index + 1
|
||||
// Mostrar solo páginas cercanas a la actual
|
||||
if (
|
||||
page === 1 ||
|
||||
page === totalPages ||
|
||||
(page >= currentPage - 1 && page <= currentPage + 1)
|
||||
) {
|
||||
return (
|
||||
<button
|
||||
key={page}
|
||||
onClick={() => setCurrentPage(page)}
|
||||
className={`px-3 py-2 rounded-lg ${
|
||||
currentPage === page
|
||||
? 'bg-blue-600 text-white'
|
||||
: 'border border-gray-300 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
{page}
|
||||
</button>
|
||||
)
|
||||
} else if (page === currentPage - 2 || page === currentPage + 2) {
|
||||
return <span key={page} className="px-2 py-2">...</span>
|
||||
}
|
||||
return null
|
||||
})}
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => setCurrentPage(prev => Math.min(totalPages, prev + 1))}
|
||||
disabled={currentPage === totalPages}
|
||||
className="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
Siguiente →
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Modal de Detalle */}
|
||||
{selectedInspection && (
|
||||
<InspectionDetailModal
|
||||
@@ -4898,6 +5026,8 @@ function ReportsTab({ user }) {
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [mechanics, setMechanics] = useState([])
|
||||
const [checklists, setChecklists] = useState([])
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
const itemsPerPage = 10
|
||||
const [filters, setFilters] = useState({
|
||||
date: '',
|
||||
mechanicId: '',
|
||||
@@ -5023,6 +5153,7 @@ function ReportsTab({ user }) {
|
||||
const applyFilters = async () => {
|
||||
setLoading(true)
|
||||
setAppliedFilters(filters)
|
||||
setCurrentPage(1) // Reset a página 1 al aplicar filtros
|
||||
await Promise.all([loadDashboard(filters), loadInspections(filters)])
|
||||
setLoading(false)
|
||||
}
|
||||
@@ -5207,6 +5338,12 @@ function ReportsTab({ user }) {
|
||||
{inspections.length > 0 && (
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
<h3 className="text-lg font-semibold mb-4">📝 Inspecciones Recientes</h3>
|
||||
|
||||
{/* Información de paginación */}
|
||||
<div className="text-sm text-gray-600 mb-4">
|
||||
Mostrando {((currentPage - 1) * itemsPerPage) + 1}-{Math.min(currentPage * itemsPerPage, inspections.length)} de {inspections.length} inspecciones
|
||||
</div>
|
||||
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full">
|
||||
<thead>
|
||||
@@ -5222,7 +5359,7 @@ function ReportsTab({ user }) {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{inspections.slice(0, 20).map((inspection) => (
|
||||
{inspections.slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage).map((inspection) => (
|
||||
<tr key={inspection.id} className="border-b hover:bg-gray-50">
|
||||
<td className="py-2 px-4">
|
||||
{new Date(inspection.started_at).toLocaleDateString()}
|
||||
@@ -5286,6 +5423,57 @@ function ReportsTab({ user }) {
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* Controles de paginación */}
|
||||
{inspections.length > itemsPerPage && (
|
||||
<div className="flex items-center justify-center gap-2 mt-6">
|
||||
<button
|
||||
onClick={() => setCurrentPage(prev => Math.max(1, prev - 1))}
|
||||
disabled={currentPage === 1}
|
||||
className="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
← Anterior
|
||||
</button>
|
||||
|
||||
<div className="flex gap-1">
|
||||
{[...Array(Math.ceil(inspections.length / itemsPerPage))].map((_, index) => {
|
||||
const page = index + 1
|
||||
const totalPages = Math.ceil(inspections.length / itemsPerPage)
|
||||
// Mostrar solo páginas cercanas a la actual
|
||||
if (
|
||||
page === 1 ||
|
||||
page === totalPages ||
|
||||
(page >= currentPage - 1 && page <= currentPage + 1)
|
||||
) {
|
||||
return (
|
||||
<button
|
||||
key={page}
|
||||
onClick={() => setCurrentPage(page)}
|
||||
className={`px-3 py-2 rounded-lg ${
|
||||
currentPage === page
|
||||
? 'bg-blue-600 text-white'
|
||||
: 'border border-gray-300 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
{page}
|
||||
</button>
|
||||
)
|
||||
} else if (page === currentPage - 2 || page === currentPage + 2) {
|
||||
return <span key={page} className="px-2 py-2">...</span>
|
||||
}
|
||||
return null
|
||||
})}
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => setCurrentPage(prev => Math.min(Math.ceil(inspections.length / itemsPerPage), prev + 1))}
|
||||
disabled={currentPage === Math.ceil(inspections.length / itemsPerPage)}
|
||||
className="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
Siguiente →
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user