#include "panels.h" #include "data.h" #include #include #include #include #include #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& 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 snapshot; { std::lock_guard g(state().mu); snapshot = state().issues; } // Bucket issues by status std::vector> 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