Widget AdvancedTable portado de Go (simifactu-fyne) a Zig. 2,526 LOC en 4 archivos, 370 tests pasan. Archivos: - types.zig (369 LOC): CellValue, ColumnType, RowState, TableColors - schema.zig (373 LOC): ColumnDef, TableSchema, DataStore interface - state.zig (762 LOC): Selection, editing, dirty tracking, snapshots - advanced_table.zig (1,022 LOC): Widget, rendering, keyboard Fases implementadas: 1. Core (types, schema, state) 2. Navigation (arrows, Tab, PgUp/Dn, Home/End, Ctrl+Home/End) 3. Cell Editing (F2/Enter start, Escape cancel, text input) 4. Sorting (header click, visual indicators) 5. Auto-CRUD (CREATE/UPDATE/DELETE detection on row change) 6. Row Operations (Ctrl+N insert, Ctrl+Delete remove) Fases diferidas (7-8): Lookup & Auto-fill, Callbacks avanzados ESTADO: Compilado y tests pasan. NO probado en uso real. REQUIERE: Aprobacion antes de tag de version. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
582 lines
18 KiB
Markdown
582 lines
18 KiB
Markdown
# AdvancedTable - Documento de Diseño
|
|
|
|
> **Fecha**: 2025-12-17
|
|
> **Basado en**: Análisis de `simifactu-fyne/internal/ui/components/advanced_table/` (~2,800 LOC Go)
|
|
> **Target**: zcatgui (Zig)
|
|
|
|
---
|
|
|
|
## 1. Resumen Ejecutivo
|
|
|
|
AdvancedTable es una tabla parametrizada de alto nivel que proporciona:
|
|
- **Configuración schema-driven** (sin callbacks hardcoded)
|
|
- **Edición Excel-style** (Entry overlay sobre celda)
|
|
- **Auto-CRUD** (detecta y ejecuta CREATE/UPDATE/DELETE automáticamente)
|
|
- **Navegación teclado completa** (arrows, Tab, Enter, Escape)
|
|
- **Sorting por columna** (click header, cycle asc/desc/restore)
|
|
- **Visual states** (normal, modified, new, deleted, error, selected)
|
|
- **Lookup & Auto-fill** (para campos de lookup de BD)
|
|
|
|
La idea es que cualquier tabla en la aplicación (WHO, líneas de documento, configuración, etc.) sea simplemente una **instancia parametrizada** de AdvancedTable.
|
|
|
|
---
|
|
|
|
## 2. Arquitectura Propuesta para zcatgui
|
|
|
|
### 2.1 Estructura de Archivos
|
|
|
|
```
|
|
src/widgets/advanced_table/
|
|
├── advanced_table.zig # Entry point, re-exports
|
|
├── types.zig # Tipos, enums, configs
|
|
├── schema.zig # TableSchema, ColumnDef
|
|
├── state.zig # AdvancedTableState
|
|
├── render.zig # Rendering visual
|
|
├── editing.zig # Edición in-situ
|
|
├── navigation.zig # Navegación teclado
|
|
├── sorting.zig # Sorting por columnas
|
|
├── row_ops.zig # Operaciones de filas (CRUD local)
|
|
├── autocrud.zig # Detección y ejecución Auto-CRUD
|
|
├── lookup.zig # Lookup y Auto-fill
|
|
└── callbacks.zig # Sistema de callbacks
|
|
```
|
|
|
|
### 2.2 Tipos Fundamentales
|
|
|
|
```zig
|
|
// types.zig
|
|
|
|
/// Tipos de datos para columnas
|
|
pub const ColumnType = enum {
|
|
string,
|
|
integer,
|
|
float,
|
|
money,
|
|
boolean,
|
|
date,
|
|
select, // Dropdown con opciones
|
|
lookup, // Busca en tabla relacionada
|
|
};
|
|
|
|
/// Estado de fila (dirty tracking)
|
|
pub const RowState = enum {
|
|
normal, // Sin cambios
|
|
modified, // Editado, pendiente de guardar
|
|
new, // Fila nueva, no existe en BD
|
|
deleted, // Marcado para borrar
|
|
error, // Error de validación
|
|
};
|
|
|
|
/// Estado de lock de fila (para VeriFacTu certified docs)
|
|
pub const RowLockState = enum {
|
|
unlocked, // Editable
|
|
locked, // Requiere password
|
|
read_only, // Solo lectura (certificado)
|
|
};
|
|
|
|
/// Resultado de validación
|
|
pub const ValidationResult = struct {
|
|
valid: bool,
|
|
message: []const u8 = "",
|
|
severity: enum { info, warning, error } = .error,
|
|
};
|
|
```
|
|
|
|
### 2.3 Schema de Tabla
|
|
|
|
```zig
|
|
// schema.zig
|
|
|
|
/// Definición de columna
|
|
pub const ColumnDef = struct {
|
|
/// Nombre interno (key en row map)
|
|
name: []const u8,
|
|
/// Título visible en header
|
|
title: []const u8,
|
|
/// Ancho en pixels
|
|
width: u32 = 100,
|
|
/// Tipo de datos
|
|
column_type: ColumnType = .string,
|
|
/// Es editable
|
|
editable: bool = true,
|
|
/// Es ordenable
|
|
sortable: bool = true,
|
|
/// Ancho mínimo
|
|
min_width: u32 = 40,
|
|
/// Alineación
|
|
align: enum { left, center, right } = .left,
|
|
|
|
// Lookup
|
|
enable_lookup: bool = false,
|
|
lookup_table: ?[]const u8 = null,
|
|
lookup_key_column: ?[]const u8 = null,
|
|
auto_fill_columns: ?[]const AutoFillMapping = null,
|
|
|
|
// Callbacks por columna (opcionales)
|
|
validator: ?*const fn(value: CellValue) ValidationResult = null,
|
|
formatter: ?*const fn(value: CellValue) []const u8 = null,
|
|
parser: ?*const fn(text: []const u8) ?CellValue = null,
|
|
};
|
|
|
|
/// Mapping para auto-fill después de lookup
|
|
pub const AutoFillMapping = struct {
|
|
source_field: []const u8, // Campo en tabla lookup
|
|
target_column: []const u8, // Columna destino
|
|
};
|
|
|
|
/// Schema completo de tabla
|
|
pub const TableSchema = struct {
|
|
/// Nombre de la tabla (para DataStore)
|
|
table_name: []const u8,
|
|
/// Columnas
|
|
columns: []const ColumnDef,
|
|
|
|
// Configuración visual
|
|
show_row_state_indicators: bool = true,
|
|
show_headers: bool = true,
|
|
header_height: u32 = 28,
|
|
row_height: u32 = 24,
|
|
alternating_rows: bool = true,
|
|
|
|
// Configuración de edición
|
|
allow_edit: bool = true,
|
|
allow_sorting: bool = true,
|
|
allow_row_operations: bool = true, // Ctrl+N, Delete, etc.
|
|
|
|
// Auto-CRUD
|
|
auto_crud_enabled: bool = true, // Detecta CREATE/UPDATE/DELETE al cambiar fila
|
|
always_show_empty_row: bool = false, // Fila vacía al final para entrada continua
|
|
|
|
// Row locking (para certificados)
|
|
support_row_locking: bool = false,
|
|
|
|
// DataStore (opcional, para Auto-CRUD)
|
|
data_store: ?*DataStore = null,
|
|
|
|
// Colors (opcional, override del theme)
|
|
colors: ?*const TableColors = null,
|
|
};
|
|
```
|
|
|
|
### 2.4 State de la Tabla
|
|
|
|
```zig
|
|
// state.zig
|
|
|
|
pub const AdvancedTableState = struct {
|
|
// Datos
|
|
rows: std.ArrayList(Row),
|
|
allocator: std.mem.Allocator,
|
|
|
|
// Selección
|
|
selected_row: i32 = -1,
|
|
selected_col: i32 = -1,
|
|
prev_selected_row: i32 = -1,
|
|
prev_selected_col: i32 = -1,
|
|
|
|
// Edición
|
|
editing: bool = false,
|
|
edit_buffer: [256]u8 = undefined,
|
|
edit_len: usize = 0,
|
|
edit_cursor: usize = 0,
|
|
original_value: ?CellValue = null,
|
|
escape_count: u8 = 0, // 1=revert, 2=cancel
|
|
|
|
// Sorting
|
|
sort_column: i32 = -1,
|
|
sort_ascending: bool = true,
|
|
original_order: ?std.ArrayList(Row) = null, // Para restore
|
|
|
|
// Scroll
|
|
scroll_row: usize = 0,
|
|
scroll_x: usize = 0,
|
|
|
|
// State maps (sparse - solo filas modificadas)
|
|
dirty_rows: std.AutoHashMap(usize, bool),
|
|
new_rows: std.AutoHashMap(usize, bool),
|
|
deleted_rows: std.AutoHashMap(usize, bool),
|
|
validation_errors: std.AutoHashMap(usize, bool),
|
|
|
|
// Snapshots para Auto-CRUD
|
|
row_snapshots: std.AutoHashMap(usize, Row),
|
|
|
|
// Focus
|
|
focused: bool = false,
|
|
|
|
// Debounce (para callbacks externos)
|
|
last_callback_time: i64 = 0,
|
|
pending_row_callback: ?usize = null,
|
|
|
|
pub fn init(allocator: std.mem.Allocator) AdvancedTableState { ... }
|
|
pub fn deinit(self: *AdvancedTableState) void { ... }
|
|
|
|
// Row operations
|
|
pub fn setRows(self: *AdvancedTableState, rows: []const Row) void { ... }
|
|
pub fn getRowCount(self: *AdvancedTableState) usize { ... }
|
|
pub fn getRow(self: *AdvancedTableState, index: usize) ?*Row { ... }
|
|
pub fn insertRow(self: *AdvancedTableState, index: usize) void { ... }
|
|
pub fn deleteRow(self: *AdvancedTableState, index: usize) void { ... }
|
|
pub fn moveRowUp(self: *AdvancedTableState, index: usize) void { ... }
|
|
pub fn moveRowDown(self: *AdvancedTableState, index: usize) void { ... }
|
|
|
|
// State queries
|
|
pub fn getRowState(self: *AdvancedTableState, index: usize) RowState { ... }
|
|
pub fn markDirty(self: *AdvancedTableState, index: usize) void { ... }
|
|
pub fn markNew(self: *AdvancedTableState, index: usize) void { ... }
|
|
pub fn markDeleted(self: *AdvancedTableState, index: usize) void { ... }
|
|
pub fn clearRowState(self: *AdvancedTableState, index: usize) void { ... }
|
|
|
|
// Snapshots (para Auto-CRUD)
|
|
pub fn captureSnapshot(self: *AdvancedTableState, index: usize) void { ... }
|
|
pub fn getSnapshot(self: *AdvancedTableState, index: usize) ?Row { ... }
|
|
pub fn clearSnapshot(self: *AdvancedTableState, index: usize) void { ... }
|
|
|
|
// Selection
|
|
pub fn selectCell(self: *AdvancedTableState, row: usize, col: usize) void { ... }
|
|
pub fn clearSelection(self: *AdvancedTableState) void { ... }
|
|
|
|
// Editing
|
|
pub fn startEditing(self: *AdvancedTableState, initial_value: []const u8) void { ... }
|
|
pub fn stopEditing(self: *AdvancedTableState) void { ... }
|
|
pub fn getEditText(self: *AdvancedTableState) []const u8 { ... }
|
|
|
|
// Sorting
|
|
pub fn toggleSort(self: *AdvancedTableState, col: usize) void { ... }
|
|
pub fn restoreOriginalOrder(self: *AdvancedTableState) void { ... }
|
|
};
|
|
```
|
|
|
|
### 2.5 DataStore Interface
|
|
|
|
```zig
|
|
// types.zig
|
|
|
|
/// Interface para persistencia (BD, archivo, API, etc.)
|
|
pub const DataStore = struct {
|
|
ptr: *anyopaque,
|
|
vtable: *const VTable,
|
|
|
|
pub const VTable = struct {
|
|
load: *const fn(ptr: *anyopaque) []Row,
|
|
save: *const fn(ptr: *anyopaque, row: *Row) anyerror!void,
|
|
delete: *const fn(ptr: *anyopaque, row: *Row) anyerror!void,
|
|
lookup: *const fn(ptr: *anyopaque, table: []const u8, key_col: []const u8, key_val: CellValue) ?Row,
|
|
};
|
|
|
|
pub fn load(self: DataStore) []Row {
|
|
return self.vtable.load(self.ptr);
|
|
}
|
|
|
|
pub fn save(self: DataStore, row: *Row) !void {
|
|
return self.vtable.save(self.ptr, row);
|
|
}
|
|
|
|
pub fn delete(self: DataStore, row: *Row) !void {
|
|
return self.vtable.delete(self.ptr, row);
|
|
}
|
|
|
|
pub fn lookup(self: DataStore, table: []const u8, key_col: []const u8, key_val: CellValue) ?Row {
|
|
return self.vtable.lookup(self.ptr, table, key_col, key_val);
|
|
}
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Funcionalidades Detalladas
|
|
|
|
### 3.1 Edición Excel-Style
|
|
|
|
**Comportamiento**:
|
|
1. **Activar edición**: F2, Enter, Spacebar, o tecla alfanumérica
|
|
2. **Tecla alfanumérica**: Reemplaza contenido con esa letra, cursor al final
|
|
3. **F2/Enter/Space**: Muestra valor actual, selecciona todo
|
|
4. **Durante edición**:
|
|
- Arrow Up/Down: Commit + navega + auto-edit
|
|
- Tab: Commit + navega siguiente editable + auto-edit
|
|
- Shift+Tab: Commit + navega anterior + auto-edit
|
|
- Enter: Commit + navega abajo
|
|
- Escape (1): Revert al valor original (sigue editando)
|
|
- Escape (2): Cancel, sale de edición
|
|
5. **Commit**: Valida, parsea, guarda en row, marca dirty
|
|
|
|
### 3.2 Navegación Teclado
|
|
|
|
| Tecla | Acción |
|
|
|-------|--------|
|
|
| ↑/↓/←/→ | Navegar entre celdas |
|
|
| Tab | Siguiente celda editable |
|
|
| Shift+Tab | Anterior celda editable |
|
|
| Enter | Iniciar edición o navegar abajo |
|
|
| F2 | Iniciar edición |
|
|
| Escape | Cancelar edición / Revert cambios |
|
|
| Ctrl+N | Insertar fila en posición actual |
|
|
| Ctrl+A | Agregar fila al final |
|
|
| Ctrl+B / Delete | Borrar fila actual |
|
|
| Ctrl+↑ | Mover fila arriba |
|
|
| Ctrl+↓ | Mover fila abajo |
|
|
| Home | Ir a primera columna |
|
|
| End | Ir a última columna |
|
|
| Ctrl+Home | Ir a primera fila |
|
|
| Ctrl+End | Ir a última fila |
|
|
| Page Up/Down | Scroll por página |
|
|
|
|
### 3.3 Auto-CRUD
|
|
|
|
**Flujo**:
|
|
1. Usuario entra en fila → `captureSnapshot(rowIndex)`
|
|
2. Usuario edita celdas → row modificado, `markDirty(rowIndex)`
|
|
3. Usuario sale de fila (navega a otra) → `detectCRUDAction(rowIndex)`
|
|
4. Según resultado:
|
|
- **ActionNone**: No hubo cambios reales → limpiar dirty flag
|
|
- **ActionCreate**: Snapshot vacío, current tiene datos → `DataStore.save()` (INSERT)
|
|
- **ActionUpdate**: Snapshot y current tienen datos diferentes → `DataStore.save()` (UPDATE)
|
|
- **ActionDelete**: Row marcado deleted o current vacío → `DataStore.delete()`
|
|
|
|
**Detección**:
|
|
```zig
|
|
fn detectCRUDAction(state: *AdvancedTableState, row_index: usize) CRUDAction {
|
|
// Check deleted flag first (Ctrl+B)
|
|
if (state.deleted_rows.get(row_index)) |_| {
|
|
return .delete;
|
|
}
|
|
|
|
const snapshot = state.getSnapshot(row_index) orelse return .none;
|
|
const current = state.getRow(row_index) orelse return .none;
|
|
|
|
const was_empty = isRowEmpty(snapshot);
|
|
const is_empty = isRowEmpty(current);
|
|
const has_changes = rowHasChanges(snapshot, current);
|
|
|
|
if (was_empty and !is_empty) return .create;
|
|
if (!was_empty and !is_empty and has_changes) return .update;
|
|
if (!was_empty and is_empty) return .delete;
|
|
|
|
return .none;
|
|
}
|
|
```
|
|
|
|
### 3.4 Sorting
|
|
|
|
**Ciclo**: Click en header → Ascending → Click → Descending → Click → Restore original
|
|
|
|
**Implementación**:
|
|
1. Primer sort en columna: Guardar orden original
|
|
2. Sort usa índices indirectos (no modifica rows directamente)
|
|
3. Después de sort: Sincronizar TODOS los state maps (dirty, new, deleted, snapshots, selection)
|
|
4. Restore: Volver al orden guardado
|
|
|
|
### 3.5 Visual States
|
|
|
|
| Estado | Color Background | Indicador |
|
|
|--------|------------------|-----------|
|
|
| Normal | Theme default | - |
|
|
| Modified | Amarillo suave | ● amarillo |
|
|
| New | Verde suave | ● verde |
|
|
| Deleted | Rojo suave | ● rojo |
|
|
| Error | Rojo intenso | ⚠ |
|
|
| Selected Cell | Azul intenso | - |
|
|
| Selected Row | Azul suave | - |
|
|
| Read-Only | Gris claro | 🔒 |
|
|
| Locked | Gris oscuro | 🔐 |
|
|
|
|
### 3.6 Lookup & Auto-fill
|
|
|
|
**Caso de uso**: Líneas de factura
|
|
- Usuario escribe código de producto
|
|
- Tabla busca en tabla `productos` por `codigo`
|
|
- Si encuentra: Auto-rellena `descripcion`, `precio`, `iva_porc`, `re_porc`
|
|
- Si no encuentra: Callback `OnLookupNotFound` (puede abrir diálogo crear producto)
|
|
|
|
**Configuración en ColumnDef**:
|
|
```zig
|
|
ColumnDef{
|
|
.name = "producto_codigo",
|
|
.title = "Código",
|
|
.enable_lookup = true,
|
|
.lookup_table = "productos",
|
|
.lookup_key_column = "codigo",
|
|
.auto_fill_columns = &[_]AutoFillMapping{
|
|
.{ .source_field = "descripcion", .target_column = "descripcion" },
|
|
.{ .source_field = "precio_venta", .target_column = "precio" },
|
|
.{ .source_field = "iva_porcentaje", .target_column = "iva_porc" },
|
|
.{ .source_field = "re_porcentaje", .target_column = "re_porc" },
|
|
},
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 4. API Pública
|
|
|
|
```zig
|
|
// advanced_table.zig
|
|
|
|
/// Dibujar AdvancedTable con schema y state
|
|
pub fn advancedTable(
|
|
ctx: *Context,
|
|
state: *AdvancedTableState,
|
|
schema: *const TableSchema,
|
|
) AdvancedTableResult;
|
|
|
|
/// Dibujar en rectángulo específico
|
|
pub fn advancedTableRect(
|
|
ctx: *Context,
|
|
bounds: Layout.Rect,
|
|
state: *AdvancedTableState,
|
|
schema: *const TableSchema,
|
|
colors: ?*const TableColors,
|
|
) AdvancedTableResult;
|
|
|
|
/// Resultado de interacción
|
|
pub const AdvancedTableResult = struct {
|
|
// Selección
|
|
selection_changed: bool = false,
|
|
selected_row: ?usize = null,
|
|
selected_col: ?usize = null,
|
|
|
|
// Edición
|
|
edit_started: bool = false,
|
|
edit_ended: bool = false,
|
|
cell_edited: bool = false,
|
|
|
|
// Sorting
|
|
sort_changed: bool = false,
|
|
sort_column: ?usize = null,
|
|
sort_ascending: bool = true,
|
|
|
|
// Row operations
|
|
row_inserted: bool = false,
|
|
row_deleted: bool = false,
|
|
row_moved: bool = false,
|
|
|
|
// Auto-CRUD (si habilitado)
|
|
crud_action: ?CRUDAction = null,
|
|
crud_error: ?[]const u8 = null,
|
|
|
|
// Lookup
|
|
lookup_triggered: bool = false,
|
|
lookup_found: bool = false,
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Comparativa con Table actual
|
|
|
|
| Feature | Table actual | AdvancedTable |
|
|
|---------|-------------|---------------|
|
|
| Config | Callbacks `getCellData` | Schema-driven |
|
|
| Edición | Overlay básico | Excel-style completo |
|
|
| Auto-CRUD | Manual | Automático |
|
|
| Lookup | No | Sí |
|
|
| Calculated | No | Sí (futuro) |
|
|
| Row types | No | Sí (futuro) |
|
|
| Row locking | No | Sí |
|
|
| Sorting sync | Básico | Completo |
|
|
| State maps | Básico | Sparse optimizado |
|
|
| Callbacks | Legacy | Jerarquía Per-Column → Global → Default |
|
|
| Debounce | No | Sí (150ms default) |
|
|
|
|
---
|
|
|
|
## 6. Fases de Implementación
|
|
|
|
### Fase 1: Core (~600 LOC)
|
|
- [ ] types.zig - Tipos básicos
|
|
- [ ] schema.zig - TableSchema, ColumnDef
|
|
- [ ] state.zig - AdvancedTableState
|
|
- [ ] advanced_table.zig - Rendering básico
|
|
|
|
### Fase 2: Navegación (~300 LOC)
|
|
- [ ] navigation.zig - Keyboard navigation
|
|
- [ ] Integración con FocusSystem
|
|
|
|
### Fase 3: Edición (~400 LOC)
|
|
- [ ] editing.zig - Entry overlay, commit/cancel
|
|
- [ ] Parsers y formatters por tipo
|
|
|
|
### Fase 4: Sorting (~200 LOC)
|
|
- [ ] sorting.zig - Sort por columna
|
|
- [ ] Sync de state maps
|
|
|
|
### Fase 5: Row Operations (~250 LOC)
|
|
- [ ] row_ops.zig - Insert/Delete/Move
|
|
- [ ] State map management
|
|
|
|
### Fase 6: Auto-CRUD (~300 LOC)
|
|
- [ ] autocrud.zig - Detection & execution
|
|
- [ ] DataStore interface
|
|
- [ ] Snapshots
|
|
|
|
### Fase 7: Lookup (~200 LOC)
|
|
- [ ] lookup.zig - Lookup & auto-fill
|
|
|
|
### Fase 8: Callbacks (~150 LOC)
|
|
- [ ] callbacks.zig - Sistema jerárquico
|
|
- [ ] Debounce
|
|
|
|
**Total estimado**: ~2,400 LOC
|
|
|
|
---
|
|
|
|
## 7. Ejemplo de Uso
|
|
|
|
```zig
|
|
// Panel WHO List usando AdvancedTable
|
|
|
|
const who_schema = TableSchema{
|
|
.table_name = "who",
|
|
.columns = &[_]ColumnDef{
|
|
.{ .name = "id", .title = "ID", .width = 50, .editable = false },
|
|
.{ .name = "serie", .title = "Serie", .width = 80 },
|
|
.{ .name = "numero", .title = "Número", .width = 80, .column_type = .integer },
|
|
.{ .name = "fecha", .title = "Fecha", .width = 100, .column_type = .date },
|
|
.{ .name = "cliente_nif", .title = "NIF", .width = 100,
|
|
.enable_lookup = true,
|
|
.lookup_table = "clientes",
|
|
.lookup_key_column = "nif",
|
|
.auto_fill_columns = &[_]AutoFillMapping{
|
|
.{ .source_field = "nombre", .target_column = "cliente_nombre" },
|
|
.{ .source_field = "direccion", .target_column = "cliente_direccion" },
|
|
},
|
|
},
|
|
.{ .name = "cliente_nombre", .title = "Cliente", .width = 200 },
|
|
.{ .name = "base", .title = "Base", .width = 100, .column_type = .money, .align = .right },
|
|
.{ .name = "total", .title = "Total", .width = 100, .column_type = .money, .align = .right },
|
|
},
|
|
.auto_crud_enabled = true,
|
|
.show_row_state_indicators = true,
|
|
.data_store = &who_data_store,
|
|
};
|
|
|
|
var who_state = AdvancedTableState.init(allocator);
|
|
defer who_state.deinit();
|
|
|
|
// En el loop de UI:
|
|
const result = advancedTable(&ctx, &who_state, &who_schema);
|
|
|
|
if (result.selection_changed) {
|
|
// Actualizar panel de detalle
|
|
if (result.selected_row) |row_idx| {
|
|
loadDocumentDetail(who_state.getRow(row_idx));
|
|
}
|
|
}
|
|
|
|
if (result.crud_action) |action| {
|
|
switch (action) {
|
|
.create => showStatus("Documento creado", .success),
|
|
.update => showStatus("Documento guardado", .success),
|
|
.delete => showStatus("Documento borrado", .success),
|
|
.none => {},
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Referencias
|
|
|
|
- **Go Implementation**: `/mnt/cello2/arno/re/recode/go/simifactu-fyne/internal/ui/components/advanced_table/`
|
|
- **zcatgui Table actual**: `src/widgets/table/`
|
|
- **DVUI Audit**: `docs/research/DVUI_AUDIT_2025-12-17.md`
|