diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 3efa336..c3c684d 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -23,7 +23,11 @@ const currentLabel = computed(() => { const item = navItems.value.find((i) => i.to === route.path) - return item?.label ?? 'gntodo' + if (item) return item.label + // глубокие страницы: задача → страница задачи, проект → раздел проектов + if (route.path.startsWith('/tasks/')) return t('nav.task') + if (route.path.startsWith('/projects/')) return t('nav.projects') + return 'gntodo' }) onMounted(async () => { @@ -88,7 +92,7 @@ \ No newline at end of file diff --git a/frontend/src/components/TaskForm.vue b/frontend/src/components/TaskForm.vue new file mode 100644 index 0000000..ef04a1f --- /dev/null +++ b/frontend/src/components/TaskForm.vue @@ -0,0 +1,395 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/TaskTree.vue b/frontend/src/components/TaskTree.vue new file mode 100644 index 0000000..f4bad49 --- /dev/null +++ b/frontend/src/components/TaskTree.vue @@ -0,0 +1,211 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/locales/en.ts b/frontend/src/locales/en.ts index 672f06d..8d49529 100644 --- a/frontend/src/locales/en.ts +++ b/frontend/src/locales/en.ts @@ -15,6 +15,7 @@ preview: 'Preview', confirmTitle: 'Confirmation', delete: 'Delete', + open: 'Open', deleteConfirmTask: 'Delete task "{title}"? This cannot be undone.', deleteConfirmProject: 'Delete project "{name}"? Tasks will remain, but without a project.', saved: 'Saved', @@ -31,6 +32,7 @@ signIn: 'Sign in', account: 'Account', signOut: 'Sign out', + task: 'Task', stack: 'Inbox stack', tree: 'Task tree', list: 'Task list', @@ -99,6 +101,36 @@ collapse: 'Collapse', expand: 'Expand', }, + task: { + notFoundTitle: 'Task not found', + notFoundText: 'The task may have been deleted or the link is stale.', + proposalTitle: 'AI proposal', + description: 'Description', + descriptionEmpty: 'No description yet — detail the task.', + fields: 'Details', + field: { + project: 'Project', + priority: 'Priority', + estimate: 'Time estimate', + money: 'Money', + deadline: 'Deadline', + recurrence: 'Recurrence', + }, + subtasks: 'Subtasks', + noSubtasks: 'No subtasks', + }, + projectPage: { + notFoundTitle: 'Project not found', + notFoundText: 'The project may have been deleted or the link is stale.', + note: 'Note', + progress: 'Project tasks: {done}/{total}', + newTask: 'New task', + taskCreated: 'Task created — it went to the stack for detailing', + promptTask: 'New project task:', + emptyTitle: 'No tasks', + emptyText: 'Create the first project task or relax the filters.', + tree: 'Project tree', + }, list: { title: 'Task list', count: 'Found: {n}', diff --git a/frontend/src/locales/ru.ts b/frontend/src/locales/ru.ts index 466dbc2..3bdd8db 100644 --- a/frontend/src/locales/ru.ts +++ b/frontend/src/locales/ru.ts @@ -16,6 +16,7 @@ preview: 'Предпросмотр', confirmTitle: 'Подтверждение', delete: 'Удалить', + open: 'Открыть', deleteConfirmTask: 'Удалить задачу «{title}»? Действие необратимо.', deleteConfirmProject: 'Удалить проект «{name}»? Задачи останутся, но без проекта.', saved: 'Сохранено', @@ -32,6 +33,7 @@ signIn: 'Войти', account: 'Аккаунт', signOut: 'Выйти', + task: 'Задача', stack: 'Стек входящих', tree: 'Дерево задач', list: 'Список задач', @@ -100,6 +102,36 @@ collapse: 'Свернуть', expand: 'Развернуть', }, + task: { + notFoundTitle: 'Задача не найдена', + notFoundText: 'Возможно, задача удалена или ссылка устарела.', + proposalTitle: 'Предложение ИИ', + description: 'Описание', + descriptionEmpty: 'Описание пусто — детализируйте задачу.', + fields: 'Параметры', + field: { + project: 'Проект', + priority: 'Приоритет', + estimate: 'Оценка времени', + money: 'Деньги', + deadline: 'Дедлайн', + recurrence: 'Повторение', + }, + subtasks: 'Подзадачи', + noSubtasks: 'Подзадач нет', + }, + projectPage: { + notFoundTitle: 'Проект не найден', + notFoundText: 'Возможно, проект удалён или ссылка устарела.', + note: 'Заметка', + progress: 'Задачи проекта: {done}/{total}', + newTask: 'Новая задача', + taskCreated: 'Задача создана — попала в стек на детализацию', + promptTask: 'Новая задача проекта:', + emptyTitle: 'Задач нет', + emptyText: 'Создайте первую задачу проекта или ослабьте фильтры.', + tree: 'Дерево проекта', + }, list: { title: 'Список задач', count: 'Найдено: {n}', diff --git a/frontend/src/locales/uk.ts b/frontend/src/locales/uk.ts index 5d78083..0ce127a 100644 --- a/frontend/src/locales/uk.ts +++ b/frontend/src/locales/uk.ts @@ -15,6 +15,7 @@ preview: 'Попередній перегляд', confirmTitle: 'Підтвердження', delete: 'Видалити', + open: 'Відкрити', deleteConfirmTask: 'Видалити задачу «{title}»? Дію не можна скасувати.', deleteConfirmProject: 'Видалити проєкт «{name}»? Задачі залишаться, але без проєкту.', saved: 'Збережено', @@ -31,6 +32,7 @@ signIn: 'Увійти', account: 'Акаунт', signOut: 'Вийти', + task: 'Завдання', stack: 'Вхідний стек', tree: 'Дерево задач', list: 'Список задач', @@ -99,6 +101,36 @@ collapse: 'Згорнути', expand: 'Розгорнути', }, + task: { + notFoundTitle: 'Задачу не знайдено', + notFoundText: 'Можливо, задачу видалено або посилання застаріло.', + proposalTitle: 'Пропозиція ШІ', + description: 'Опис', + descriptionEmpty: 'Опис порожній — деталізуйте задачу.', + fields: 'Параметри', + field: { + project: 'Проєкт', + priority: 'Пріоритет', + estimate: 'Оцінка часу', + money: 'Гроші', + deadline: 'Дедлайн', + recurrence: 'Повторення', + }, + subtasks: 'Підзадачі', + noSubtasks: 'Підзадач немає', + }, + projectPage: { + notFoundTitle: 'Проєкт не знайдено', + notFoundText: 'Можливо, проєкт видалено або посилання застаріло.', + note: 'Нотатка', + progress: 'Задачі проєкту: {done}/{total}', + newTask: 'Нова задача', + taskCreated: 'Задачу створено — потрапила у стек на деталізацію', + promptTask: 'Нова задача проєкту:', + emptyTitle: 'Задач немає', + emptyText: 'Створіть першу задачу проєкту або послабте фільтри.', + tree: 'Дерево проєкту', + }, list: { title: 'Список задач', count: 'Знайдено: {n}', diff --git a/frontend/src/main.ts b/frontend/src/main.ts index e2990b6..cd4f0a4 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -9,9 +9,11 @@ import i18n from './i18n' import ListView from './views/ListView.vue' import OptionsView from './views/OptionsView.vue' +import ProjectView from './views/ProjectView.vue' import ProjectsView from './views/ProjectsView.vue' import SettingsView from './views/SettingsView.vue' import StackView from './views/StackView.vue' +import TaskView from './views/TaskView.vue' import TreeView from './views/TreeView.vue' const router = createRouter({ @@ -22,6 +24,8 @@ { path: '/tree', component: TreeView }, { path: '/list', component: ListView }, { path: '/projects', component: ProjectsView }, + { path: '/projects/:id', component: ProjectView }, + { path: '/tasks/:id', component: TaskView }, { path: '/options', component: OptionsView }, { path: '/settings', component: SettingsView }, ], diff --git a/frontend/src/taskui.ts b/frontend/src/taskui.ts index 83fe028..752f3f9 100644 --- a/frontend/src/taskui.ts +++ b/frontend/src/taskui.ts @@ -168,4 +168,20 @@ parts.push(t('taskui.money.estimate', { amount: formatMoney(task.cost_estimate_money) })) } return parts.join(' / ') +} + +// Подписи чернового предложения LLM (стек, страница задачи) +export function proposalItems(task: { + ai_proposal: import('./api').AiProposal | null +}): string[] { + const p = task.ai_proposal + if (!p) return [] + const items: string[] = [] + p.tags.forEach((tag) => items.push(tag)) + if (p.project) + items.push(p.new_project ? t('stack.proposalNewProject', { name: p.project }) : p.project) + if (p.priority !== null) items.push(t('stack.proposalPriority', { n: p.priority })) + if (p.estimated_minutes !== null && p.estimated_minutes !== undefined) + items.push(`≈ ${formatMinutes(p.estimated_minutes)}`) + return items } \ No newline at end of file diff --git a/frontend/src/views/ListView.vue b/frontend/src/views/ListView.vue index e7cb7f7..1eeb8ca 100644 --- a/frontend/src/views/ListView.vue +++ b/frontend/src/views/ListView.vue @@ -3,7 +3,7 @@ import { useI18n } from 'vue-i18n' import { api, type Project, type Tag, type Task } from '../api' import { statusOptions } from '../taskui' -import TaskBadges from '../components/TaskBadges.vue' +import TaskCard from '../components/TaskCard.vue' // Классический вид (ТЗ 3.6): список с фильтрами — проект, тег, статус. // Фильтры применяются сразу при смене значения. @@ -77,23 +77,11 @@ } } -// Опции фильтра/смены статуса: «любой» + все статусы +// Опции фильтра: «любой» + все статусы const statusOptionsWithAny = computed(() => [ { 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) }, - ] -}