Skip to main content

Modal Components

MotorDesk uses modal dialogs for various user interactions including forms, detail views, and delete confirmations. All modals follow a consistent pattern with overlay, content area, header, body, and footer sections. All modals in MotorDesk share a common structure:
  1. Overlay - Semi-transparent background that covers the main content
  2. Modal Content - The main dialog box
  3. Header - Title and close button
  4. Body - Main content area (forms, details, etc.)
  5. Footer - Action buttons

Common Modal Props

Most modals share these common props:
isOpen
boolean
required
Controls whether the modal is visible.
onClose
() => void
required
Callback function called when the modal should close (clicking overlay or close button).

CustomerFormModal

Modal for adding or editing customer information with API validation support.

Import

import { CustomerFormModal } from '@components/customers/CustomerFormModal';

Props

isOpen
boolean
required
Controls modal visibility.
onClose
() => void
required
Called when the modal should close.
mode
'add' | 'edit'
required
Determines if the modal is for adding a new customer or editing an existing one.
formData
any
required
Object containing the current form field values.
onChange
(e: any) => void
required
Handler for form field changes.
onSearchApi
() => void
required
Handler for API lookup button (validates RUC/DNI via external API).
isSearching
boolean
required
Indicates if API search is in progress.
apiSuccess
boolean
required
Indicates if API validation was successful.

Usage Example

import { useState } from 'react';
import { CustomerFormModal } from '@components/customers/CustomerFormModal';

function CustomersPage() {
  const [isOpen, setIsOpen] = useState(false);
  const [isSearching, setIsSearching] = useState(false);
  const [apiSuccess, setApiSuccess] = useState(false);
  const [formData, setFormData] = useState({
    tipoDocumentoIdentidad: 'RUC',
    numeroDocumento: '',
    nombreRazonSocial: '',
    direccion: '',
    telefono: '',
    email: ''
  });
  
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setFormData({
      ...formData,
      [e.target.name]: e.target.value
    });
  };
  
  const handleSearchApi = async () => {
    setIsSearching(true);
    // Call external API for RUC/DNI validation
    const result = await validateDocument(formData.numeroDocumento);
    if (result) {
      setFormData({
        ...formData,
        nombreRazonSocial: result.name,
        direccion: result.address
      });
      setApiSuccess(true);
    }
    setIsSearching(false);
  };
  
  return (
    <>
      <button onClick={() => setIsOpen(true)}>Add Customer</button>
      
      <CustomerFormModal
        isOpen={isOpen}
        onClose={() => setIsOpen(false)}
        mode="add"
        formData={formData}
        onChange={handleChange}
        onSearchApi={handleSearchApi}
        isSearching={isSearching}
        apiSuccess={apiSuccess}
      />
    </>
  );
}

Form Fields

The CustomerFormModal includes these fields:
  • Tipo de Documento - Select dropdown (RUC, DNI, CE)
  • Número de Documento - Text input with API search button
  • Nombres o Razón Social - Text input (can be auto-filled by API)
  • Dirección Fiscal - Text input (can be auto-filled by API)
  • Teléfono - Text input
  • Email - Text input

VehicleDetailsModal

Modal displaying complete vehicle information and service history.

Import

import { VehicleDetailsModal } from '@components/vehicles/VehicleDetailsModal';

Props

vehicle
any
required
The vehicle object containing all vehicle data and history.
onClose
() => void
required
Called when the modal should close.
onEmitFactura
(vehicle: any, pastSale?: any) => void
required
Called when user wants to create a new sale or repeat a previous service.

Usage Example

import { useState } from 'react';
import { VehicleDetailsModal } from '@components/vehicles/VehicleDetailsModal';

function VehiclesPage() {
  const [selectedVehicle, setSelectedVehicle] = useState(null);
  
  const handleEmitFactura = (vehicle: any, pastSale?: any) => {
    // Navigate to sales page with pre-filled data
    navigate('/sales', {
      state: {
        prefillData: {
          vehicleId: vehicle.id,
          customerId: vehicle.propietarioId,
          placa: vehicle.placa,
          cartItems: pastSale?.items || [],
          kilometrajeActual: vehicle.kmActual
        }
      }
    });
  };
  
  return (
    <>
      <button onClick={() => setSelectedVehicle(vehicle)}>
        View Details
      </button>
      
      {selectedVehicle && (
        <VehicleDetailsModal
          vehicle={selectedVehicle}
          onClose={() => setSelectedVehicle(null)}
          onEmitFactura={handleEmitFactura}
        />
      )}
    </>
  );
}

Vehicle Object Structure

interface Vehicle {
  id: string;
  placa: string;
  marca: string;
  modelo: string;
  anio: number;
  color?: string;
  clienteNombre: string;
  clienteDocumento: string;
  choferNombre?: string;
  kmActual: number;
  kmProximo: number;
  historial: SaleHistory[];
}

interface SaleHistory {
  id: string;
  tipoComprobante: string;
  serie: string;
  correlativo: string;
  fechaEmision: string;
  total: number;
  kilometrajeIngreso: number;
  proximoCambioKm: number;
  items: SaleItem[];
}

Features

  • Displays vehicle information (owner, specs, current mileage)
  • Shows complete service history chronologically
  • Each service shows products/services performed
  • Tracks mileage at each service
  • “Repeat Service” button to recreate past sales
  • “New Sale” button to create blank invoice

CustomerDeleteModal

Confirmation modal for deleting customers.

Import

import { CustomerDeleteModal } from '@components/customers/CustomerDeleteModal';

Props

isOpen
boolean
required
Controls modal visibility.
onClose
() => void
required
Called when user cancels deletion.
onConfirm
() => void
required
Called when user confirms deletion.
customer
any
required
The customer object to be deleted.

Usage Example

import { useState } from 'react';
import { CustomerDeleteModal } from '@components/customers/CustomerDeleteModal';

function CustomersList() {
  const [customerToDelete, setCustomerToDelete] = useState(null);
  
  const handleConfirmDelete = () => {
    // Perform delete operation
    deleteCustomer(customerToDelete.id);
    setCustomerToDelete(null);
  };
  
  return (
    <>
      <button onClick={() => setCustomerToDelete(customer)}>
        Delete Customer
      </button>
      
      <CustomerDeleteModal
        isOpen={!!customerToDelete}
        onClose={() => setCustomerToDelete(null)}
        onConfirm={handleConfirmDelete}
        customer={customerToDelete}
      />
    </>
  );
}

Features

  • Warning icon and message
  • Displays customer name to be deleted
  • Warning text about irreversible action
  • Cancel and Confirm buttons
  • Rounded design with visual hierarchy

Closing Behavior

Modals should close when:
  • User clicks the close (X) button
  • User clicks outside the modal (on overlay)
  • User presses Escape key (implement with useEffect)
  • User completes the action successfully
import { useEffect } from 'react';

function useEscapeKey(onClose: () => void, isOpen: boolean) {
  useEffect(() => {
    const handleEscape = (e: KeyboardEvent) => {
      if (e.key === 'Escape' && isOpen) {
        onClose();
      }
    };
    
    document.addEventListener('keydown', handleEscape);
    return () => document.removeEventListener('keydown', handleEscape);
  }, [onClose, isOpen]);
}

Preventing Propagation

Always prevent click events on modal content from propagating to the overlay:
<div className="modalOverlay" onClick={onClose}>
  <div className="modalContent" onClick={(e) => e.stopPropagation()}>
    {/* Modal content */}
  </div>
</div>

Conditional Rendering

Return null when modal is closed to improve performance:
function MyModal({ isOpen, onClose }) {
  if (!isOpen) return null;
  
  return (
    <div className="modalOverlay">
      {/* Modal content */}
    </div>
  );
}

Styling

Modals use CSS modules from @styles/modules/ with consistent classes:
  • modalOverlay - Full-screen overlay with semi-transparent background
  • modalContent - Centered dialog box with shadow
  • modalHeader - Title bar with close button
  • modalBody - Scrollable content area
  • modalFooter - Action buttons area

Accessibility

  • Focus trap within modal when open
  • Escape key to close
  • Proper heading hierarchy
  • ARIA labels for screen readers
  • Keyboard navigation support
Always provide a way to close modals without completing the action (close button, overlay click, or cancel button).

Source Code

  • CustomerFormModal: /home/daytona/workspace/source/src/components/customers/CustomerFormModal.tsx:1
  • VehicleDetailsModal: /home/daytona/workspace/source/src/components/vehicles/VehicleDetailsModal.tsx:1
  • CustomerDeleteModal: /home/daytona/workspace/source/src/components/customers/CustomerDeleteModal.tsx:1

Build docs developers (and LLMs) love