255e8dcf71
Frontend C++ ImGui (main.cpp + 4 paneles) + backend Go (HTTP + SQLite + fsnotify + SSE). Reusa parse/scan/watch funcs del registry (issue 0130a).
151 lines
4.5 KiB
C++
151 lines
4.5 KiB
C++
#include "panels.h"
|
|
#include "data.h"
|
|
|
|
#include <imgui.h>
|
|
#include <imgui_internal.h>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "core/icons_tabler.h"
|
|
|
|
namespace kanban {
|
|
|
|
static const char* k_columns[] = {"pendiente", "in-progress", "bloqueado", "completado"};
|
|
static const int k_n_columns = sizeof(k_columns) / sizeof(k_columns[0]);
|
|
|
|
static ImU32 priority_color(const std::string& p) {
|
|
if (p == "critica") return IM_COL32(255, 80, 80, 255);
|
|
if (p == "alta") return IM_COL32(255, 165, 0, 255);
|
|
if (p == "media") return IM_COL32(120, 170, 255, 255);
|
|
if (p == "baja") return IM_COL32(140, 140, 140, 255);
|
|
return IM_COL32(180, 180, 180, 255);
|
|
}
|
|
|
|
static void draw_card(const Issue& iss) {
|
|
ImGui::PushID(iss.id.c_str());
|
|
|
|
ImVec2 size(0.0f, 64.0f);
|
|
ImGui::BeginChild("card", size, true, ImGuiWindowFlags_NoScrollbar);
|
|
|
|
// Header: id + priority badge
|
|
ImGui::TextColored(ImColor(180, 180, 180, 255), "%s", iss.id.c_str());
|
|
ImGui::SameLine();
|
|
{
|
|
ImVec2 cur = ImGui::GetCursorScreenPos();
|
|
ImU32 c = priority_color(iss.priority);
|
|
ImDrawList* dl = ImGui::GetWindowDrawList();
|
|
float r = 5.0f;
|
|
dl->AddCircleFilled(ImVec2(cur.x + r, cur.y + r + 2), r, c);
|
|
ImGui::Dummy(ImVec2(2 * r + 4, 2 * r));
|
|
ImGui::SameLine();
|
|
ImGui::TextUnformatted(iss.priority.c_str());
|
|
}
|
|
|
|
// Title (truncate to ~60 chars)
|
|
std::string t = iss.title;
|
|
if (t.size() > 70) t = t.substr(0, 67) + "...";
|
|
ImGui::TextWrapped("%s", t.c_str());
|
|
|
|
// First domain chip
|
|
if (!iss.domain.empty()) {
|
|
ImGui::TextColored(ImColor(140, 200, 255, 255), TI_TAG " %s", iss.domain[0].c_str());
|
|
}
|
|
|
|
ImGui::EndChild();
|
|
|
|
if (ImGui::IsItemClicked()) {
|
|
refresh_issue_detail(iss.id);
|
|
}
|
|
|
|
// Drag source
|
|
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) {
|
|
ImGui::SetDragDropPayload("KANBAN_ISSUE", iss.id.c_str(), iss.id.size() + 1);
|
|
ImGui::Text("%s %s", iss.id.c_str(), t.c_str());
|
|
ImGui::EndDragDropSource();
|
|
}
|
|
|
|
ImGui::PopID();
|
|
}
|
|
|
|
static void draw_column(const char* status_key, std::vector<const Issue*>& items) {
|
|
ImGui::TableNextColumn();
|
|
// Column header with count
|
|
ImGui::PushFont(nullptr);
|
|
ImGui::TextColored(ImColor(220, 220, 220, 255), "%s (%zu)", status_key, items.size());
|
|
ImGui::PopFont();
|
|
ImGui::Separator();
|
|
|
|
// Drop target on the whole column
|
|
ImVec2 region = ImGui::GetContentRegionAvail();
|
|
ImGui::BeginChild((std::string("col_") + status_key).c_str(), region, false);
|
|
|
|
for (const auto* iss : items) {
|
|
draw_card(*iss);
|
|
}
|
|
// Bottom drop zone
|
|
ImGui::InvisibleButton("col_drop", ImVec2(-1, 16));
|
|
if (ImGui::BeginDragDropTarget()) {
|
|
if (const ImGuiPayload* p = ImGui::AcceptDragDropPayload("KANBAN_ISSUE")) {
|
|
std::string id((const char*)p->Data, p->DataSize - 1);
|
|
patch_issue_status(id, status_key);
|
|
}
|
|
ImGui::EndDragDropTarget();
|
|
}
|
|
ImGui::EndChild();
|
|
|
|
if (ImGui::BeginDragDropTarget()) {
|
|
if (const ImGuiPayload* p = ImGui::AcceptDragDropPayload("KANBAN_ISSUE")) {
|
|
std::string id((const char*)p->Data, p->DataSize - 1);
|
|
patch_issue_status(id, status_key);
|
|
}
|
|
ImGui::EndDragDropTarget();
|
|
}
|
|
}
|
|
|
|
void draw_board() {
|
|
if (!ImGui::Begin(TI_LAYOUT_KANBAN " Board")) {
|
|
ImGui::End();
|
|
return;
|
|
}
|
|
|
|
// Snapshot under lock
|
|
std::vector<Issue> snapshot;
|
|
{
|
|
std::lock_guard<std::mutex> g(state().mu);
|
|
snapshot = state().issues;
|
|
}
|
|
|
|
// Bucket issues by status
|
|
std::vector<std::vector<const Issue*>> buckets(k_n_columns);
|
|
int filtered_out = 0;
|
|
for (const auto& iss : snapshot) {
|
|
if (!passes_filters(iss)) {
|
|
filtered_out++;
|
|
continue;
|
|
}
|
|
for (int c = 0; c < k_n_columns; ++c) {
|
|
if (iss.status == k_columns[c]) {
|
|
buckets[c].push_back(&iss);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
ImGui::Text("%zu total — %d filtered out", snapshot.size(), filtered_out);
|
|
ImGui::Separator();
|
|
|
|
if (ImGui::BeginTable("board_table", k_n_columns,
|
|
ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingStretchSame)) {
|
|
ImGui::TableNextRow();
|
|
for (int c = 0; c < k_n_columns; ++c) {
|
|
draw_column(k_columns[c], buckets[c]);
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
|
|
ImGui::End();
|
|
}
|
|
|
|
} // namespace kanban
|