Files
kanban_cpp/panel_board.cpp
T
Egutierrez a76ec74338 feat: initial scaffold kanban_cpp v0.1.0
C++ ImGui kanban for steering LLM agents. Six panels (Board, Calendar,
Dashboard, Agent runs, Worktrees, DoD inspector) wired to registry
functions http_request, kpi_card, sparkline, agent_runs_timeline,
dod_evidence_panel. Backend Go on :8403 (independent operations.db from
kanban_web).
2026-05-18 18:46:09 +02:00

114 lines
4.3 KiB
C++

// panel_board.cpp — columns + cards Kanban panel.
#include "panels.h"
#include "core/icons_tabler.h"
#include <imgui.h>
#include <ctime>
namespace kanban_cpp {
void refresh_data(AppState& s) {
std::string err;
s.cards = list_cards(s.cfg, err);
if (!err.empty()) s.last_refresh_error = "cards: " + err;
s.columns = list_columns(s.cfg, err);
if (!err.empty()) s.last_refresh_error += " columns: " + err;
s.backend_ok = health(s.cfg);
s.last_refresh_ts = std::time(nullptr);
}
void draw_board(AppState& s, bool* p_open) {
if (!ImGui::Begin(TI_LAYOUT_KANBAN " Board", p_open)) {
ImGui::End();
return;
}
// Toolbar
if (ImGui::Button(TI_REFRESH " Refresh")) refresh_data(s);
ImGui::SameLine();
if (s.backend_ok) {
ImGui::TextColored(ImVec4(0.4f, 0.85f, 0.4f, 1.0f), TI_CHECK " backend :8403");
} else {
ImGui::TextColored(ImVec4(0.85f, 0.4f, 0.4f, 1.0f), TI_ALERT_TRIANGLE " backend offline (:8403)");
}
if (!s.last_refresh_error.empty()) {
ImGui::SameLine();
ImGui::TextColored(ImVec4(0.85f, 0.6f, 0.2f, 1.0f), "%s", s.last_refresh_error.c_str());
}
ImGui::Separator();
// Empty state
if (s.columns.empty()) {
ImGui::TextDisabled("No columns yet. Pulsa Refresh o lanza el backend en :8403.");
ImGui::End();
return;
}
// Render columns left-to-right
const float col_w = 280.0f;
if (ImGui::BeginChild("##board_scroll", ImVec2(0, 0), false,
ImGuiWindowFlags_HorizontalScrollbar)) {
for (size_t ci = 0; ci < s.columns.size(); ++ci) {
const auto& col = s.columns[ci];
ImGui::SameLine();
ImGui::BeginChild((std::string("##col_") + col.id).c_str(),
ImVec2(col_w, 0), true);
ImGui::TextUnformatted(col.name.c_str());
ImGui::SameLine();
int count = 0;
for (const auto& c : s.cards) if (c.column_id == col.id) ++count;
ImGui::TextDisabled("(%d)", count);
ImGui::Separator();
for (const auto& card : s.cards) {
if (card.column_id != col.id) continue;
ImGui::PushID(card.id.c_str());
ImGui::BeginChild("##card", ImVec2(0, 70), true,
ImGuiWindowFlags_NoScrollbar);
ImGui::TextUnformatted(card.title.c_str());
if (!card.priority.empty()) {
ImVec4 col_p(0.6f, 0.6f, 0.6f, 1);
if (card.priority == "high") col_p = {0.95f, 0.55f, 0.2f, 1};
else if (card.priority == "critical") col_p = {0.95f, 0.25f, 0.25f, 1};
else if (card.priority == "low") col_p = {0.45f, 0.7f, 0.95f, 1};
ImGui::TextColored(col_p, TI_FLAG " %s", card.priority.c_str());
}
if (!card.assignee.empty()) {
ImGui::SameLine();
ImGui::TextDisabled(TI_USER " %s", card.assignee.c_str());
}
ImGui::EndChild();
if (ImGui::IsItemHovered() && ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
ImGui::OpenPopup("##card_ctx");
}
if (ImGui::BeginPopup("##card_ctx")) {
ImGui::TextDisabled("Move to:");
for (const auto& tgt : s.columns) {
if (tgt.id == card.column_id) continue;
if (ImGui::MenuItem(tgt.name.c_str())) {
std::string err;
if (!move_card(s.cfg, card.id, tgt.id, err))
s.last_refresh_error = "move: " + err;
else refresh_data(s);
}
}
ImGui::Separator();
if (ImGui::MenuItem(TI_PLAYER_PLAY " Launch agent workflow")) {
std::string run_id, err;
if (!launch_workflow(s.cfg, card.id, run_id, err))
s.last_refresh_error = "launch: " + err;
}
ImGui::EndPopup();
}
ImGui::PopID();
}
ImGui::EndChild();
}
}
ImGui::EndChild();
ImGui::End();
}
} // namespace kanban_cpp