Merge issue/0035d-tableview-drill-in
This commit is contained in:
+27
-1
@@ -782,10 +782,32 @@ bool entity_list_rows(const char* db_path,
|
||||
if (db) sqlite3_close(db);
|
||||
return false;
|
||||
}
|
||||
const char* sql =
|
||||
// Detecta si existe la columna `group_id` (issue 0035a). En BDs viejas
|
||||
// sin la columna, el campo queda vacio y nada cambia.
|
||||
bool has_group_id = false;
|
||||
{
|
||||
sqlite3_stmt* pst = nullptr;
|
||||
if (sqlite3_prepare_v2(db, "PRAGMA table_info(entities)", -1, &pst, nullptr) == SQLITE_OK) {
|
||||
while (sqlite3_step(pst) == SQLITE_ROW) {
|
||||
const unsigned char* name = sqlite3_column_text(pst, 1);
|
||||
if (name && std::strcmp((const char*)name, "group_id") == 0) {
|
||||
has_group_id = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(pst);
|
||||
}
|
||||
}
|
||||
const char* sql_with =
|
||||
"SELECT id, COALESCE(name,''), COALESCE(type_ref,''), "
|
||||
" COALESCE(status,''), COALESCE(updated_at,''), "
|
||||
" COALESCE(group_id,'') "
|
||||
"FROM entities ORDER BY type_ref, name";
|
||||
const char* sql_without =
|
||||
"SELECT id, COALESCE(name,''), COALESCE(type_ref,''), "
|
||||
" COALESCE(status,''), COALESCE(updated_at,'') "
|
||||
"FROM entities ORDER BY type_ref, name";
|
||||
const char* sql = has_group_id ? sql_with : sql_without;
|
||||
sqlite3_stmt* st = nullptr;
|
||||
if (sqlite3_prepare_v2(db, sql, -1, &st, nullptr) != SQLITE_OK) {
|
||||
sqlite3_close(db);
|
||||
@@ -803,6 +825,10 @@ bool entity_list_rows(const char* db_path,
|
||||
r.type_ref = a2 ? (const char*)a2 : "";
|
||||
r.status = a3 ? (const char*)a3 : "";
|
||||
r.updated_at = a4 ? (const char*)a4 : "";
|
||||
if (has_group_id) {
|
||||
const unsigned char* a5 = sqlite3_column_text(st, 5);
|
||||
r.group_id = a5 ? (const char*)a5 : "";
|
||||
}
|
||||
out->push_back(std::move(r));
|
||||
}
|
||||
sqlite3_finalize(st);
|
||||
|
||||
@@ -130,6 +130,7 @@ struct EntityRowSnapshot {
|
||||
std::string type_ref;
|
||||
std::string status;
|
||||
std::string updated_at;
|
||||
std::string group_id; // "" si la fila no pertenece a ningun grupo
|
||||
};
|
||||
|
||||
// Carga todas las filas de `entities` ordenadas por type_ref, name. Tolera BD
|
||||
|
||||
@@ -729,6 +729,7 @@ static bool load_input(bool first_load) {
|
||||
tr.type_ref = std::move(s.type_ref);
|
||||
tr.status = std::move(s.status);
|
||||
tr.updated_at = std::move(s.updated_at);
|
||||
tr.group_id = std::move(s.group_id);
|
||||
g_app.table_rows.push_back(std::move(tr));
|
||||
}
|
||||
ge::views_table_refresh_indices(g_app);
|
||||
@@ -1558,6 +1559,7 @@ static void render() {
|
||||
tr.type_ref = std::move(s.type_ref);
|
||||
tr.status = std::move(s.status);
|
||||
tr.updated_at = std::move(s.updated_at);
|
||||
tr.group_id = std::move(s.group_id);
|
||||
g_app.table_rows.push_back(std::move(tr));
|
||||
}
|
||||
ge::views_table_refresh_indices(g_app);
|
||||
@@ -1855,21 +1857,40 @@ static void render() {
|
||||
}
|
||||
|
||||
// Note editor — abrir / guardar.
|
||||
// Excepcion (issue 0035d): si el nodo es un Group, en lugar de abrir
|
||||
// Note se abre el panel Table con filtro por group_id.
|
||||
if (g_app.want_open_note && g_app.open_note_target >= 0
|
||||
&& g_app.open_note_target < g_graph.node_count) {
|
||||
int n = g_app.open_note_target;
|
||||
const char* sql_id = ge::entity_index_lookup(g_idx, g_graph.nodes[n].user_data);
|
||||
if (sql_id) {
|
||||
|
||||
// Detectar si el nodo es de tipo Group.
|
||||
bool is_group = false;
|
||||
uint16_t tid = g_graph.nodes[n].type_id;
|
||||
const char* type_name = (tid < (uint16_t)g_graph.type_count
|
||||
&& g_graph.types[tid].name)
|
||||
? g_graph.types[tid].name : "";
|
||||
if (type_name && std::strcmp(type_name, "Group") == 0) is_group = true;
|
||||
|
||||
if (is_group && sql_id) {
|
||||
// Drill-in: abrir tableview filtrada por group_id = sql_id.
|
||||
g_app.table_filter_group_id = sql_id;
|
||||
const char* lbl = graph::graph_label(&g_graph, g_graph.nodes[n].label_idx);
|
||||
g_app.table_filter_group_name = lbl ? lbl : sql_id;
|
||||
// Reset filtros que pueden ocultar las filas del grupo.
|
||||
g_app.table_search_buf[0] = 0;
|
||||
g_app.table_col_filters.clear();
|
||||
g_app.table_show_all = true;
|
||||
g_app.panel_table = true;
|
||||
ImGui::SetWindowFocus("Table");
|
||||
} else if (sql_id) {
|
||||
std::string md;
|
||||
ge::entity_get_notes(g_app.input_db_path.c_str(), sql_id, &md);
|
||||
g_app.note_node = n;
|
||||
g_app.note_entity_id = sql_id;
|
||||
const char* lbl = graph::graph_label(&g_graph, g_graph.nodes[n].label_idx);
|
||||
g_app.note_entity_label = lbl ? lbl : "";
|
||||
uint16_t tid = g_graph.nodes[n].type_id;
|
||||
g_app.note_entity_type = (tid < (uint16_t)g_graph.type_count
|
||||
&& g_graph.types[tid].name)
|
||||
? g_graph.types[tid].name : "";
|
||||
g_app.note_entity_type = type_name;
|
||||
// Asegura buffer >= max(64KB, contenido + holgura).
|
||||
size_t need = md.size() + 4096;
|
||||
if (need < 65536) need = 65536;
|
||||
|
||||
@@ -1733,12 +1733,41 @@ void render_one_table(AppState& app, std::vector<int>& visible_indices) {
|
||||
} // namespace
|
||||
|
||||
void views_table(AppState& app) {
|
||||
if (!app.panel_table) return;
|
||||
// Si el panel se cierra, limpiamos el filtro de grupo (RAM-only).
|
||||
if (!app.panel_table) {
|
||||
if (!app.table_filter_group_id.empty()) {
|
||||
app.table_filter_group_id.clear();
|
||||
app.table_filter_group_name.clear();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!ImGui::Begin("Table", &app.panel_table)) {
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
// Breadcrumb de drill-in por grupo (issue 0035d).
|
||||
if (!app.table_filter_group_id.empty()) {
|
||||
// Contar filas que pertenecen a este grupo (sin aplicar otros filtros).
|
||||
size_t n_group = 0;
|
||||
for (const auto& r : app.table_rows) {
|
||||
if (r.group_id == app.table_filter_group_id) ++n_group;
|
||||
}
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.85f, 0.75f, 0.35f, 1.0f));
|
||||
ImGui::Text(TI_FOLDER " Group: %s (%zu)",
|
||||
app.table_filter_group_name.empty()
|
||||
? app.table_filter_group_id.c_str()
|
||||
: app.table_filter_group_name.c_str(),
|
||||
n_group);
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::SameLine();
|
||||
if (fn_ui::button("Clear group filter", fn_ui::ButtonVariant::Subtle)) {
|
||||
app.table_filter_group_id.clear();
|
||||
app.table_filter_group_name.clear();
|
||||
}
|
||||
ImGui::Separator();
|
||||
}
|
||||
|
||||
// Toolbar superior: search + show all.
|
||||
ImGui::SetNextItemWidth(220);
|
||||
ImGui::InputTextWithHint("##tsearch", TI_SEARCH " filter name/id...",
|
||||
@@ -1777,12 +1806,15 @@ void views_table(AppState& app) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Indices por tipo.
|
||||
// Indices por tipo. Si hay filtro de grupo (issue 0035d) acotamos los
|
||||
// types tabulables a los presentes dentro del grupo.
|
||||
std::vector<std::string> types_present;
|
||||
types_present.reserve(8);
|
||||
{
|
||||
std::unordered_set<std::string> seen;
|
||||
for (const auto& r : app.table_rows) {
|
||||
if (!app.table_filter_group_id.empty()
|
||||
&& r.group_id != app.table_filter_group_id) continue;
|
||||
if (seen.insert(r.type_ref).second) types_present.push_back(r.type_ref);
|
||||
}
|
||||
std::sort(types_present.begin(), types_present.end());
|
||||
@@ -1793,6 +1825,10 @@ void views_table(AppState& app) {
|
||||
v.reserve(app.table_rows.size());
|
||||
for (size_t i = 0; i < app.table_rows.size(); ++i) {
|
||||
const auto& r = app.table_rows[i];
|
||||
// Drill-in por grupo (issue 0035d): si hay filtro activo, solo
|
||||
// pasan filas cuyo group_id coincide.
|
||||
if (!app.table_filter_group_id.empty()
|
||||
&& r.group_id != app.table_filter_group_id) continue;
|
||||
if (type_filter && r.type_ref != type_filter) continue;
|
||||
if (app.table_search_buf[0]
|
||||
&& !ci_contains(r.name, app.table_search_buf)
|
||||
|
||||
@@ -210,6 +210,7 @@ struct AppState {
|
||||
std::string type_ref;
|
||||
std::string status;
|
||||
std::string updated_at;
|
||||
std::string group_id; // si pertenece a un Group, su sql id; "" si no
|
||||
int neighbors = 0;
|
||||
int node_idx = -1;
|
||||
};
|
||||
@@ -225,6 +226,12 @@ struct AppState {
|
||||
std::unordered_map<int, std::string> table_col_filters;
|
||||
char table_filter_input[96] = {}; // buffer del popup activo
|
||||
int table_filter_pending_col = -1; // col_user_id en edicion
|
||||
// Drill-in por grupo (issue 0035d): cuando esta seteado, la tabla solo
|
||||
// muestra filas cuyo `group_id` coincida. Header muestra "Group: <name>
|
||||
// (N)" + boton "Clear group filter". RAM-only — al cerrar el panel se
|
||||
// limpia.
|
||||
std::string table_filter_group_id; // "" = sin filtro
|
||||
std::string table_filter_group_name; // label legible
|
||||
|
||||
// ---- Type Editor (issue 0007) ------------------------------------------
|
||||
// Draft del editor de tipos. Se inicializa con una copia de parsed_types
|
||||
|
||||
Reference in New Issue
Block a user