diff --git a/docs/TZ.md b/docs/TZ.md index b753842..a8a07d8 100644 --- a/docs/TZ.md +++ b/docs/TZ.md @@ -4,7 +4,7 @@ | | | |---|---| -| Версия ТЗ | 0.59 | +| Версия ТЗ | 0.60 | | Дата | 2026-10-09 | | Статус | На обсуждении | @@ -180,6 +180,13 @@ - Статус **актуальности** проекта (активен / приостановлен; «закрыт» выражается архивом, см. 3.8.1). - **Приоритет** проекта. - **Заметка к проекту**: Markdown-поле для ссылок на ресурсы проекта и прочего контекста; заметка — тот же «документ», что и описание задачи: в неё можно вставлять изображения из буфера (Ctrl+V). +- **Проекты без задач — отдельной секцией в конце списка** (0.60): на карточке + бейдж «Задач закрыто 0/0» ничем не отличался от заполненных, и пустой проект + терялся среди остальных. Список проектов делится на секции «С задачами» и + «Без задач» (иконка `ph-folder-dotted`), счётчики в заголовках — по всему списку; + заголовки секций показываются только когда пустые проекты есть, иначе список + выглядит как раньше. Порядок применяется **до** пагинации (3.6), чтобы секция не + разъезжалась по страницам. #### 3.8.1. Архив diff --git a/frontend/src/locales/en.ts b/frontend/src/locales/en.ts index 5dcbfe0..7b87c40 100644 --- a/frontend/src/locales/en.ts +++ b/frontend/src/locales/en.ts @@ -414,6 +414,8 @@ prompt: 'Name of the new project:', promptLabel: 'Project name', taskCounts: 'Tasks done {done}/{total}', + withTasks: 'With tasks', + withoutTasks: 'No tasks', archived: 'Project sent to archive', archiveConfirm: 'Send project "{name}" and its tasks ({n}) to the archive? Everything can be restored later.', diff --git a/frontend/src/locales/ru.ts b/frontend/src/locales/ru.ts index 2554523..cb41625 100644 --- a/frontend/src/locales/ru.ts +++ b/frontend/src/locales/ru.ts @@ -415,6 +415,8 @@ prompt: 'Название нового проекта:', promptLabel: 'Название проекта', taskCounts: 'Задач закрыто {done}/{total}', + withTasks: 'С задачами', + withoutTasks: 'Без задач', archived: 'Проект отправлен в архив', archiveConfirm: 'Отправить проект «{name}» и его задачи ({n}) в архив? Всё можно будет восстановить из архива.', diff --git a/frontend/src/locales/uk.ts b/frontend/src/locales/uk.ts index a562988..8929b85 100644 --- a/frontend/src/locales/uk.ts +++ b/frontend/src/locales/uk.ts @@ -414,6 +414,8 @@ prompt: 'Назва нового проєкту:', promptLabel: 'Назва проєкту', taskCounts: 'Задач закрито {done}/{total}', + withTasks: 'Із завданнями', + withoutTasks: 'Без завдань', archived: 'Проєкт надіслано до архіву', archiveConfirm: 'Надіслати проєкт «{name}» і його задачі ({n}) до архіву? Все можна буде відновити.', diff --git a/frontend/src/views/ProjectsView.vue b/frontend/src/views/ProjectsView.vue index 9b569e3..25ce917 100644 --- a/frontend/src/views/ProjectsView.vue +++ b/frontend/src/views/ProjectsView.vue @@ -71,12 +71,46 @@ // Реактивность (ТЗ 3.14): счётчики зависят от задач, карточки — от проектов useRealtimeRefresh(load, ['project.changed', 'project.deleted', 'task.changed', 'task.deleted']) -// Пагинация: по 30 проектов на страницу +// Пагинация: по 30 проектов на страницу. Порядок — проекты с задачами сверху, +// пустые в конце (0.60): сортируем ДО пагинации, иначе секции разъедутся +// по страницам и «Без задач» окажется в середине списка const page = ref(1) +const orderedProjects = computed(() => [ + ...projects.value.filter((p) => countsOf(p.id).total > 0), + ...projects.value.filter((p) => countsOf(p.id).total === 0), +]) const pagedProjects = computed(() => - projects.value.slice((page.value - 1) * 30, page.value * 30), + orderedProjects.value.slice((page.value - 1) * 30, page.value * 30), ) +// Проект без задач терялся среди заполненных: одинаковый бейдж «закрыто 0/0» +// ничем не отличался от соседей. Такие проекты уходят в отдельную секцию внизу +// (заголовки показываем только когда пустые есть, иначе список выглядит как раньше). +// Счётчики в заголовках — по всему списку, а не по странице (как у стека). +const projectSections = computed(() => { + const withTasks = pagedProjects.value.filter((p) => countsOf(p.id).total > 0) + const withoutTasks = pagedProjects.value.filter((p) => countsOf(p.id).total === 0) + const titled = withoutTasks.length > 0 + return [ + { + key: 'with', + titled, + icon: 'ph-folders', + label: t('projects.withTasks'), + count: projects.value.length - withoutTasks.length, + items: withTasks, + }, + { + key: 'without', + titled, + icon: 'ph-folder-dotted', + label: t('projects.withoutTasks'), + count: withoutTasks.length, + items: withoutTasks, + }, + ].filter((s) => s.items.length > 0) +}) + // Создание — модальное окно вместо window.prompt const createOpen = ref(false) @@ -141,45 +175,54 @@
- - +
@@ -202,6 +245,21 @@ flex-direction: column; gap: 0.75rem; } +/* Подпись секции списка (паттерн стека): проекты с задачами и без — 0.60 */ +.list-section-title { + display: flex; + gap: 0.6rem; + align-items: center; + margin: 0.75rem 0 0; + font-weight: 600; + font-size: 1.05rem; +} +.list-section-title:first-child { + margin-top: 0; +} +.list-section-title > i { + color: var(--accent, #7aa2f7); +} .card-link { color: inherit; text-decoration: none;