useVehicles Hook
The useVehicles hook manages the complete vehicle fleet including listing, search, pagination, modals, and integration with service history and sales.
Import
import { useVehicles } from '@hooks/useVehicles';
Basic Usage
import { useVehicles } from '@hooks/useVehicles';
function VehiclesPage() {
const {
currentVehicles,
searchTerm,
handleSearch,
openModal
} = useVehicles();
return (
<div>
<input
value={searchTerm}
onChange={handleSearch}
placeholder="Search vehicles..."
/>
{currentVehicles.map(vehicle => (
<div key={vehicle.id} onClick={() => openModal('details', vehicle)}>
{vehicle.placa} - {vehicle.marca} {vehicle.modelo}
</div>
))}
</div>
);
}
Return Value
Search and Filtering
Current search query string.
handleSearch
(e: React.ChangeEvent<HTMLInputElement>) => void
Handles search input changes and resets pagination to page 1.Searches across:
- License plate (placa)
- Customer name (clienteNombre)
- Vehicle brand (marca)
- Vehicle model (modelo)
Vehicle Data
Array of vehicles for the current page (filtered and paginated).interface Vehicle {
id: string;
placa: string;
marca: string;
modelo: string;
anio: number;
color?: string;
propietarioId: string;
conductorHabitualId?: string;
// Computed fields
clienteNombre: string;
clienteDocumento: string;
choferNombre: string;
notas: string;
kmActual: number;
kmProximo: number;
historial: SaleHistory[];
}
Total number of vehicles after filtering.
Current page number (1-indexed).
Function to change the current page.
Total number of pages based on filtered results and items per page.
Number of vehicles displayed per page (default: 5).
handleItemsPerPageChange
(e: React.ChangeEvent<HTMLSelectElement>) => void
Changes items per page and resets to page 1.
Modal Management
Currently open modal type.type ModalType = 'details' | 'add' | 'edit' | 'delete' | null;
The vehicle object currently selected in a modal.
openModal
(type: ModalType, vehicle?: any) => void
Opens a modal with the specified type and optional vehicle data.Parameters:
type - The modal type to open
vehicle (optional) - Vehicle data to display/edit
Closes the active modal and clears selected vehicle.
Data Operations
List of all customers for dropdown selection in forms.
Saves a new or updated vehicle (currently mock implementation).Parameters:
data - Vehicle data to save
Deletes a vehicle by ID (currently mock implementation).Parameters:
id - Vehicle ID to delete
handleEmitirFactura
(vehicle: any, pastSale?: any) => void
Navigates to sales page with pre-filled vehicle data.Parameters:
vehicle - The vehicle to create a sale for
pastSale (optional) - Previous sale to repeat
Behavior:
- If
pastSale provided, pre-fills cart with those products
- Sets vehicle plate and mileage
- Links customer and driver
- Navigates to /sales route with state
Examples
Complete Vehicle Management Page
import { useVehicles } from '@hooks/useVehicles';
import { Input } from '@components/ui/Input';
import { Select } from '@components/ui/Select';
import { Button } from '@components/ui/Button';
function VehiclesPage() {
const {
currentVehicles,
searchTerm,
handleSearch,
currentPage,
totalPages,
setCurrentPage,
itemsPerPage,
handleItemsPerPageChange,
totalItems,
openModal,
activeModal,
selectedVehicle,
closeModal
} = useVehicles();
return (
<div>
{/* Search and Controls */}
<div>
<Input
placeholder="Search by plate, customer, brand, model..."
value={searchTerm}
onChange={handleSearch}
/>
<Select value={itemsPerPage} onChange={handleItemsPerPageChange}>
<option value={5}>5 per page</option>
<option value={10}>10 per page</option>
<option value={25}>25 per page</option>
</Select>
<Button onClick={() => openModal('add')}>
Add Vehicle
</Button>
</div>
{/* Results Summary */}
<p>Showing {currentVehicles.length} of {totalItems} vehicles</p>
{/* Vehicle Table */}
<table>
<thead>
<tr>
<th>Plate</th>
<th>Vehicle</th>
<th>Owner</th>
<th>Mileage</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{currentVehicles.map(vehicle => (
<tr key={vehicle.id}>
<td>{vehicle.placa}</td>
<td>{vehicle.marca} {vehicle.modelo} ({vehicle.anio})</td>
<td>{vehicle.clienteNombre}</td>
<td>{vehicle.kmActual.toLocaleString()} km</td>
<td>
<Button
size="sm"
onClick={() => openModal('details', vehicle)}
>
Details
</Button>
<Button
size="sm"
variant="secondary"
onClick={() => openModal('edit', vehicle)}
>
Edit
</Button>
<Button
size="sm"
variant="danger"
onClick={() => openModal('delete', vehicle)}
>
Delete
</Button>
</td>
</tr>
))}
</tbody>
</table>
{/* Pagination */}
<div>
<Button
disabled={currentPage === 1}
onClick={() => setCurrentPage(currentPage - 1)}
>
Previous
</Button>
<span>Page {currentPage} of {totalPages}</span>
<Button
disabled={currentPage === totalPages}
onClick={() => setCurrentPage(currentPage + 1)}
>
Next
</Button>
</div>
</div>
);
}
Vehicle Details with Service History
import { useVehicles } from '@hooks/useVehicles';
import { VehicleDetailsModal } from '@components/vehicles/VehicleDetailsModal';
function VehicleDetailsView() {
const {
selectedVehicle,
activeModal,
closeModal,
handleEmitirFactura
} = useVehicles();
return (
<VehicleDetailsModal
vehicle={selectedVehicle}
onClose={closeModal}
onEmitFactura={handleEmitirFactura}
/>
);
}
// The modal displays:
// - Vehicle info (owner, specs, current mileage)
// - Complete service history
// - "Repeat Service" button for each past service
// - "New Sale" button for blank invoice
Quick Sale from Vehicle
import { useVehicles } from '@hooks/useVehicles';
function VehicleQuickActions({ vehicle }) {
const { handleEmitirFactura } = useVehicles();
return (
<div>
{/* Create new blank sale */}
<Button onClick={() => handleEmitirFactura(vehicle)}>
New Service
</Button>
{/* Repeat last service */}
{vehicle.historial[0] && (
<Button onClick={() => handleEmitirFactura(vehicle, vehicle.historial[0])}>
Repeat Last Service
</Button>
)}
</div>
);
}
Filtering Example
import { useVehicles } from '@hooks/useVehicles';
function FilteredVehicleList() {
const { currentVehicles, handleSearch } = useVehicles();
// Search works across multiple fields
return (
<div>
<Input
placeholder="Search..."
onChange={handleSearch}
/>
{/* Searches in:
- vehicle.placa: "ABC-123"
- vehicle.clienteNombre: "Transport Company S.A."
- vehicle.marca: "TOYOTA"
- vehicle.modelo: "HILUX"
*/}
{currentVehicles.map(v => (
<div key={v.id}>{v.placa} - {v.marca}</div>
))}
</div>
);
}
import { useVehicles } from '@hooks/useVehicles';
function PaginationControls() {
const {
currentPage,
totalPages,
setCurrentPage,
itemsPerPage,
handleItemsPerPageChange,
totalItems
} = useVehicles();
const startItem = (currentPage - 1) * itemsPerPage + 1;
const endItem = Math.min(currentPage * itemsPerPage, totalItems);
return (
<div>
<p>Showing {startItem}-{endItem} of {totalItems} vehicles</p>
{/* Page numbers */}
<div>
{Array.from({ length: totalPages }, (_, i) => i + 1).map(page => (
<button
key={page}
onClick={() => setCurrentPage(page)}
disabled={page === currentPage}
>
{page}
</button>
))}
</div>
{/* Items per page */}
<Select value={itemsPerPage} onChange={handleItemsPerPageChange}>
<option value={5}>5</option>
<option value={10}>10</option>
<option value={25}>25</option>
<option value={50}>50</option>
</Select>
</div>
);
}
Data Relationships
The hook automatically maps related data:
// Vehicle → Customer (Owner)
clienteNombre = customers.find(c => c.id === vehicle.propietarioId)?.nombreRazonSocial
// Vehicle → Customer (Driver)
choferNombre = customers.find(c => c.id === vehicle.conductorHabitualId)?.nombreRazonSocial
// Vehicle → Sales → Products
historial = sales
.filter(s => s.vehicleId === vehicle.id)
.map(sale => ({
...sale,
items: saleDetails
.filter(si => si.saleId === sale.id)
.map(si => ({
...si,
productName: products.find(p => p.id === si.productId)?.nombre
}))
}))
Mileage Tracking
Current mileage is taken from the most recent service:
const latestSale = sales
.filter(s => s.vehicleId === vehicle.id)
.sort((a, b) => new Date(b.fechaEmision) - new Date(a.fechaEmision))[0];
kmActual = latestSale?.kilometrajeIngreso || vehicle.kilometrajeActual || 0;
kmProximo = latestSale?.proximoCambioKm || 0;
Service History Structure
interface SaleHistory {
id: string;
tipoComprobante: string; // 'FACTURA' or 'BOLETA'
serie: string;
correlativo: string;
fechaEmision: string;
total: number;
kilometrajeIngreso: number;
proximoCambioKm: number;
items: {
id: string;
productId: string;
productName: string;
cantidad: number;
precioUnitario: number;
}[];
}
Navigation with Pre-fill
When creating a sale from a vehicle:
navigate('/sales', {
state: {
prefillData: {
vehicleId: vehicle.id,
customerId: vehicle.propietarioId,
choferId: vehicle.conductorHabitualId,
placa: vehicle.placa,
cartItems: productosParaCart, // Pre-filled products
observacionSugerida: "...",
kilometrajeActual: vehicle.kmActual,
kmProximo: pastSale?.proximoCambioKm || 0
}
}
});
The hook filters out soft-deleted vehicles (where isDeleted: true).
Use handleEmitirFactura with a pastSale parameter to quickly repeat common services for returning vehicles.
Source Code
Location: /home/daytona/workspace/source/src/hooks/useVehicles.ts:1