fix(mission): show project-level todo tasks in mission control detail
This commit is contained in:
parent
071d926781
commit
c019a0367b
@ -595,6 +595,37 @@ class MissionService:
|
|||||||
(project_id,),
|
(project_id,),
|
||||||
) or []
|
) or []
|
||||||
|
|
||||||
|
project_open_todo_count = 0
|
||||||
|
project_open_todo_titles: list[str] = []
|
||||||
|
if MissionService._table_exists("sag_todo_steps"):
|
||||||
|
project_todo_row = execute_query_single(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
COUNT(*) FILTER (
|
||||||
|
WHERE t.deleted_at IS NULL
|
||||||
|
AND COALESCE(t.is_done, FALSE) = FALSE
|
||||||
|
) AS open_todo_count,
|
||||||
|
ARRAY_REMOVE(
|
||||||
|
ARRAY_AGG(
|
||||||
|
CASE
|
||||||
|
WHEN t.deleted_at IS NULL
|
||||||
|
AND COALESCE(t.is_done, FALSE) = FALSE
|
||||||
|
THEN t.title
|
||||||
|
END
|
||||||
|
ORDER BY COALESCE(t.due_date, DATE '9999-12-31') ASC, t.id ASC
|
||||||
|
),
|
||||||
|
NULL
|
||||||
|
) AS open_todo_titles
|
||||||
|
FROM sag_todo_steps t
|
||||||
|
WHERE t.sag_id = %s
|
||||||
|
""",
|
||||||
|
(project_id,),
|
||||||
|
) or {}
|
||||||
|
project_open_todo_count = int(project_todo_row.get("open_todo_count") or 0)
|
||||||
|
titles_raw = project_todo_row.get("open_todo_titles") or []
|
||||||
|
if isinstance(titles_raw, list):
|
||||||
|
project_open_todo_titles = [str(item).strip() for item in titles_raw if str(item or "").strip()]
|
||||||
|
|
||||||
# Fallback for case-backed projects: fetch directly related/under cases from relation table.
|
# Fallback for case-backed projects: fetch directly related/under cases from relation table.
|
||||||
# This is used when a project is a case of type project/projekt and tasks are linked as case relations.
|
# This is used when a project is a case of type project/projekt and tasks are linked as case relations.
|
||||||
if not tasks and MissionService._table_exists("sag_relationer"):
|
if not tasks and MissionService._table_exists("sag_relationer"):
|
||||||
@ -667,6 +698,8 @@ class MissionService:
|
|||||||
"milestones": [dict(row) for row in milestones],
|
"milestones": [dict(row) for row in milestones],
|
||||||
"blockers": [dict(row) for row in blockers],
|
"blockers": [dict(row) for row in blockers],
|
||||||
"tasks": [dict(row) for row in tasks],
|
"tasks": [dict(row) for row in tasks],
|
||||||
|
"project_open_todo_count": project_open_todo_count,
|
||||||
|
"project_open_todo_titles": project_open_todo_titles,
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@ -1328,6 +1328,10 @@
|
|||||||
const tasks = Array.isArray(detail?.tasks) ? detail.tasks : [];
|
const tasks = Array.isArray(detail?.tasks) ? detail.tasks : [];
|
||||||
const milestones = Array.isArray(detail?.milestones) ? detail.milestones : [];
|
const milestones = Array.isArray(detail?.milestones) ? detail.milestones : [];
|
||||||
const blockers = Array.isArray(detail?.blockers) ? detail.blockers : [];
|
const blockers = Array.isArray(detail?.blockers) ? detail.blockers : [];
|
||||||
|
const projectOpenTodoCount = Number(detail?.project_open_todo_count || 0);
|
||||||
|
const projectOpenTodoTitles = Array.isArray(detail?.project_open_todo_titles)
|
||||||
|
? detail.project_open_todo_titles.map((item) => String(item || '').trim()).filter(Boolean)
|
||||||
|
: [];
|
||||||
|
|
||||||
const grouped = { todo: [], doing: [], done: [] };
|
const grouped = { todo: [], doing: [], done: [] };
|
||||||
tasks.forEach((task) => {
|
tasks.forEach((task) => {
|
||||||
@ -1339,6 +1343,7 @@
|
|||||||
|
|
||||||
kpis.innerHTML = [
|
kpis.innerHTML = [
|
||||||
{ label: 'Opgaver', value: tasks.length },
|
{ label: 'Opgaver', value: tasks.length },
|
||||||
|
{ label: 'Projekt todo', value: projectOpenTodoCount },
|
||||||
{ label: 'Milepæle', value: milestones.length },
|
{ label: 'Milepæle', value: milestones.length },
|
||||||
{ label: 'Blockers', value: blockers.length },
|
{ label: 'Blockers', value: blockers.length },
|
||||||
{ label: 'Deadline', value: formatShortDate(detail?.ended_at || detail?.deadline) },
|
{ label: 'Deadline', value: formatShortDate(detail?.ended_at || detail?.deadline) },
|
||||||
@ -1355,11 +1360,29 @@
|
|||||||
{ key: 'done', label: 'Lukket' },
|
{ key: 'done', label: 'Lukket' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const projectTodoPreview = projectOpenTodoTitles.slice(0, 5)
|
||||||
|
.map((title) => `<div>• ${escapeHtml(title)}</div>`)
|
||||||
|
.join('');
|
||||||
|
const projectTodoMore = Math.max(projectOpenTodoCount - Math.min(projectOpenTodoTitles.length, 5), 0);
|
||||||
|
const projectTodoCard = projectOpenTodoCount > 0
|
||||||
|
? `
|
||||||
|
<div class="mc-kanban-card" style="border-left:3px solid #0f4c75;">
|
||||||
|
<div class="mc-kanban-title">Projekt todo (${projectOpenTodoCount})</div>
|
||||||
|
<div class="mc-kanban-meta">
|
||||||
|
${projectTodoPreview || '<div>-</div>'}
|
||||||
|
${projectTodoMore > 0 ? `<div>+${projectTodoMore} flere</div>` : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: '';
|
||||||
|
|
||||||
board.innerHTML = laneMeta.map((lane) => {
|
board.innerHTML = laneMeta.map((lane) => {
|
||||||
const laneTasks = grouped[lane.key] || [];
|
const laneTasks = grouped[lane.key] || [];
|
||||||
|
const extraCard = lane.key === 'todo' ? projectTodoCard : '';
|
||||||
return `
|
return `
|
||||||
<div class="mc-kanban-col">
|
<div class="mc-kanban-col">
|
||||||
<h6>${escapeHtml(lane.label)} (${laneTasks.length})</h6>
|
<h6>${escapeHtml(lane.label)} (${laneTasks.length + (lane.key === 'todo' && projectOpenTodoCount > 0 ? 1 : 0)})</h6>
|
||||||
|
${extraCard}
|
||||||
${laneTasks.length ? laneTasks.map((task) => `
|
${laneTasks.length ? laneTasks.map((task) => `
|
||||||
<div class="mc-kanban-card">
|
<div class="mc-kanban-card">
|
||||||
<div class="mc-kanban-title">#${Number(task.id || 0)} ${escapeHtml(task.titel || task.title || 'Uden titel')}</div>
|
<div class="mc-kanban-title">#${Number(task.id || 0)} ${escapeHtml(task.titel || task.title || 'Uden titel')}</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user