diff --git a/2026-05-25.canvas b/2026-05-25.canvas new file mode 100644 index 0000000..2291c58 --- /dev/null +++ b/2026-05-25.canvas @@ -0,0 +1,156 @@ +{ + "nodes": [ + { + "id": "center", + "type": "text", + "x": 100, + "y": 300, + "width": 350, + "height": 220, + "label": "📅 Отчет за сессию 25 мая 2026", + "text": "# 📅 Сессия: 25 мая 2026 г.\n\nСегодня мы провели масштабное обновление XFlow, интегрировав новые инструменты планирования, приоритезации, а также готовую интерактивную среду для вашего практического задания!\n\n**Ключевые блоки изменений:**\n1. 📋 Шаблон «Мои цели на год»\n2. ⚖️ Тест на истинность цели\n3. 📅 Диаграмма Ганта\n4. 📊 Матрица Эйзенхауэра\n5. 🧪 Сборка проекта Nuxt\n\n*Изучите подключенные узлы для деталей.*", + "style": { + "background": "#1e1b4b", + "border": "#fbbf24" + } + }, + { + "id": "block-goals", + "type": "task", + "x": 550, + "y": 50, + "width": 320, + "height": 180, + "label": "📋 Шаблон целей на год", + "state": "completed", + "priority": "high", + "text": "## 📋 Шаблон «Мои цели на год»\n\n* **Тулбар:** Добавлена кнопка `📋 +Цели на Год` для автоматического развертывания карты из 5 целей.\n* **Структура:** Каждая цель пре-генерируется с шаблонами для ввода по методикам SMART и GROW (с полем для шагов и ресурсов).\n* **Связь с Гантом:** Все цели являются задачами и отображаются в таймлайне при заполнении дат.", + "style": { + "background": "#064e3b", + "border": "#10b981" + } + }, + { + "id": "block-test", + "type": "task", + "x": 550, + "y": 250, + "width": 320, + "height": 180, + "label": "⚖️ Тест на истинность цели", + "state": "completed", + "priority": "high", + "text": "## ⚖️ Тест на истинность цели\n\n* **Сайдбар:** В свойства узла добавлен интерактивный опросник с 8 вопросами (шкала 1-10) для проверки мотивации по Заданию №2.\n* **Авторасчет:** Автоматически вычисляется сумма.\n* **Запись в узел:** Результат с подробными баллами записывается прямо в Markdown-текст узла по нажатию одной кнопки.", + "style": { + "background": "#0f172a", + "border": "#10b981" + } + }, + { + "id": "block-gantt", + "type": "task", + "x": 550, + "y": 450, + "width": 320, + "height": 180, + "label": "📅 Диаграмма Ганта", + "state": "completed", + "priority": "high", + "text": "## 📅 Диаграмма Ганта\n\n* **Компонент:** Создан `GanttChartDrawer.vue` — выдвижной таймлайн снизу.\n* **Узлы задач:** Отслеживает все узлы типа `task` с заполненными датами.\n* **Фокус камеры:** Клик по задаче в списке Ганта плавно центрирует и приближает камеру Vue Flow на этот узел.", + "style": { + "background": "#1e293b", + "border": "#3b82f6" + } + }, + { + "id": "block-eisen", + "type": "task", + "x": 550, + "y": 650, + "width": 320, + "height": 180, + "label": "📊 Матрица Эйзенхауэра", + "state": "completed", + "priority": "high", + "text": "## 📊 Матрица Эйзенхауэра\n\n* **2x2 Сетка:** Размещает четыре квадранта (Важно/Срочно, Важно/Несрочно, Неважно/Срочно, Неважно/Несрочно).\n* **ИИ коучинг:** Подключен бэкенд OpenRouter AI для автоматической сортировки списка задач пользователя по 4 квадрантам.", + "style": { + "background": "#451a03", + "border": "#f59e0b" + } + }, + { + "id": "block-build", + "type": "task", + "x": 950, + "y": 350, + "width": 320, + "height": 180, + "label": "🧪 Сборка проекта", + "state": "completed", + "priority": "high", + "text": "## 🧪 Валидация сборки\n\n* **Команда:** `npm run build`\n* **Результат:** Успешно пройдено! Ошибок нет.\n* **Готовность:** 100% готовность к завтрашнему использованию на занятии.", + "style": { + "background": "#312e81", + "border": "#6366f1" + } + } + ], + "edges": [ + { + "id": "edge-goals", + "fromNode": "center", + "fromSide": "right", + "toNode": "block-goals", + "toSide": "left" + }, + { + "id": "edge-test", + "fromNode": "center", + "fromSide": "right", + "toNode": "block-test", + "toSide": "left" + }, + { + "id": "edge-gantt", + "fromNode": "center", + "fromSide": "right", + "toNode": "block-gantt", + "toSide": "left" + }, + { + "id": "edge-eisen", + "fromNode": "center", + "fromSide": "right", + "toNode": "block-eisen", + "toSide": "left" + }, + { + "id": "edge-goals-build", + "fromNode": "block-goals", + "fromSide": "right", + "toNode": "block-build", + "toSide": "left" + }, + { + "id": "edge-test-build", + "fromNode": "block-test", + "fromSide": "right", + "toNode": "block-build", + "toSide": "left" + }, + { + "id": "edge-gantt-build", + "fromNode": "block-gantt", + "fromSide": "right", + "toNode": "block-build", + "toSide": "left" + }, + { + "id": "edge-eisen-build", + "fromNode": "block-eisen", + "fromSide": "right", + "toNode": "block-build", + "toSide": "left" + } + ] +} diff --git a/app/app.vue b/app/app.vue index 748aed6..fa4c246 100644 --- a/app/app.vue +++ b/app/app.vue @@ -5,6 +5,9 @@ diff --git a/app/components/editor/GanttChartDrawer.vue b/app/components/editor/GanttChartDrawer.vue new file mode 100644 index 0000000..a84b7b5 --- /dev/null +++ b/app/components/editor/GanttChartDrawer.vue @@ -0,0 +1,318 @@ + + + + + diff --git a/app/components/editor/NodePalette.vue b/app/components/editor/NodePalette.vue new file mode 100644 index 0000000..8bb1423 --- /dev/null +++ b/app/components/editor/NodePalette.vue @@ -0,0 +1,38 @@ + + + diff --git a/app/components/editor/NodePropertiesPanel.vue b/app/components/editor/NodePropertiesPanel.vue new file mode 100644 index 0000000..355cc50 --- /dev/null +++ b/app/components/editor/NodePropertiesPanel.vue @@ -0,0 +1,291 @@ + + + diff --git a/app/composables/useAuth.ts b/app/composables/useAuth.ts index f429c6a..7e9879b 100644 --- a/app/composables/useAuth.ts +++ b/app/composables/useAuth.ts @@ -12,9 +12,11 @@ interface User { export const useAuth = () => { const token = useCookie('xflow_token', { maxAge: 60 * 60 * 24 * 7, - sameSite: 'strict' + sameSite: 'lax' }) + const isInitializing = useState('auth_initializing', () => true) + const user = computed(() => { if (!token.value) return null try { @@ -55,8 +57,10 @@ export const useAuth = () => { } const initAuth = () => { + if (process.server) return + const auth = getAuth() - onAuthStateChanged(auth, async (firebaseUser) => { + const unsubscribe = onAuthStateChanged(auth, async (firebaseUser) => { // If user is authenticated in Firebase but we lost our backend token, try to restore if (firebaseUser && !token.value) { try { @@ -68,13 +72,15 @@ export const useAuth = () => { token.value = res.token if (useRoute().path === '/login') { - navigateTo('/') + await navigateTo('/') } } catch (e) { console.error('Failed to restore backend auth from Firebase', e) } } + isInitializing.value = false }) + return unsubscribe } return { @@ -82,6 +88,7 @@ export const useAuth = () => { user, isAuthenticated, isAdmin, + isInitializing, googleSignIn, logout, initAuth diff --git a/app/pages/login.vue b/app/pages/login.vue index a398698..c513850 100644 --- a/app/pages/login.vue +++ b/app/pages/login.vue @@ -1,6 +1,13 @@