feat(stats): optimize dashboard data loading with internal ui endpoints

This commit is contained in:
Alexander Kalinovsky
2026-04-01 19:05:57 +03:00
parent 19d659df6b
commit 9b9c7b5575
11 changed files with 552 additions and 140 deletions

View File

@@ -175,74 +175,55 @@
<section class="board">
<div class="column">
<h2 class="column-title">Backlog</h2>
<div class="task-list">
{% for task in backlog_tasks %}
<button
type="button"
class="task-card"
data-task='{{ task|tojson }}'
>
<div class="task-title">{{ task.title }}</div>
<div class="task-meta">Created: {{ task.created_at }}</div>
</button>
{% endfor %}
{% if not backlog_tasks %}
<div class="empty">No tasks</div>
{% endif %}
<div class="task-list" id="backlog-list">
<div class="empty">Loading tasks...</div>
</div>
</div>
<div class="column">
<h2 class="column-title">In Progress</h2>
<div class="task-list">
{% for task in in_progress_tasks %}
<button
type="button"
class="task-card"
data-task='{{ task|tojson }}'
>
<div class="task-title">{{ task.title }}</div>
<div class="task-meta">Started: {{ task.started_at }}</div>
</button>
{% endfor %}
{% if not in_progress_tasks %}
<div class="empty">No tasks</div>
{% endif %}
<div class="task-list" id="in-progress-list">
<div class="empty">Loading tasks...</div>
</div>
</div>
<div class="column">
<h2 class="column-title">Done</h2>
<div class="task-list">
{% for task in done_tasks %}
<button
type="button"
class="task-card"
data-task='{{ task|tojson }}'
>
<div class="task-title">{{ task.title }}</div>
<div class="task-meta">Done: {{ task.done_at }}</div>
</button>
{% endfor %}
{% if not done_tasks %}
<div class="empty">No tasks</div>
{% endif %}
<div class="task-list" id="done-list">
<div class="empty">Loading tasks...</div>
</div>
</div>
</section>
</div>
<script>
const cards = Array.from(document.querySelectorAll(".task-card"));
const selectedTitle = document.getElementById("selected-title");
const selectedStatus = document.getElementById("selected-status");
const selectedStartedAt = document.getElementById("selected-started-at");
const selectedDoneAt = document.getElementById("selected-done-at");
const selectedCycleTime = document.getElementById("selected-cycle-time");
const selectedCreatedAt = document.getElementById("selected-created-at");
const boardLists = {
backlog: document.getElementById("backlog-list"),
in_progress: document.getElementById("in-progress-list"),
done: document.getElementById("done-list"),
};
const detailFields = [
selectedTitle,
selectedStatus,
selectedStartedAt,
selectedDoneAt,
selectedCycleTime,
selectedCreatedAt,
];
function renderTask(task) {
function setDetailsLoading() {
detailFields.forEach((field) => {
field.textContent = "Loading...";
});
}
function renderTaskDetails(task) {
selectedTitle.textContent = task.title || "No task selected";
selectedStatus.textContent = task.status || "—";
selectedStartedAt.textContent = task.started_at || "—";
@@ -251,18 +232,130 @@
selectedCreatedAt.textContent = task.created_at || "—";
}
cards.forEach((card, index) => {
if (index === 0) {
card.classList.add("active");
}
function renderTaskDetailsError() {
selectedTitle.textContent = "Failed to load task";
selectedStatus.textContent = "—";
selectedStartedAt.textContent = "—";
selectedDoneAt.textContent = "—";
selectedCycleTime.textContent = "—";
selectedCreatedAt.textContent = "—";
}
function createTaskCard(task) {
const card = document.createElement("button");
card.type = "button";
card.className = "task-card";
card.dataset.taskId = task.id;
const title = document.createElement("div");
title.className = "task-title";
title.textContent = task.title;
const meta = document.createElement("div");
meta.className = "task-meta";
meta.textContent = `${task.display_date_label}: ${task.display_date_value || "—"}`;
card.appendChild(title);
card.appendChild(meta);
card.addEventListener("click", () => {
cards.forEach((item) => item.classList.remove("active"));
card.classList.add("active");
renderTask(JSON.parse(card.dataset.task));
setActiveCard(card);
loadTaskDetails(task.id);
});
});
return card;
}
function renderTaskList(listElement, tasks) {
listElement.replaceChildren();
if (!tasks.length) {
const empty = document.createElement("div");
empty.className = "empty";
empty.textContent = "No tasks";
listElement.appendChild(empty);
return;
}
tasks.forEach((task) => {
listElement.appendChild(createTaskCard(task));
});
}
function renderBoard(board) {
renderTaskList(boardLists.backlog, board.backlog_tasks);
renderTaskList(boardLists.in_progress, board.in_progress_tasks);
renderTaskList(boardLists.done, board.done_tasks);
}
function setActiveCard(activeCard) {
document.querySelectorAll(".task-card").forEach((card) => {
card.classList.toggle("active", card === activeCard);
});
}
async function loadTaskDetails(taskId) {
setDetailsLoading();
try {
const response = await fetch(`/ui_data/tasks/${taskId}`);
if (!response.ok) {
throw new Error("Failed to load task details");
}
const task = await response.json();
renderTaskDetails(task);
} catch (error) {
renderTaskDetailsError();
}
}
function getFirstTaskId(board) {
const groups = [board.backlog_tasks, board.in_progress_tasks, board.done_tasks];
for (const group of groups) {
if (group.length) {
return group[0].id;
}
}
return null;
}
async function loadBoard() {
try {
const response = await fetch("/ui_data/stats/board");
if (!response.ok) {
throw new Error("Failed to load board");
}
const board = await response.json();
renderBoard(board);
const firstTaskId = getFirstTaskId(board);
if (firstTaskId === null) {
return;
}
const firstCard = document.querySelector(`.task-card[data-task-id="${firstTaskId}"]`);
if (firstCard !== null) {
setActiveCard(firstCard);
}
await loadTaskDetails(firstTaskId);
} catch (error) {
Object.values(boardLists).forEach((listElement) => {
listElement.replaceChildren();
const empty = document.createElement("div");
empty.className = "empty";
empty.textContent = "Failed to load tasks";
listElement.appendChild(empty);
});
renderTaskDetailsError();
}
}
void loadBoard();
</script>
</body>
</html>