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:
2025-11-27 02:31:20 -03:00
parent e3ac1c84d7
commit ef9c37dcdd
3 changed files with 243 additions and 7 deletions

View File

@@ -360,6 +360,54 @@ UPDATE checklists SET max_score = (
MIT License - Uso libre para proyectos comerciales y personales MIT License - Uso libre para proyectos comerciales y personales
## 📝 Control de Versiones
### Instrucciones para commits de Git
**IMPORTANTE**: Siempre incluir la versión actualizada en los mensajes de commit.
Formato recomendado:
```bash
git add .
git commit -m "tipo: descripción del cambio
- Detalle 1
- Detalle 2
- Frontend vX.X.XX / Backend vX.X.XX"
```
Tipos de commit:
- `feat`: Nueva funcionalidad
- `fix`: Corrección de bugs
- `refactor`: Refactorización de código
- `style`: Cambios de formato/estilo
- `docs`: Actualización de documentación
- `perf`: Mejoras de rendimiento
- `test`: Añadir o actualizar tests
**Ejemplo real**:
```bash
git add .
git commit -m "feat: Add pagination (10 items/page) to all main tabs
- Pagination for Inspections, Checklists, and Reports
- Auto-reset on filter changes
- Smart page navigation with ellipsis
- Result counters showing X-Y of Z items
- Frontend v1.0.64"
```
### Versionado
Seguir **Semantic Versioning** (MAJOR.MINOR.PATCH):
- **MAJOR**: Cambios incompatibles en la API
- **MINOR**: Nueva funcionalidad compatible con versiones anteriores
- **PATCH**: Correcciones de bugs
Ubicación de versiones:
- Frontend: `frontend/package.json``"version": "X.X.XX"`
- Backend: `backend/app/main.py``version="X.X.XX"` en FastAPI app
## 🆘 Soporte ## 🆘 Soporte
Para problemas o preguntas: Para problemas o preguntas:

View File

@@ -1,7 +1,7 @@
{ {
"name": "checklist-frontend", "name": "checklist-frontend",
"private": true, "private": true,
"version": "1.0.63", "version": "1.0.64",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@@ -1735,6 +1735,8 @@ function ChecklistsTab({ checklists, user, onChecklistCreated, onStartInspection
const [mechanics, setMechanics] = useState([]) const [mechanics, setMechanics] = useState([])
const [searchTerm, setSearchTerm] = useState('') const [searchTerm, setSearchTerm] = useState('')
const [aiModeFilter, setAiModeFilter] = useState('all') // all, off, optional, required const [aiModeFilter, setAiModeFilter] = useState('all') // all, off, optional, required
const [currentPage, setCurrentPage] = useState(1)
const itemsPerPage = 10
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
name: '', name: '',
description: '', description: '',
@@ -1783,6 +1785,17 @@ function ChecklistsTab({ checklists, user, onChecklistCreated, onStartInspection
return matchesSearch && matchesAiMode 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) => { const handleCreate = async (e) => {
e.preventDefault() e.preventDefault()
setCreating(true) setCreating(true)
@@ -1969,7 +1982,7 @@ function ChecklistsTab({ checklists, user, onChecklistCreated, onStartInspection
{/* Contador de resultados */} {/* Contador de resultados */}
<div className="text-sm text-gray-600"> <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>
</div> </div>
)} )}
@@ -2010,7 +2023,8 @@ function ChecklistsTab({ checklists, user, onChecklistCreated, onStartInspection
</button> </button>
</div> </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 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"> <div className="flex justify-between items-start gap-4">
{/* Logo del Checklist */} {/* Logo del Checklist */}
@@ -2110,7 +2124,58 @@ function ChecklistsTab({ checklists, user, onChecklistCreated, onStartInspection
</div> </div>
</div> </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 */} {/* Modal Gestionar Preguntas */}
@@ -3173,6 +3238,8 @@ function InspectionsTab({ inspections, user, onUpdate }) {
const [selectedInspection, setSelectedInspection] = useState(null) const [selectedInspection, setSelectedInspection] = useState(null)
const [searchTerm, setSearchTerm] = useState('') const [searchTerm, setSearchTerm] = useState('')
const [statusFilter, setStatusFilter] = useState('all') // all, completed, draft const [statusFilter, setStatusFilter] = useState('all') // all, completed, draft
const [currentPage, setCurrentPage] = useState(1)
const itemsPerPage = 10
// Filtrar inspecciones // Filtrar inspecciones
const filteredInspections = inspections.filter(inspection => { const filteredInspections = inspections.filter(inspection => {
@@ -3192,6 +3259,17 @@ function InspectionsTab({ inspections, user, onUpdate }) {
return matchesSearch && matchesStatus 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) { if (inspections.length === 0) {
return ( return (
<div className="text-center py-12"> <div className="text-center py-12">
@@ -3231,7 +3309,7 @@ function InspectionsTab({ inspections, user, onUpdate }) {
{/* Contador de resultados */} {/* Contador de resultados */}
<div className="text-sm text-gray-600"> <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>
</div> </div>
@@ -3251,7 +3329,7 @@ function InspectionsTab({ inspections, user, onUpdate }) {
</div> </div>
) : ( ) : (
<div className="space-y-4"> <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 key={inspection.id} className="border border-gray-200 rounded-lg p-4 hover:shadow-md transition">
<div className="flex justify-between items-start"> <div className="flex justify-between items-start">
<div> <div>
@@ -3296,6 +3374,56 @@ function InspectionsTab({ inspections, user, onUpdate }) {
</div> </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 */} {/* Modal de Detalle */}
{selectedInspection && ( {selectedInspection && (
<InspectionDetailModal <InspectionDetailModal
@@ -4898,6 +5026,8 @@ function ReportsTab({ user }) {
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [mechanics, setMechanics] = useState([]) const [mechanics, setMechanics] = useState([])
const [checklists, setChecklists] = useState([]) const [checklists, setChecklists] = useState([])
const [currentPage, setCurrentPage] = useState(1)
const itemsPerPage = 10
const [filters, setFilters] = useState({ const [filters, setFilters] = useState({
date: '', date: '',
mechanicId: '', mechanicId: '',
@@ -5023,6 +5153,7 @@ function ReportsTab({ user }) {
const applyFilters = async () => { const applyFilters = async () => {
setLoading(true) setLoading(true)
setAppliedFilters(filters) setAppliedFilters(filters)
setCurrentPage(1) // Reset a página 1 al aplicar filtros
await Promise.all([loadDashboard(filters), loadInspections(filters)]) await Promise.all([loadDashboard(filters), loadInspections(filters)])
setLoading(false) setLoading(false)
} }
@@ -5207,6 +5338,12 @@ function ReportsTab({ user }) {
{inspections.length > 0 && ( {inspections.length > 0 && (
<div className="bg-white rounded-lg shadow p-6"> <div className="bg-white rounded-lg shadow p-6">
<h3 className="text-lg font-semibold mb-4">📝 Inspecciones Recientes</h3> <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"> <div className="overflow-x-auto">
<table className="min-w-full"> <table className="min-w-full">
<thead> <thead>
@@ -5222,7 +5359,7 @@ function ReportsTab({ user }) {
</tr> </tr>
</thead> </thead>
<tbody> <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"> <tr key={inspection.id} className="border-b hover:bg-gray-50">
<td className="py-2 px-4"> <td className="py-2 px-4">
{new Date(inspection.started_at).toLocaleDateString()} {new Date(inspection.started_at).toLocaleDateString()}
@@ -5286,6 +5423,57 @@ function ReportsTab({ user }) {
</tbody> </tbody>
</table> </table>
</div> </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>
)} )}
</div> </div>