feat(stats): optimize dashboard data loading with internal ui endpoints
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user