Настройки хаба не загрузились: {settings.error.message}
;
if (d && d.missing.length) {
return (
-
+
В настройках хаба нет {d.missing.join(" и ")} — доска не может перевести виды работ и типы связей Jira. Задайте
словари в config.example.json и выполните uv run jira-hub init --config … или запишите их через PUT /settings.
-
- JSON доски: все задачи, связи, раскладка, дорожки и фильтры. Загружается обратно ссылкой «файл…». Если файл не скачался, скопируйте текст.
-
-
+ JSON доски: все задачи, связи, раскладка, дорожки и фильтры. Загружается обратно пунктом «Загрузить файл…» в меню «Ещё». Если файл не скачался, скопируйте текст.
+
+
+
);
}
diff --git a/web/src/board/components/FilterBar.tsx b/web/src/board/components/FilterBar.tsx
index 4594668..3015c86 100644
--- a/web/src/board/components/FilterBar.tsx
+++ b/web/src/board/components/FilterBar.tsx
@@ -1,27 +1,43 @@
+import type { ReactNode } from "react";
import { sprintBox, sprintItems, whoBox, whoItems } from "../analysis/facets.ts";
import type { BoardPrefsApi } from "../useBoardPrefs.ts";
import type { BoardModel } from "../types.ts";
+import { Button, IconCalendar, IconCritical, IconHandoff, IconLink, IconNeighbors, Panel } from "../../ui/index.ts";
import { MultiSelect } from "./MultiSelect.tsx";
-const TOGGLES = [
- { name: "neighbors", label: "+ соседи по связям", title: "Добавить к отобранным задачам их соседей по связям из любого спринта, любого исполнителя и вида работы. Соседи рисуются пунктиром и считаются отдельно" },
- { name: "onlyHand", label: "⇄ передачи", title: "Оставить только стрелки, где задачу ждёт другой человек. Карточки не убирает — для этого «скрыть без связей»" },
- { name: "onlyLinked", label: "скрыть без связей", title: "Убрать с доски карточки, у которых не осталось ни одной видимой стрелки" },
- { name: "crit", label: "⚑ критический путь", title: "Самая длинная цепочка блокировок по плановым датам окончания" },
- { name: "byDate", label: "▥ по датам", title: "Разложить карточки по колонкам плановых дат окончания. У этого режима своя расстановка: карточку можно двигать внутри её колонки; обычная расстановка не меняется и вернётся, когда режим выключен" },
-] as const;
+type Toggle = "neighbors" | "onlyHand" | "onlyLinked" | "crit" | "byDate";
+interface Item { name: Toggle; label: string; icon: ReactNode; title: string }
+
+const SHOW: Item[] = [
+ { name: "neighbors", label: "Соседи по связям", icon: , title: "Добавить к отобранным задачам их соседей по связям из любого спринта, любого исполнителя и вида работы. Соседи рисуются пунктиром и считаются отдельно" },
+ { name: "onlyHand", label: "Только передачи", icon: , title: "Оставить только стрелки, где задачу ждёт другой человек. Карточки не убирает — для этого «скрыть без связей»" },
+ { name: "onlyLinked", label: "Скрыть без связей", icon: , title: "Убрать с доски карточки, у которых не осталось ни одной видимой стрелки" },
+];
+const MODE: Item[] = [
+ { name: "crit", label: "Критический путь", icon: , title: "Самая длинная цепочка блокировок по плановым датам окончания" },
+ { name: "byDate", label: "По датам", icon: , title: "Разложить карточки по колонкам плановых дат окончания. У этого режима своя расстановка: карточку можно двигать внутри её колонки; обычная расстановка не меняется и вернётся, когда режим выключен" },
+];
export function FilterBar({ model, prefs }: { model: BoardModel; prefs: BoardPrefsApi }) {
const f = prefs.filters, sb = sprintBox(model), wb = whoBox(model, f);
- return (
-
- {KINDS.map(([k, label]) => btn("kind", k, hideKind.has(k), <>{label}>))}
-
- {EDGES.map(([k, label, ln]) => btn("edge", k, hideEdge.has(k), <>{label}>))}
- передача другому исполнителю
- критический путь
- сосед по связям, сам под фильтры не попадает
- щелчок по виду работы или типу связи скрывает их
+
+ {KINDS.map(([k, label]) => btn("kind", k, hideKind.has(k), <>{label}>))}
+
+ {EDGES.map(([k, label, ln]) => btn("edge", k, hideEdge.has(k), <>{label}>))}
+ передача другому исполнителю
+ критический путь
+ сосед по связям, сам под фильтры не попадает
+ щелчок по виду работы или типу связи скрывает их
+
+ )}
);
}
diff --git a/web/src/board/components/LinkTable.tsx b/web/src/board/components/LinkTable.tsx
index 7e53759..7f8abd2 100644
--- a/web/src/board/components/LinkTable.tsx
+++ b/web/src/board/components/LinkTable.tsx
@@ -1,13 +1,14 @@
+import { Panel } from "../../ui/index.ts";
import type { VisLink } from "../types.ts";
export function LinkTable({ links }: { links: VisLink[] }) {
return (
-
- Таблица связей
+
+
Таблица связей
Блокирует
Тип
Заблокирована
{links.map((l) =>
${l.dst}${l.type}`}>
{l.src}
{l.type}
{l.dst}
)}
-
+
);
}
diff --git a/web/src/board/components/MultiSelect.tsx b/web/src/board/components/MultiSelect.tsx
index 90e17b7..9f829d4 100644
--- a/web/src/board/components/MultiSelect.tsx
+++ b/web/src/board/components/MultiSelect.tsx
@@ -1,8 +1,8 @@
import { useState } from "react";
import type { FacetItem } from "../analysis/facets.ts";
-import { Dropdown } from "../../ui/index.ts";
+import { Button, Dropdown, IconChevronDown, SearchInput } from "../../ui/index.ts";
-// мультиселект с поиском: пустой выбор = без фильтра (строки 913–938)
+// мультиселект с поиском: пустой выбор = без фильтра (строки 913–938); кнопка — вид Select
export function MultiSelect({ items, selected, allLabel, onChange }: {
items: FacetItem[]; selected: Set; allLabel: string; onChange(next: Set): void;
}) {
@@ -13,13 +13,13 @@ export function MultiSelect({ items, selected, allLabel, onChange }: {
const flip = (v: string, on: boolean) => { const n = new Set(selected); if (on) n.add(v); else n.delete(v); onChange(n); };
return (
setQ("")} button={({ open, toggle }) => (
-