feat(dashboard): v0.3.0 — Status panel, dashboard_panel/grid, AppConfig pattern

- main.cpp: bump 0.2.0 → 0.3.0; añadir Status section en Settings via
  settings_window_add_section (fuente API/SQLite, URL, DB path, Reload).
- views.cpp: chart_panel manual + BeginChild custom → dashboard_panel/grid
  del registry. KPI cards con sparkline a la derecha, altura responsive.
- views.h: signature de draw_kpi_row pasa RegistryData (para sparkline).
- CMakeLists.txt: añadir process_state_machine.cpp (dependencia de
  process_runner tras issue 0045).
This commit is contained in:
2026-04-29 00:58:17 +02:00
parent a728e2b0a1
commit 00a19d8632
4 changed files with 75 additions and 18 deletions
+30 -15
View File
@@ -9,9 +9,9 @@
#include "viz/pie_chart.h"
#include "viz/table_view.h"
#include "viz/sparkline.h"
#include "core/icons_tabler.h"
#include "core/dashboard_panel.h"
#include "core/dashboard_grid.h"
#include "core/fps_overlay.h"
#include "core/fullscreen_window.h"
#include "core/tokens.h"
#include "core/page_header.h"
@@ -68,7 +68,8 @@ static void trigger_reload() {
// KPI row
// ---------------------------------------------------------------------------
void draw_kpi_row(const RegistryStats& stats) {
void draw_kpi_row(const RegistryData& data) {
const RegistryStats& stats = data.stats;
float tested_pct = stats.total_functions > 0
? 100.0f * stats.tested_functions / stats.total_functions : 0.0f;
float pure_pct = stats.total_functions > 0
@@ -77,22 +78,30 @@ void draw_kpi_row(const RegistryStats& stats) {
const ImGuiTableFlags flags = ImGuiTableFlags_SizingStretchSame
| ImGuiTableFlags_NoPadOuterX;
// Sparkline shared: ultimos 30 dias de creacion de funciones (timeline real
// del registry). Si no hay datos cargados, queda vacio y el card mostrara
// solo valor + delta placeholder.
const float* spark_data = data.date_values.empty() ? nullptr : data.date_values.data();
const int spark_count = static_cast<int>(data.date_values.size());
if (ImGui::BeginTable("##kpi_grid", 4, flags)) {
struct KPI { const char* label; float value; const char* fmt; };
struct KPI { const char* label; float value; const char* fmt; const char* icon; };
const KPI cards[8] = {
{"Functions", static_cast<float>(stats.total_functions), "%.0f"},
{"Types", static_cast<float>(stats.total_types), "%.0f"},
{"Apps", static_cast<float>(stats.total_apps), "%.0f"},
{"Analysis", static_cast<float>(stats.total_analysis), "%.0f"},
{"Unit Tests", static_cast<float>(stats.total_unit_tests), "%.0f"},
{"Proposals", static_cast<float>(stats.total_proposals), "%.0f"},
{"Tested", tested_pct, "%.0f%%"},
{"Pure", pure_pct, "%.0f%%"},
{"Functions", static_cast<float>(stats.total_functions), "%.0f", TI_FUNCTION},
{"Types", static_cast<float>(stats.total_types), "%.0f", TI_HEXAGON},
{"Apps", static_cast<float>(stats.total_apps), "%.0f", TI_APPS},
{"Analysis", static_cast<float>(stats.total_analysis), "%.0f", TI_FLASK},
{"Unit Tests", static_cast<float>(stats.total_unit_tests), "%.0f", TI_TEST_PIPE},
{"Proposals", static_cast<float>(stats.total_proposals), "%.0f", TI_BULB},
{"Tested", tested_pct, "%.0f%%", TI_CIRCLE_CHECK},
{"Pure", pure_pct, "%.0f%%", TI_HEART},
};
for (int i = 0; i < 8; i++) {
if (i % 4 == 0) ImGui::TableNextRow();
ImGui::TableSetColumnIndex(i % 4);
kpi_card(cards[i].label, cards[i].value, 0.0f, nullptr, 0, cards[i].fmt);
kpi_card(cards[i].label, cards[i].value, 0.0f,
spark_data, spark_count,
cards[i].fmt, cards[i].icon);
}
ImGui::EndTable();
}
@@ -587,8 +596,8 @@ static void draw_actions_bar() {
void draw_dashboard(RegistryData& data) {
// Tema aplicado por fn::run_app() (app_base.h, ThemeMode::FnDark default).
// FPS overlay lo dibuja app_base.cpp segun settings().show_fps — no llamarlo aqui.
fps_overlay();
fullscreen_window_begin("##dashboard");
char subtitle[128];
@@ -604,10 +613,16 @@ void draw_dashboard(RegistryData& data) {
draw_actions_bar();
ImGui::Dummy(ImVec2(0, fn_tokens::spacing::sm));
draw_kpi_row(data.stats);
draw_kpi_row(data);
ImGui::Dummy(ImVec2(0, fn_tokens::spacing::md));
constexpr float chart_h = 260.0f;
// Altura del bloque de charts proporcional al espacio disponible: ~28% del
// espacio restante despues de KPIs/header/tabs (clamped a [200, 360] px
// para que ni se aplaste ni domine en pantallas grandes).
const float remaining_h = ImGui::GetContentRegionAvail().y;
float chart_h = remaining_h * 0.32f;
if (chart_h < 200.0f) chart_h = 200.0f;
if (chart_h > 360.0f) chart_h = 360.0f;
draw_charts(data, chart_h);
ImGui::Dummy(ImVec2(0, fn_tokens::spacing::md));