""" Admin CRUD screen for Cross-References in the AUTOPARTES console application. Provides a paginated list view with create (F3), edit (ENTER), and delete (F8/Del) operations for the part_cross_references table. """ 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 # Form field definitions for create/edit _FIELDS = [ {'label': 'Part ID', 'key': 'part_id', 'width': 8, 'hint': 'F1=Buscar parte'}, {'label': 'Numero cruzado', 'key': 'cross_reference_number', 'width': 25}, {'label': 'Tipo', 'key': 'reference_type', 'width': 15, 'hint': 'supersession/interchange/competitor'}, {'label': 'Fuente', 'key': 'source', 'width': 20}, {'label': 'Notas', 'key': 'notes', 'width': 40}, ] # Footer labels per mode _FOOTER_LIST = [ ("F3", "Nuevo"), ("ENTER", "Editar"), ("F8", "Eliminar"), ("PgUp/Dn", "Paginar"), ("ESC", "Atras"), ] _FOOTER_FORM = [ ("TAB/Down", "Siguiente"), ("Up", "Anterior"), ("F9", "Guardar"), ("ESC", "Cancelar"), ] class AdminCrossrefScreen(Screen): """Admin CRUD screen for the part_cross_references table.""" def __init__(self): super().__init__(name="admin_crossref", title="Cross-References") self._mode = 'list' # 'list' or 'form' self._page = 1 self._per_page = 15 self._selected = 0 self._crossrefs = [] self._editing_id = None # None = creating, int = editing self._focused_field = 0 self._form_data = {} self._dirty = False # ------------------------------------------------------------------ # Data loading # ------------------------------------------------------------------ def _load_crossrefs(self, db): """Load the current page of cross-references.""" self._crossrefs = db.get_crossrefs_paginated( page=self._page, per_page=self._per_page ) def _init_form(self, xref=None): """Initialise form_data from an existing cross-reference or blank.""" self._form_data = {} if xref: for f in _FIELDS: val = xref.get(f['key'], '') self._form_data[f['key']] = str(val) if val is not None else '' else: for f in _FIELDS: self._form_data[f['key']] = '' self._focused_field = 0 self._dirty = False # ------------------------------------------------------------------ # Render # ------------------------------------------------------------------ def render(self, context, db, renderer): renderer.draw_header( f" {APP_NAME} v{VERSION}", " CROSS-REFERENCES ", ) if self._mode == 'list': self._render_list(db, renderer) else: self._render_form(renderer) def _render_list(self, db, renderer): """Render the paginated cross-references list.""" self._load_crossrefs(db) headers = ["PARTE OEM", "NUMERO CRUZADO", "TIPO", "FUENTE"] widths = [18, 22, 14, 16] rows = [] for x in self._crossrefs: rows.append(( truncate(x.get("oem_part_number", ""), 18), truncate(x.get("cross_reference_number", ""), 22), truncate(x.get("reference_type", "") or "", 14), truncate(x.get("source", "") or "", 16), )) renderer.draw_table( headers, rows, widths, page_info={ "page": self._page, "total_pages": self._page, "total_rows": len(rows), }, selected_row=self._selected, ) renderer.draw_footer(_FOOTER_LIST) def _render_form(self, renderer): """Render the create/edit form.""" title = "EDITAR CROSS-REFERENCE" if self._editing_id else "NUEVA CROSS-REFERENCE" fields = [] for f in _FIELDS: fields.append({ 'label': f['label'], 'value': self._form_data.get(f['key'], ''), 'width': f['width'], 'hint': f.get('hint', ''), }) renderer.draw_form(fields, focused_index=self._focused_field, title=title) renderer.draw_footer(_FOOTER_FORM) # ------------------------------------------------------------------ # Key handling # ------------------------------------------------------------------ def on_key(self, key, context, db, renderer, nav): if self._mode == 'list': return self._handle_list_key(key, db, renderer) else: return self._handle_form_key(key, db, renderer) def _handle_list_key(self, key, db, renderer): """Handle keys in list mode.""" # ESC: go back if key == Key.ESCAPE: return "back" # Arrow navigation if key == Key.UP: if self._selected > 0: self._selected -= 1 return None if key == Key.DOWN: if self._crossrefs and self._selected < len(self._crossrefs) - 1: self._selected += 1 return None # PgDn: next page if key == Key.PGDN: if len(self._crossrefs) == self._per_page: self._page += 1 self._selected = 0 return None # PgUp: previous page if key == Key.PGUP: if self._page > 1: self._page -= 1 self._selected = 0 return None # F3: create new cross-reference if key == Key.F3: self._mode = 'form' self._editing_id = None self._init_form() return None # ENTER: edit selected cross-reference if key == Key.ENTER: if self._crossrefs and 0 <= self._selected < len(self._crossrefs): xref = self._crossrefs[self._selected] self._editing_id = xref["id"] self._mode = 'form' self._init_form(xref) return None # Number keys 1-9: edit cross-reference at that row index if 49 <= key <= 57: idx = key - 49 if 0 <= idx < len(self._crossrefs): xref = self._crossrefs[idx] self._editing_id = xref["id"] self._mode = 'form' self._init_form(xref) return None # F8 or DEL: delete selected cross-reference if key in (Key.F8, 330): # 330 = KEY_DC (Delete) if self._crossrefs and 0 <= self._selected < len(self._crossrefs): xref = self._crossrefs[self._selected] oem = xref.get("oem_part_number", "") xnum = xref.get("cross_reference_number", "") confirmed = renderer.show_message( f"Eliminar cross-reference?\n{oem} -> {xnum}", "confirm", ) if confirmed: try: db.delete_crossref(xref["id"]) except Exception as exc: renderer.show_message(f"Error:\n{exc}", "error") return None if self._selected >= len(self._crossrefs) - 1: self._selected = max(0, self._selected - 1) return None return None def _handle_form_key(self, key, db, renderer): """Handle keys in form mode.""" # ESC: cancel form (with dirty check) if key == Key.ESCAPE: if self._dirty: confirmed = renderer.show_message( "Descartar cambios?", "confirm" ) if not confirmed: return None self._mode = 'list' return None # TAB / Down: next field if key in (Key.TAB, Key.DOWN): if self._focused_field < len(_FIELDS) - 1: self._focused_field += 1 return None # Up: previous field if key == Key.UP: if self._focused_field > 0: self._focused_field -= 1 return None # F9: save if key == Key.F9: return self._save(db, renderer) # Backspace: delete last char from current field value if key in (Key.BACKSPACE, 8): field_key = _FIELDS[self._focused_field]['key'] val = self._form_data.get(field_key, '') if val: self._form_data[field_key] = val[:-1] self._dirty = True return None # Printable characters: append to current field if 32 <= key <= 126: field_def = _FIELDS[self._focused_field] field_key = field_def['key'] val = self._form_data.get(field_key, '') if len(val) < field_def['width']: self._form_data[field_key] = val + chr(key) self._dirty = True return None return None def _save(self, db, renderer): """Validate and save the form data.""" data = dict(self._form_data) # Validate required fields pid = data.get('part_id', '').strip() if not pid or not pid.isdigit(): renderer.show_message("Part ID debe ser un numero valido", "error") return None data['part_id'] = int(pid) if not data.get('cross_reference_number', '').strip(): renderer.show_message("Numero cruzado es requerido", "error") return None try: if self._editing_id: db.update_crossref(self._editing_id, data) renderer.show_message("Cross-reference actualizada correctamente", "info") else: db.create_crossref(data) renderer.show_message("Cross-reference creada correctamente", "info") except Exception as exc: renderer.show_message(f"Error al guardar:\n{exc}", "error") return None self._mode = 'list' self._dirty = False return None