feat(console): add catalog, search, and VIN decoder screens
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
153
console/screens/buscar_parte.py
Normal file
153
console/screens/buscar_parte.py
Normal file
@@ -0,0 +1,153 @@
|
||||
"""
|
||||
Part number search screen for the AUTOPARTES console application.
|
||||
|
||||
Prompts the user for a part number (OEM, aftermarket, or cross-reference)
|
||||
and displays matching results in a table. Selecting a result navigates
|
||||
to the part detail screen.
|
||||
"""
|
||||
|
||||
from console.core.screens import Screen
|
||||
from console.core.keybindings import Key
|
||||
from console.config import APP_NAME, VERSION
|
||||
from console.utils.formatting import truncate
|
||||
|
||||
|
||||
# Match type labels in Spanish
|
||||
_TYPE_LABELS = {
|
||||
"oem": "OEM",
|
||||
"aftermarket": "Aftermarket",
|
||||
"cross_reference": "X-Ref",
|
||||
}
|
||||
|
||||
# Footer labels
|
||||
_FOOTER_INPUT = [
|
||||
("ENTER", "Buscar"),
|
||||
("ESC", "Atras"),
|
||||
]
|
||||
|
||||
_FOOTER_RESULTS = [
|
||||
("1-9", "Ver parte"),
|
||||
("ENTER", "Ver parte"),
|
||||
("F3", "Nueva busqueda"),
|
||||
("ESC", "Atras"),
|
||||
]
|
||||
|
||||
|
||||
class BuscarParteScreen(Screen):
|
||||
"""Search by part number (OEM, aftermarket, cross-reference)."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(name="buscar_parte", title="Buscar por Numero de Parte")
|
||||
self._results = None
|
||||
self._search_term = None
|
||||
self._selected = 0
|
||||
self._needs_input = True
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Render
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def render(self, context, db, renderer):
|
||||
# Header
|
||||
renderer.draw_header(
|
||||
f" {APP_NAME} v{VERSION}",
|
||||
" BUSCAR POR NUMERO DE PARTE ",
|
||||
)
|
||||
|
||||
if self._needs_input:
|
||||
# Show the input dialog (handled in on_key via on_enter-like flow)
|
||||
# Just draw footer; the input dialog will overlay
|
||||
renderer.draw_footer(_FOOTER_INPUT)
|
||||
return
|
||||
|
||||
if self._results is None:
|
||||
renderer.draw_text(5, 4, "Presione F3 para buscar", "info")
|
||||
renderer.draw_footer(_FOOTER_RESULTS)
|
||||
return
|
||||
|
||||
if not self._results:
|
||||
renderer.draw_text(
|
||||
5, 4,
|
||||
f'No se encontraron resultados para "{self._search_term}"',
|
||||
"info",
|
||||
)
|
||||
renderer.draw_footer(_FOOTER_RESULTS)
|
||||
return
|
||||
|
||||
# Display results table
|
||||
headers = ["TIPO", "NUMERO", "DESCRIPCION", "FUENTE"]
|
||||
widths = [12, 20, 30, 20]
|
||||
rows = []
|
||||
for r in self._results:
|
||||
rows.append((
|
||||
_TYPE_LABELS.get(r.get("match_type", ""), r.get("match_type", "")),
|
||||
truncate(r.get("matched_number", ""), 20),
|
||||
truncate(r.get("name_es") or r.get("name", ""), 30),
|
||||
truncate(r.get("oem_part_number", ""), 20),
|
||||
))
|
||||
|
||||
renderer.draw_table(
|
||||
headers,
|
||||
rows,
|
||||
widths,
|
||||
selected_row=self._selected,
|
||||
)
|
||||
renderer.draw_footer(_FOOTER_RESULTS)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Key handling
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def on_key(self, key, context, db, renderer, nav):
|
||||
# If we need input, show the input dialog
|
||||
if self._needs_input:
|
||||
self._needs_input = False
|
||||
value = renderer.show_input("Numero de parte", max_len=30)
|
||||
if value is None:
|
||||
# User pressed ESC in input dialog
|
||||
if self._results is not None:
|
||||
# Go back to results view
|
||||
return None
|
||||
return "back"
|
||||
if value.strip():
|
||||
self._search_term = value.strip()
|
||||
self._results = db.search_part_number(self._search_term)
|
||||
self._selected = 0
|
||||
return None
|
||||
|
||||
# ESC: go back
|
||||
if key == Key.ESCAPE:
|
||||
return "back"
|
||||
|
||||
# F3: new search
|
||||
if key == Key.F3:
|
||||
self._needs_input = True
|
||||
return None
|
||||
|
||||
# Arrow navigation
|
||||
if key == Key.UP:
|
||||
if self._selected > 0:
|
||||
self._selected -= 1
|
||||
return None
|
||||
|
||||
if key == Key.DOWN:
|
||||
if self._results and self._selected < len(self._results) - 1:
|
||||
self._selected += 1
|
||||
return None
|
||||
|
||||
# ENTER: view selected part
|
||||
if key == Key.ENTER:
|
||||
if self._results and 0 <= self._selected < len(self._results):
|
||||
part = self._results[self._selected]
|
||||
return ("parte_detalle", {"part_id": part["id"]}, "Parte")
|
||||
return None
|
||||
|
||||
# Number keys: direct selection (1-9)
|
||||
if 49 <= key <= 57: # '1'..'9'
|
||||
idx = key - 49 # 0-based
|
||||
if self._results and 0 <= idx < len(self._results):
|
||||
part = self._results[idx]
|
||||
return ("parte_detalle", {"part_id": part["id"]}, "Parte")
|
||||
return None
|
||||
|
||||
return None
|
||||
Reference in New Issue
Block a user