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:
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user