Cambios realizados:

Estados de filtros actualizados - Reemplazado date por dateFrom y dateTo

API calls modificadas - Ahora loadDashboard() y loadInspections() envían start_date y end_date separados

UI mejorada - Grid de 6 columnas con:

Fecha Desde / Fecha Hasta (selectores de fecha)
Mecánico (solo admins)
Checklist
Matrícula
Botón Aplicar + botón Limpiar (🗑️) que aparece solo cuando hay filtros activos
Consistencia - Mismo patrón de diseño que el módulo Inspecciones para experiencia uniforme

Características:
 Filtrado por rango de fechas (desde - hasta)
 Paginación (10 items por página, se resetea al filtrar)
 Botón limpiar filtros condicional
 Labels descriptivos en español
 Compatibilidad con filtros existentes (mecánico, checklist, matrícula)

Los módulos de Inspecciones y Reportes ahora tienen el mismo sistema de filtrado avanzado.
This commit is contained in:
2025-12-08 18:02:38 -03:00
parent 216b9ceb20
commit 95543d5858

View File

@@ -4156,6 +4156,8 @@ function InspectionsTab({ inspections, user, onUpdate, onContinue }) {
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, incomplete const [statusFilter, setStatusFilter] = useState('all') // all, completed, incomplete
const [dateFrom, setDateFrom] = useState('')
const [dateTo, setDateTo] = useState('')
const [currentPage, setCurrentPage] = useState(1) const [currentPage, setCurrentPage] = useState(1)
const itemsPerPage = 10 const itemsPerPage = 10
@@ -4174,7 +4176,23 @@ function InspectionsTab({ inspections, user, onUpdate, onContinue }) {
(statusFilter === 'completed' && inspection.status === 'completed') || (statusFilter === 'completed' && inspection.status === 'completed') ||
(statusFilter === 'incomplete' && inspection.status !== 'completed') (statusFilter === 'incomplete' && inspection.status !== 'completed')
return matchesSearch && matchesStatus // Filtro por rango de fechas
let matchesDate = true
if (dateFrom || dateTo) {
const inspectionDate = new Date(inspection.created_at)
if (dateFrom) {
const fromDate = new Date(dateFrom)
fromDate.setHours(0, 0, 0, 0)
matchesDate = matchesDate && inspectionDate >= fromDate
}
if (dateTo) {
const toDate = new Date(dateTo)
toDate.setHours(23, 59, 59, 999)
matchesDate = matchesDate && inspectionDate <= toDate
}
}
return matchesSearch && matchesStatus && matchesDate
}) })
// Calcular paginación // Calcular paginación
@@ -4186,7 +4204,7 @@ function InspectionsTab({ inspections, user, onUpdate, onContinue }) {
// Reset a página 1 cuando cambian los filtros // Reset a página 1 cuando cambian los filtros
useEffect(() => { useEffect(() => {
setCurrentPage(1) setCurrentPage(1)
}, [searchTerm, statusFilter]) }, [searchTerm, statusFilter, dateFrom, dateTo])
if (inspections.length === 0) { if (inspections.length === 0) {
return ( return (
@@ -4201,12 +4219,13 @@ function InspectionsTab({ inspections, user, onUpdate, onContinue }) {
<> <>
{/* Buscador y Filtros */} {/* Buscador y Filtros */}
<div className="mb-6 space-y-4"> <div className="mb-6 space-y-4">
<div className="flex gap-4 flex-wrap"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{/* Buscador */} {/* Buscador */}
<div className="flex-1 min-w-[300px]"> <div className="lg:col-span-2">
<label className="block text-xs font-medium text-gray-700 mb-1">Buscar</label>
<input <input
type="text" type="text"
placeholder="Buscar por placa, marca, modelo, Nº pedido, OR o ID..." placeholder="Placa, marca, modelo, Nº pedido, OR o ID..."
value={searchTerm} value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
@@ -4214,36 +4233,92 @@ function InspectionsTab({ inspections, user, onUpdate, onContinue }) {
</div> </div>
{/* Filtro de Estado */} {/* Filtro de Estado */}
<select <div>
value={statusFilter} <label className="block text-xs font-medium text-gray-700 mb-1">Estado</label>
onChange={(e) => setStatusFilter(e.target.value)} <select
className="px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" value={statusFilter}
> onChange={(e) => setStatusFilter(e.target.value)}
<option value="all">Todos los estados</option> className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
<option value="completed">Completadas</option> >
<option value="incomplete">Incompletas</option> <option value="all">Todos</option>
</select> <option value="completed">Completadas</option>
<option value="incomplete">Incompletas</option>
</select>
</div>
{/* Filtro por fecha desde */}
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">Fecha desde</label>
<input
type="date"
value={dateFrom}
onChange={(e) => setDateFrom(e.target.value)}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
</div>
{/* Segunda fila de filtros */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{/* Filtro por fecha hasta */}
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">Fecha hasta</label>
<input
type="date"
value={dateTo}
onChange={(e) => setDateTo(e.target.value)}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
{/* Botón limpiar filtros */}
{(searchTerm || statusFilter !== 'all' || dateFrom || dateTo) && (
<div className="flex items-end">
<button
onClick={() => {
setSearchTerm('')
setStatusFilter('all')
setDateFrom('')
setDateTo('')
}}
className="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition"
>
🗑 Limpiar filtros
</button>
</div>
)}
</div> </div>
{/* Contador de resultados */} {/* Contador de resultados */}
<div className="text-sm text-gray-600"> <div className="flex justify-between items-center text-sm text-gray-600">
Mostrando {startIndex + 1}-{Math.min(endIndex, filteredInspections.length)} de {filteredInspections.length} inspecciones <span>
Mostrando {filteredInspections.length > 0 ? startIndex + 1 : 0}-{Math.min(endIndex, filteredInspections.length)} de {filteredInspections.length} inspecciones
</span>
{filteredInspections.length > 0 && (
<span className="text-xs text-gray-500">
Página {currentPage} de {totalPages}
</span>
)}
</div> </div>
</div> </div>
{/* Lista de Inspecciones */} {/* Lista de Inspecciones */}
{filteredInspections.length === 0 ? ( {filteredInspections.length === 0 ? (
<div className="text-center py-12"> <div className="text-center py-12">
<p className="text-gray-500">No se encontraron inspecciones con los filtros aplicados</p> <p className="text-gray-500">No se encontraron inspecciones</p>
<button {(searchTerm || statusFilter !== 'all' || dateFrom || dateTo) && (
onClick={() => { <button
setSearchTerm('') onClick={() => {
setStatusFilter('all') setSearchTerm('')
}} setStatusFilter('all')
className="mt-4 text-blue-600 hover:text-blue-700 underline" setDateFrom('')
> setDateTo('')
Limpiar filtros }}
</button> className="mt-4 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition"
>
Limpiar filtros
</button>
)}
</div> </div>
) : ( ) : (
<div className="space-y-4"> <div className="space-y-4">
@@ -4280,11 +4355,11 @@ function InspectionsTab({ inspections, user, onUpdate, onContinue }) {
)} )}
</div> </div>
{/* Fechas de inspección */} {/* Fechas de inspección */}
<div className="flex flex-wrap gap-4 mt-2 text-xs text-gray-500"> <div className="flex flex-wrap gap-3 mt-2 text-xs">
{inspection.created_at && ( {inspection.created_at && (
<span className="flex items-center gap-1"> <span className="flex items-center gap-1 text-gray-600">
<span>📅</span> <span>📅</span>
<span>Iniciada: <strong>{new Date(inspection.created_at).toLocaleDateString('es-ES', { <span>Inicio: <strong>{new Date(inspection.created_at).toLocaleDateString('es-ES', {
day: '2-digit', day: '2-digit',
month: '2-digit', month: '2-digit',
year: 'numeric', year: 'numeric',
@@ -6812,13 +6887,15 @@ function ReportsTab({ user }) {
const [currentPage, setCurrentPage] = useState(1) const [currentPage, setCurrentPage] = useState(1)
const itemsPerPage = 10 const itemsPerPage = 10
const [filters, setFilters] = useState({ const [filters, setFilters] = useState({
date: '', dateFrom: '',
dateTo: '',
mechanicId: '', mechanicId: '',
checklistId: '', checklistId: '',
vehiclePlate: '' vehiclePlate: ''
}) })
const [appliedFilters, setAppliedFilters] = useState({ const [appliedFilters, setAppliedFilters] = useState({
date: '', dateFrom: '',
dateTo: '',
mechanicId: '', mechanicId: '',
checklistId: '', checklistId: '',
vehiclePlate: '' vehiclePlate: ''
@@ -6862,8 +6939,11 @@ function ReportsTab({ user }) {
const API_URL = import.meta.env.VITE_API_URL || '' const API_URL = import.meta.env.VITE_API_URL || ''
let url = `${API_URL}/api/reports/dashboard?` let url = `${API_URL}/api/reports/dashboard?`
if (filtersToApply.date) { if (filtersToApply.dateFrom) {
url += `start_date=${filtersToApply.date}&end_date=${filtersToApply.date}&` url += `start_date=${filtersToApply.dateFrom}&`
}
if (filtersToApply.dateTo) {
url += `end_date=${filtersToApply.dateTo}&`
} }
if (filtersToApply.mechanicId) url += `mechanic_id=${filtersToApply.mechanicId}&` if (filtersToApply.mechanicId) url += `mechanic_id=${filtersToApply.mechanicId}&`
@@ -6892,8 +6972,11 @@ function ReportsTab({ user }) {
const API_URL = import.meta.env.VITE_API_URL || '' const API_URL = import.meta.env.VITE_API_URL || ''
let url = `${API_URL}/api/reports/inspections?` let url = `${API_URL}/api/reports/inspections?`
if (filtersToApply.date) { if (filtersToApply.dateFrom) {
url += `start_date=${filtersToApply.date}&end_date=${filtersToApply.date}&` url += `start_date=${filtersToApply.dateFrom}&`
}
if (filtersToApply.dateTo) {
url += `end_date=${filtersToApply.dateTo}&`
} }
if (filtersToApply.mechanicId) url += `mechanic_id=${filtersToApply.mechanicId}&` if (filtersToApply.mechanicId) url += `mechanic_id=${filtersToApply.mechanicId}&`
@@ -6947,7 +7030,7 @@ function ReportsTab({ user }) {
// Cargar mecánicos y checklists primero // Cargar mecánicos y checklists primero
await Promise.all([loadMechanics(), loadChecklists()]) await Promise.all([loadMechanics(), loadChecklists()])
// Luego cargar datos sin filtros (filtros vacíos = todos los datos) // Luego cargar datos sin filtros (filtros vacíos = todos los datos)
const emptyFilters = { date: '', mechanicId: '', checklistId: '', vehiclePlate: '' } const emptyFilters = { dateFrom: '', dateTo: '', mechanicId: '', checklistId: '', vehiclePlate: '' }
await Promise.all([loadDashboard(emptyFilters), loadInspections(emptyFilters)]) await Promise.all([loadDashboard(emptyFilters), loadInspections(emptyFilters)])
setLoading(false) setLoading(false)
} }
@@ -6967,15 +7050,26 @@ function ReportsTab({ user }) {
{/* Filtros */} {/* Filtros */}
<div className="bg-white rounded-lg shadow p-4"> <div className="bg-white rounded-lg shadow p-4">
<h3 className="text-lg font-semibold mb-4">🔍 Filtros</h3> <h3 className="text-lg font-semibold mb-4">🔍 Filtros</h3>
<div className="grid grid-cols-1 md:grid-cols-5 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-6 gap-4">
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-1"> <label className="block text-sm font-medium text-gray-700 mb-1">
Fecha Fecha Desde
</label> </label>
<input <input
type="date" type="date"
value={filters.date} value={filters.dateFrom}
onChange={(e) => setFilters({...filters, date: e.target.value})} onChange={(e) => setFilters({...filters, dateFrom: e.target.value})}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Fecha Hasta
</label>
<input
type="date"
value={filters.dateTo}
onChange={(e) => setFilters({...filters, dateTo: e.target.value})}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500" className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500"
/> />
</div> </div>
@@ -6989,10 +7083,10 @@ function ReportsTab({ user }) {
onChange={(e) => setFilters({...filters, mechanicId: e.target.value})} onChange={(e) => setFilters({...filters, mechanicId: e.target.value})}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500" className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500"
> >
<option value="">Todos los mecánicos</option> <option value="">Todos</option>
{mechanics.map(mechanic => ( {mechanics.map(mechanic => (
<option key={mechanic.id} value={mechanic.id}> <option key={mechanic.id} value={mechanic.id}>
{mechanic.full_name || mechanic.username} ({mechanic.role}) {mechanic.full_name || mechanic.username}
</option> </option>
))} ))}
</select> </select>
@@ -7007,7 +7101,7 @@ function ReportsTab({ user }) {
onChange={(e) => setFilters({...filters, checklistId: e.target.value})} onChange={(e) => setFilters({...filters, checklistId: e.target.value})}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500" className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500"
> >
<option value="">Todos los checklists</option> <option value="">Todos</option>
{checklists.map(checklist => ( {checklists.map(checklist => (
<option key={checklist.id} value={checklist.id}> <option key={checklist.id} value={checklist.id}>
{checklist.name} {checklist.name}
@@ -7027,13 +7121,29 @@ function ReportsTab({ user }) {
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500" className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500"
/> />
</div> </div>
<div className="flex items-end"> <div className="flex items-end gap-2">
<button <button
onClick={applyFilters} onClick={applyFilters}
className="w-full px-4 py-2 bg-gradient-to-r from-indigo-600 to-purple-600 text-white rounded-lg hover:from-indigo-700 hover:to-purple-700 transition" className="flex-1 px-4 py-2 bg-gradient-to-r from-indigo-600 to-purple-600 text-white rounded-lg hover:from-indigo-700 hover:to-purple-700 transition"
> >
Aplicar Filtros Aplicar
</button> </button>
{(filters.dateFrom || filters.dateTo || filters.mechanicId || filters.checklistId || filters.vehiclePlate) && (
<button
onClick={() => {
setFilters({ dateFrom: '', dateTo: '', mechanicId: '', checklistId: '', vehiclePlate: '' })
setAppliedFilters({ dateFrom: '', dateTo: '', mechanicId: '', checklistId: '', vehiclePlate: '' })
setCurrentPage(1)
const emptyFilters = { dateFrom: '', dateTo: '', mechanicId: '', checklistId: '', vehiclePlate: '' }
loadDashboard(emptyFilters)
loadInspections(emptyFilters)
}}
className="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition"
title="Limpiar filtros"
>
🗑
</button>
)}
</div> </div>
</div> </div>
</div> </div>