diff --git a/src/views/taskDashboardHtml.ts b/src/views/taskDashboardHtml.ts index f0fe3b2..121d324 100644 --- a/src/views/taskDashboardHtml.ts +++ b/src/views/taskDashboardHtml.ts @@ -199,9 +199,11 @@ export function getTaskDashboardHtml( +
+
@@ -228,8 +230,15 @@ export function getTaskDashboardHtml( { value: "cancelled", label: "Cancelled" }, ]; + const PRIORITIES = [ + { value: "critical", label: "Critical" }, + { value: "high", label: "High" }, + { value: "medium", label: "Medium" }, + { value: "low", label: "Low" }, + ]; + const state = { - filter: { statuses: [], query: "" }, + filter: {}, groupBy: "none", selectedId: undefined, groups: [], @@ -242,7 +251,9 @@ export function getTaskDashboardHtml( query: document.getElementById("query"), groupBy: document.getElementById("groupBy"), refresh: document.getElementById("refresh"), + resetFilters: document.getElementById("resetFilters"), statusFilters: document.getElementById("status-filters"), + priorityFilters: document.getElementById("priority-filters"), empty: document.getElementById("empty"), detail: document.getElementById("detail"), title: document.getElementById("detail-title"), @@ -261,31 +272,6 @@ export function getTaskDashboardHtml( el.statusLine.classList.toggle("error", Boolean(isError)); } - function selectedStatuses() { - return state.filter.statuses || []; - } - - function renderStatusChips() { - el.statusFilters.innerHTML = ""; - for (const s of STATUSES) { - const btn = document.createElement("button"); - btn.type = "button"; - btn.className = "chip" + (selectedStatuses().includes(s.value) ? " active" : ""); - btn.textContent = s.label; - btn.addEventListener("click", () => { - const current = new Set(selectedStatuses()); - if (current.has(s.value)) current.delete(s.value); - else current.add(s.value); - state.filter = { - ...state.filter, - statuses: Array.from(current), - }; - post({ type: "setFilter", filter: normalizeFilter(state.filter) }); - }); - el.statusFilters.appendChild(btn); - } - } - function normalizeFilter(filter) { const next = {}; if (filter.statuses && filter.statuses.length) next.statuses = filter.statuses; @@ -295,6 +281,65 @@ export function getTaskDashboardHtml( return next; } + function postFilter() { + post({ type: "setFilter", filter: normalizeFilter(state.filter) }); + } + + function renderChipRow(container, options, selectedValues, key) { + container.innerHTML = ""; + for (const option of options) { + const btn = document.createElement("button"); + btn.type = "button"; + btn.className = "chip" + (selectedValues.includes(option.value) ? " active" : ""); + btn.textContent = option.label; + btn.addEventListener("click", () => { + const current = new Set(selectedValues); + if (current.has(option.value)) current.delete(option.value); + else current.add(option.value); + state.filter = { + ...state.filter, + [key]: Array.from(current), + }; + postFilter(); + }); + container.appendChild(btn); + } + } + + function renderFilterChips() { + renderChipRow( + el.statusFilters, + STATUSES, + state.filter.statuses || [], + "statuses", + ); + renderChipRow( + el.priorityFilters, + PRIORITIES, + state.filter.priorities || [], + "priorities", + ); + } + + function saveDescription() { + if (!state.selected) return; + post({ + type: "saveDescription", + id: state.selected.id, + description: el.description.value, + }); + } + + function resetFilters() { + state.filter = {}; + state.groupBy = "none"; + el.query.value = ""; + el.groupBy.value = "none"; + renderFilterChips(); + post({ type: "setFilter", filter: {} }); + post({ type: "setGroupBy", groupBy: "none" }); + } + function renderList() { el.list.innerHTML = ""; if (!state.groups.length) { @@ -367,7 +412,7 @@ export function getTaskDashboardHtml( state.dirty = false; el.query.value = state.filter.query || ""; el.groupBy.value = state.groupBy; - renderStatusChips(); + renderFilterChips(); renderList(); renderDetail(); setStatus(""); @@ -379,7 +424,7 @@ export function getTaskDashboardHtml( window.clearTimeout(queryTimer); queryTimer = window.setTimeout(() => { state.filter = { ...state.filter, query }; - post({ type: "setFilter", filter: normalizeFilter(state.filter) }); + postFilter(); }, ${QUERY_DEBOUNCE_MS}); }); @@ -392,18 +437,24 @@ export function getTaskDashboardHtml( post({ type: "refresh" }); }); + el.resetFilters.addEventListener("click", () => { + resetFilters(); + }); + el.description.addEventListener("input", () => { state.dirty = true; setStatus("Unsaved changes"); }); el.save.addEventListener("click", () => { - if (!state.selected) return; - post({ - type: "saveDescription", - id: state.selected.id, - description: el.description.value, - }); + saveDescription(); + }); + + window.addEventListener("keydown", (event) => { + if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "s") { + event.preventDefault(); + saveDescription(); + } }); window.addEventListener("message", (event) => { @@ -421,7 +472,7 @@ export function getTaskDashboardHtml( } }); - renderStatusChips(); + renderFilterChips(); post({ type: "ready" }); diff --git a/todo.md b/todo.md index 4e2d649..bbb480e 100644 --- a/todo.md +++ b/todo.md @@ -2,7 +2,7 @@ ## Dashboard -[ ] **Сохранение по Ctrl+S** +[x] **Сохранение по Ctrl+S** - В detail (textarea description) — hotkey `Ctrl+S` / `Cmd+S` → тот же flow, что кнопка Save (`saveDescription`). @@ -29,15 +29,14 @@ Use-case/repo для headless/тестов можно оставить; editor-s **Пока:** не блокер; format при правке файла в editor уже ок. Ctrl+S в Dashboard без editor-path format не даст. -[ ] **Кнопка сброса фильтров** +[x] **Кнопка сброса фильтров** - В toolbar Dashboard: Reset → `filter = {}`, query/chips/groupBy defaults, - `setFilter` + при необходимости сброс groupBy. + `setFilter` + сброс groupBy. -[ ] **Фильтр по приоритету** +[x] **Фильтр по приоритету** -- Chips как у status: low / medium / high / critical → `filter.priorities` (уже - есть в `TaskFilter` / `filterTasks`). +- Chips: critical / high / medium / low → `filter.priorities`. [ ] **Dashboard: raw MD ↔ view**