#include "views.h" #include "imgui.h" #include "implot.h" #include "viz/kpi_card.h" #include "viz/bar_chart.h" #include "viz/pie_chart.h" #include "viz/table_view.h" #include "viz/sparkline.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" #include "core/empty_state.h" #include "core/badge.h" #include #include static std::vector to_cstr(const std::vector& v) { std::vector out; out.reserve(v.size()); for (auto& s : v) out.push_back(s.c_str()); return out; } void draw_kpi_row(const RegistryStats& 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 ? 100.0f * stats.pure_functions / stats.total_functions : 0.0f; dashboard_grid_begin(8, 8.0f); kpi_card("Functions", static_cast(stats.total_functions), 0, nullptr, 0, "%.0f"); dashboard_grid_next(); kpi_card("Types", static_cast(stats.total_types), 0, nullptr, 0, "%.0f"); dashboard_grid_next(); kpi_card("Apps", static_cast(stats.total_apps), 0, nullptr, 0, "%.0f"); dashboard_grid_next(); kpi_card("Analysis", static_cast(stats.total_analysis), 0, nullptr, 0, "%.0f"); dashboard_grid_next(); kpi_card("Unit Tests", static_cast(stats.total_unit_tests), 0, nullptr, 0, "%.0f"); dashboard_grid_next(); kpi_card("Proposals", static_cast(stats.total_proposals), 0, nullptr, 0, "%.0f"); dashboard_grid_next(); kpi_card("Tested", tested_pct, 0, nullptr, 0, "%.0f%%"); dashboard_grid_next(); kpi_card("Pure", pure_pct, 0, nullptr, 0, "%.0f%%"); dashboard_grid_end(); } void draw_charts(RegistryData& data, float height) { dashboard_grid_begin(4, 8.0f); if (dashboard_panel_begin("By Language", 0, height)) { auto labels = to_cstr(data.lang_labels); if (!labels.empty()) bar_chart("##lang", labels.data(), data.lang_values.data(), static_cast(labels.size())); } dashboard_panel_end(); dashboard_grid_next(); if (dashboard_panel_begin("By Domain", 0, height)) { auto labels = to_cstr(data.domain_labels); if (!labels.empty()) bar_chart("##domain", labels.data(), data.domain_values.data(), static_cast(labels.size())); } dashboard_panel_end(); dashboard_grid_next(); if (dashboard_panel_begin("Purity", 0, height)) { const char* labels[] = {"Pure", "Impure"}; float values[] = {static_cast(data.stats.pure_functions), static_cast(data.stats.impure_functions)}; pie_chart("##purity", labels, values, 2); } dashboard_panel_end(); dashboard_grid_next(); if (dashboard_panel_begin("Kind", 0, height)) { auto labels = to_cstr(data.kind_labels); if (!labels.empty()) pie_chart("##kind", labels.data(), data.kind_values.data(), static_cast(labels.size())); } dashboard_panel_end(); dashboard_grid_end(); } void draw_recent_functions(const std::vector& funcs) { if (funcs.empty()) { empty_state("( no data )", "No functions yet", "Run 'fn index' to populate the registry"); return; } const char* headers[] = {"Name", "Lang", "Domain", "Kind", "Purity", "Tested", "Created"}; constexpr int cols = 7; std::vector cell_strings; cell_strings.reserve(funcs.size() * cols); for (auto& f : funcs) { cell_strings.push_back(f.name); cell_strings.push_back(f.lang); cell_strings.push_back(f.domain); cell_strings.push_back(f.kind); cell_strings.push_back(f.purity); cell_strings.push_back(f.tested ? "yes" : "no"); cell_strings.push_back(f.created_at.substr(0, 10)); } auto cells = to_cstr(cell_strings); table_view("##recent", headers, cols, cells.data(), static_cast(funcs.size())); } void draw_apps_list(const std::vector& apps) { if (apps.empty()) { empty_state("( no data )", "No apps registered", "Clone apps with 'fn app clone ' or run 'fn sync'"); return; } const char* headers[] = {"Name", "Lang", "Domain", "Framework", "Description"}; constexpr int cols = 5; std::vector cell_strings; cell_strings.reserve(apps.size() * cols); for (auto& a : apps) { cell_strings.push_back(a.name); cell_strings.push_back(a.lang); cell_strings.push_back(a.domain); cell_strings.push_back(a.framework); cell_strings.push_back(a.description); } auto cells = to_cstr(cell_strings); table_view("##apps", headers, cols, cells.data(), static_cast(apps.size())); } void draw_analysis_list(const std::vector& analyses) { if (analyses.empty()) { empty_state("( no data )", "No analysis yet", "Create one with 'fn run init_jupyter_analysis '"); return; } const char* headers[] = {"Name", "Domain", "Description"}; constexpr int cols = 3; std::vector cell_strings; cell_strings.reserve(analyses.size() * cols); for (auto& a : analyses) { cell_strings.push_back(a.name); cell_strings.push_back(a.domain); cell_strings.push_back(a.description); } auto cells = to_cstr(cell_strings); table_view("##analysis", headers, cols, cells.data(), static_cast(analyses.size())); } void draw_types_list(const std::vector& types) { if (types.empty()) { empty_state("( no data )", "No types yet", "Types are indexed from the registry alongside functions"); return; } const char* headers[] = {"Name", "Lang", "Domain", "Algebraic", "Description"}; constexpr int cols = 5; std::vector cell_strings; cell_strings.reserve(types.size() * cols); for (auto& t : types) { cell_strings.push_back(t.name); cell_strings.push_back(t.lang); cell_strings.push_back(t.domain); cell_strings.push_back(t.algebraic); cell_strings.push_back(t.description); } auto cells = to_cstr(cell_strings); table_view("##types", headers, cols, cells.data(), static_cast(types.size())); } void draw_dashboard(RegistryData& data) { // Aplicar tema una sola vez por vida de la app. static bool theme_applied = false; if (!theme_applied) { fn_tokens::apply_dark_theme(); theme_applied = true; } fps_overlay(); fullscreen_window_begin("##dashboard"); // Subtitle con conteos — contexto rápido para el usuario char subtitle[128]; std::snprintf(subtitle, sizeof(subtitle), "%d functions · %d types · %d apps · %d analyses", data.stats.total_functions, data.stats.total_types, data.stats.total_apps, data.stats.total_analysis); // Header con acción Reload a la derecha page_header_begin("fn_registry Dashboard", subtitle); ImGui::SameLine(ImGui::GetWindowWidth() - 120.0f); if (ImGui::Button("Reload")) { ImGui::GetIO().UserData = reinterpret_cast(1); } page_header_end(); // KPIs draw_kpi_row(data.stats); ImGui::Dummy(ImVec2(0, fn_tokens::spacing::md)); // Charts — 35% de la altura restante float remaining = ImGui::GetContentRegionAvail().y; float chart_h = remaining * 0.35f; if (chart_h < 150.0f) chart_h = 150.0f; draw_charts(data, chart_h); ImGui::Dummy(ImVec2(0, fn_tokens::spacing::md)); // Tables if (ImGui::BeginTabBar("##tables")) { if (ImGui::BeginTabItem("Recent Functions")) { draw_recent_functions(data.recent_funcs); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Apps")) { draw_apps_list(data.apps); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Analysis")) { draw_analysis_list(data.analyses); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Types")) { draw_types_list(data.types); ImGui::EndTabItem(); } ImGui::EndTabBar(); } fullscreen_window_end(); }