diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 7e091ce..bb918a7 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -74,6 +74,14 @@ margin: 0 auto; padding: 1.5rem 1rem 3rem; } +/* У кита .card имеет intrinsic max-width: 340px / width: max-content — в наших видах + карточки должны заполнять колонку контента целиком (иначе обрезаются действия). + overflow отключаем, иначе dropdown-меню внутри карточки обрезается. */ +.app-content .card { + max-width: 100%; + width: 100%; + overflow: visible; +} .user-chip a { margin-left: 0.5rem; color: var(--muted); diff --git a/frontend/src/components/QuickPrompt.vue b/frontend/src/components/QuickPrompt.vue new file mode 100644 index 0000000..fd8825c --- /dev/null +++ b/frontend/src/components/QuickPrompt.vue @@ -0,0 +1,60 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/TaskBadges.vue b/frontend/src/components/TaskBadges.vue new file mode 100644 index 0000000..62a2984 --- /dev/null +++ b/frontend/src/components/TaskBadges.vue @@ -0,0 +1,41 @@ + + + \ No newline at end of file diff --git a/frontend/src/locales/en.ts b/frontend/src/locales/en.ts index 362cb61..a30bbc5 100644 --- a/frontend/src/locales/en.ts +++ b/frontend/src/locales/en.ts @@ -13,6 +13,17 @@ cancel: 'Cancel', edit: 'Edit', preview: 'Preview', + confirmTitle: 'Confirmation', + delete: 'Delete', + deleteConfirmTask: 'Delete task "{title}"? This cannot be undone.', + deleteConfirmProject: 'Delete project "{name}"? Tasks will remain, but without a project.', + saved: 'Saved', + created: 'Created', + taskDeleted: 'Task deleted', + projectDeleted: 'Project deleted', + taskApproved: 'Task approved', + taskTaken: 'Task started', + taskFinished: 'Task finished', }, nav: { sections: 'Sections', @@ -31,6 +42,8 @@ count: 'Tasks in stack: {n}', quickPlaceholder: 'Throw in a task — just the text, details later…', quickAdd: 'Throw in', + added: 'Task added to the stack', + detailTitle: 'Task details', emptyTitle: 'Stack is empty', emptyText: 'Throw in your first task — detailing is picked automatically.', inStack: 'in stack', @@ -88,7 +101,6 @@ list: { title: 'Task list', count: 'Found: {n}', - apply: 'Apply', emptyTitle: 'Nothing found', emptyText: 'Try relaxing the filters or throw a new task into the stack.', filterStatus: 'Status', diff --git a/frontend/src/locales/ru.ts b/frontend/src/locales/ru.ts index 0f9b992..2ca1bca 100644 --- a/frontend/src/locales/ru.ts +++ b/frontend/src/locales/ru.ts @@ -14,6 +14,17 @@ cancel: 'Отмена', edit: 'Редактировать', preview: 'Предпросмотр', + confirmTitle: 'Подтверждение', + delete: 'Удалить', + deleteConfirmTask: 'Удалить задачу «{title}»? Действие необратимо.', + deleteConfirmProject: 'Удалить проект «{name}»? Задачи останутся, но без проекта.', + saved: 'Сохранено', + created: 'Создано', + taskDeleted: 'Задача удалена', + projectDeleted: 'Проект удалён', + taskApproved: 'Задача утверждена', + taskTaken: 'Задача взята в работу', + taskFinished: 'Задача завершена', }, nav: { sections: 'Разделы', @@ -32,6 +43,8 @@ count: 'Задач в стеке: {n}', quickPlaceholder: 'Закинуть задачу — только текст, остальное потом…', quickAdd: 'Закинуть', + added: 'Задача закинута в стек', + detailTitle: 'Детализация задачи', emptyTitle: 'Стек пуст', emptyText: 'Закиньте первую задачу — детализация подберётся автоматически.', inStack: 'в стеке', @@ -89,7 +102,6 @@ list: { title: 'Список задач', count: 'Найдено: {n}', - apply: 'Применить', emptyTitle: 'Ничего не найдено', emptyText: 'Попробуйте ослабить фильтры или закиньте новую задачу в стек.', filterStatus: 'Статус', diff --git a/frontend/src/locales/uk.ts b/frontend/src/locales/uk.ts index 3b3df9c..91c45c7 100644 --- a/frontend/src/locales/uk.ts +++ b/frontend/src/locales/uk.ts @@ -13,6 +13,17 @@ cancel: 'Скасувати', edit: 'Редагувати', preview: 'Попередній перегляд', + confirmTitle: 'Підтвердження', + delete: 'Видалити', + deleteConfirmTask: 'Видалити задачу «{title}»? Дію не можна скасувати.', + deleteConfirmProject: 'Видалити проєкт «{name}»? Задачі залишаться, але без проєкту.', + saved: 'Збережено', + created: 'Створено', + taskDeleted: 'Задачу видалено', + projectDeleted: 'Проєкт видалено', + taskApproved: 'Задачу затверджено', + taskTaken: 'Задачу взято в роботу', + taskFinished: 'Задачу завершено', }, nav: { sections: 'Розділи', @@ -31,6 +42,8 @@ count: 'Завдань у стеці: {n}', quickPlaceholder: 'Кинути задачу — лише текст, решта потім…', quickAdd: 'Кинути', + added: 'Задачу кинуто в стек', + detailTitle: 'Деталізація задачі', emptyTitle: 'Стек порожній', emptyText: 'Киньте першу задачу — деталізація підбереться автоматично.', inStack: 'у стеці', @@ -88,7 +101,6 @@ list: { title: 'Список задач', count: 'Знайдено: {n}', - apply: 'Застосувати', emptyTitle: 'Нічого не знайдено', emptyText: 'Спробуйте послабити фільтри або киньте нову задачу у стек.', filterStatus: 'Статус', diff --git a/frontend/src/views/ListView.vue b/frontend/src/views/ListView.vue index b167853..e7cb7f7 100644 --- a/frontend/src/views/ListView.vue +++ b/frontend/src/views/ListView.vue @@ -2,18 +2,11 @@ import { computed, onMounted, ref } from 'vue' import { useI18n } from 'vue-i18n' import { api, type Project, type Tag, type Task } from '../api' -import { - budgetBadge, - deadlineLabel, - deadlineOverdue, - formatMinutes, - recurrenceLabel, - statusLabel, - statusOptions, - statusVariant, -} from '../taskui' +import { statusOptions } from '../taskui' +import TaskBadges from '../components/TaskBadges.vue' // Классический вид (ТЗ 3.6): список с фильтрами — проект, тег, статус. +// Фильтры применяются сразу при смене значения. const { t } = useI18n() @@ -62,7 +55,19 @@ } } -async function remove(task: Task) { +// Удаление — через подтверждение +const deleteTarget = ref(null) +const confirmOpen = ref(false) + +function askDelete(task: Task) { + deleteTarget.value = task + confirmOpen.value = true +} + +async function remove() { + const task = deleteTarget.value + if (!task) return + confirmOpen.value = false error.value = '' try { await api.deleteTask(task.id) @@ -77,6 +82,18 @@ { value: '', label: t('common.anyStatus') }, ...statusOptions(), ]) + +// Меню «⋯» в карточке: смена статуса (отмечен текущий), удаление +function taskMenu(task: Task) { + return [ + ...statusOptions().map((o) => ({ + label: o.label, + icon: o.value === task.status ? 'ph-check' : undefined, + onSelect: () => setStatus(task, o.value), + })), + { label: t('stack.deleteTask'), icon: 'ph-trash', danger: true, onSelect: () => askDelete(task) }, + ] +} \ No newline at end of file diff --git a/frontend/src/views/OptionsView.vue b/frontend/src/views/OptionsView.vue index ad07d70..67e8d93 100644 --- a/frontend/src/views/OptionsView.vue +++ b/frontend/src/views/OptionsView.vue @@ -1,6 +1,7 @@ @@ -182,33 +212,55 @@ flex-direction: column; gap: 0.4rem; } -.node-row { - display: flex; - gap: 0.4rem; - align-items: center; +.tree-skeleton { + margin-bottom: 1rem; +} +.node { border: 1px solid var(--border, #333); border-radius: 8px; padding: 0.4rem 0.6rem; background: var(--surface, transparent); } +.node-main { + display: flex; + gap: 0.4rem; + align-items: center; +} +.node-meta { + display: flex; + flex-wrap: wrap; + gap: 0.3rem; + align-items: center; + margin-top: 0.35rem; + /* выравнивание по заголовку (после каретки) */ + padding-left: 2.4rem; +} .leaf-space { width: 2rem; flex: none; } -.node-row .title { +.node-main .title { + flex: 1; + min-width: 0; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.node-row .title.done { +.node-main .title.done { text-decoration: line-through; color: var(--text-muted, #888); } -.spacer { - flex: 1; +.node-menu { + flex: none; } -.status-select { - min-width: 9rem; +/* метаданные переносятся на свою строку, мобильной вёрстке ничего не грозит */ +@media (max-width: 480px) { + .node { + margin-left: 0 !important; + } + .node-meta { + padding-left: 0; + } } \ No newline at end of file