# TASK_PHASE_MICRODAO_DASHBOARD_v1 Статус: PLANNED Пріоритет: High (презентація MVP) ## 1. Ціль Зробити повноцінний **кабінет MicroDAO** (спочатку для `DAARION DAO`), який показує: - базові метрики DAO (кімнати, громадяни, агенти); - стрічку активності (новини/апдейти); - кімнати DAO + кнопки створення кімнати; - команду DAO (ключові агенти/громадяни); - підготовлені секції під проєкти/задачі/файли/інтеграції. Архітектурна вимога: **цей самий дашборд** повинен працювати для будь-якого MicroDAO (`slug`-залежний), щоб його можна було фрактально клонувати. --- ## 2. Поточний стан Є: - /microdao — список MicroDAO (з лого, тегами, pinned-сортуванням). - /microdao/[slug] — сторінка MicroDAO з: - Branding (лого, банер), - visibility (public / platform), - MicroDAO Rooms section, - базовою статистикою (rooms count). - /city — кімнати міста (мапа + список). - /citizens — публічні агенти (громадяни). - /agents — Agent Console (63 агентів, фільтри по нодах, кнопка "Новий агент"). - API для кімнат і агентів уже працює. Немає: - окремого **Dashboard API** для MicroDAO; - таблиць для **активності (новин)** и **DAO-level метрик**; - явної прив'язки громадян до конкретного MicroDAO (лише district/room mapping). --- ## 3. Архітектура рішення ### 3.1. База даних (PostgreSQL) #### 3.1.1. Таблиця активності MicroDAO Створити міграцію `migrations/041_microdao_activity.sql`: ```sql CREATE TABLE microdao_activity ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), microdao_slug TEXT NOT NULL REFERENCES city_microdao(slug) ON DELETE CASCADE, kind TEXT NOT NULL CHECK (kind IN ('post', 'event', 'update')), title TEXT, body TEXT NOT NULL, author_agent_id UUID NULL REFERENCES city_agent(id), author_name TEXT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX idx_microdao_activity_microdao_created_at ON microdao_activity (microdao_slug, created_at DESC); ``` #### 3.1.2. Розширити `city_microdao` під метрики (опціонально, але корисно) Міграція `migrations/042_microdao_stats.sql`: ```sql ALTER TABLE city_microdao ADD COLUMN IF NOT EXISTS citizens_count INTEGER NOT NULL DEFAULT 0, ADD COLUMN IF NOT EXISTS rooms_count INTEGER NOT NULL DEFAULT 0, ADD COLUMN IF NOT EXISTS agents_count INTEGER NOT NULL DEFAULT 0, ADD COLUMN IF NOT EXISTS last_update_at TIMESTAMPTZ; ``` > На етапі MVP значення можна просто перераховувати на льоту в репозиторії й не оновлювати ці поля — але стовпці потрібні для майбутнього кешування. --- ### 3.2. Backend: FastAPI (city-service) #### 3.2.1. Repo-рівень (Python) Файл: `services/city-service/repo_city.py` Додати: ```python async def get_microdao_dashboard(conn, slug: str) -> MicrodaoDashboard: """ Aggregates: - microdao summary - last 5 activity items - up to 5 rooms - up to 6 citizens (agents) linked to this microdao """ ``` Використати існуючі репо-методи: * `get_microdao_by_slug` * `get_microdao_rooms(slug)` * `get_agents_by_microdao(slug)` або аналог (якщо немає — додати простий SELECT по district/slug mapping). Нові helper-методи: ```python async def get_microdao_activity(conn, slug: str, limit: int = 10) -> list[MicrodaoActivity]: ... async def insert_microdao_activity(conn, activity: CreateMicrodaoActivity) -> MicrodaoActivity: ... ``` #### 3.2.2. Pydantic-моделі Файл: `services/city-service/models_city.py` Додати: ```python class MicrodaoActivity(BaseModel): id: UUID microdao_slug: str kind: str title: str | None body: str author_agent_id: UUID | None author_name: str | None created_at: datetime class CreateMicrodaoActivity(BaseModel): kind: str = Field(pattern="^(post|event|update)$") title: str | None = None body: str author_agent_id: UUID | None = None author_name: str | None = None class MicrodaoDashboard(BaseModel): microdao: MicrodaoSummary stats: MicrodaoStats recent_activity: list[MicrodaoActivity] rooms: list[RoomSummary] citizens: list[PublicCitizenSummary] ``` `MicrodaoStats`: ```python class MicrodaoStats(BaseModel): rooms_count: int citizens_count: int agents_count: int last_update_at: datetime | None ``` #### 3.2.3. Routes (FastAPI) Файл: `services/city-service/routes_city.py` Додати endpoints: ```python @router.get("/microdao/{slug}/dashboard", response_model=MicrodaoDashboard) async def get_microdao_dashboard(slug: str, conn=Depends(get_conn)): return await repo_city.get_microdao_dashboard(conn, slug) @router.get("/microdao/{slug}/activity", response_model=list[MicrodaoActivity]) async def list_microdao_activity(slug: str, limit: int = 20, conn=Depends(get_conn)): return await repo_city.get_microdao_activity(conn, slug, limit) @router.post("/microdao/{slug}/activity", response_model=MicrodaoActivity, status_code=201) async def create_microdao_activity( slug: str, payload: CreateMicrodaoActivity, conn=Depends(get_conn), # TODO: optional auth / orchestrator check ): return await repo_city.create_microdao_activity(conn, slug, payload) ``` Для MVP можна не чіпати авторизацію — припустити, що виклик робить orchestrator через адмін-UI. --- ### 3.3. Frontend: Next.js (apps/web) #### 3.3.1. API-клієнт Файл: `apps/web/src/lib/api.ts` Додати: ```ts export async function fetchMicrodaoDashboard(slug: string): Promise { const res = await fetch(`${CITY_API_BASE_URL}/microdao/${slug}/dashboard`, { cache: "no-store" }); if (!res.ok) throw new Error("Failed to load microdao dashboard"); return res.json(); } export async function fetchMicrodaoActivity(slug: string): Promise { const res = await fetch(`${CITY_API_BASE_URL}/microdao/${slug}/activity?limit=20`, { cache: "no-store" }); if (!res.ok) throw new Error("Failed to load microdao activity"); return res.json(); } ``` Типи `MicrodaoDashboard` / `MicrodaoActivity` додати до `apps/web/src/lib/types.ts`. --- #### 3.3.2. Структура сторінки `/microdao/[slug]` Файл (вже існує): `apps/web/src/app/microdao/[slug]/page.tsx` Перетворити на **Dashboard Layout**: 1. На сервері викликати `fetchMicrodaoDashboard(slug)`. 2. Розбити на секції: ```tsx // hero // placeholder // placeholder ``` Нові компоненти: * `apps/web/src/components/microdao/MicrodaoHeaderCard.tsx` * `apps/web/src/components/microdao/MicrodaoActivitySection.tsx` * `apps/web/src/components/microdao/MicrodaoTeamSection.tsx` * `apps/web/src/components/microdao/MicrodaoProjectsSection.tsx` (stub) * `apps/web/src/components/microdao/MicrodaoTasksSection.tsx` (stub) ##### MicrodaoHeaderCard Показати: * Лого (через `normalizeAssetUrl`) * Назву, теги (Platform / Core / Active) * Метрики: * `{stats.rooms_count} Кімнат` * `{stats.citizens_count} Громадян` * `{stats.agents_count} Агентів` * кнопки: * "Поспілкуватися з DAARWIZZ" → `href="/city?room=city-lobby"` * "Створити кімнату" → прокрутка до секції кімнат або модалка. ##### MicrodaoActivitySection * Заголовок "Новини DAARION" (або назва DAO). * Список останніх 5–10 записів: * заголовок / перші рядки body, * дата, * автор (агент або author_name). * Якщо немає записів — показати `Empty state: "Поки що немає новин"`. --- ### 3.4. Зв'язок з існуючими розділами * MicroDAO Rooms: у компоненті `MicrodaoRoomsSection` використати вже існуючу логіку створення/видалення кімнат. * Citizens: `MicrodaoTeamSection` повинна показувати тільки тих громадян, у яких `home_microdao_slug` або district/room mapping вказує на цей MicroDAO. На етапі MVP можна вибрати фіксований набір (наприклад, 6 ключових агентів DAARION) через простий SQL. --- ## 4. План виконання (для Cursor) ### Крок 1. DB міграції 1. Створити `migrations/041_microdao_activity.sql` і `042_microdao_stats.sql`. 2. Запустити міграції локально й на NODE1 (MVP DB). ### Крок 2. Backend 1. Оновити `models_city.py` — додати моделі `MicrodaoActivity`, `CreateMicrodaoActivity`, `MicrodaoStats`, `MicrodaoDashboard`. 2. Оновити `repo_city.py` — реалізувати aggregation для `get_microdao_dashboard` та CRUD для activity. 3. Оновити `routes_city.py` — додати `/microdao/{slug}/dashboard` та `/microdao/{slug}/activity`. ### Крок 3. Frontend 1. Оновити `apps/web/src/lib/types.ts` та `api.ts` (типи й API-клієнт). 2. Переписати `apps/web/src/app/microdao/[slug]/page.tsx`, щоб вона використовувала `fetchMicrodaoDashboard`. 3. Створити компоненти: * `MicrodaoHeaderCard.tsx` * `MicrodaoActivitySection.tsx` * `MicrodaoTeamSection.tsx` * (stubs) `MicrodaoProjectsSection.tsx`, `MicrodaoTasksSection.tsx` ### Крок 4. DAARION DAO як демо 1. Заповнити хоча б **5–10 записів `microdao_activity`** для `slug='daarion'` (через SQL або простий admin-endpoint /seed). 2. Прив'язати ключових агентів DAARION до цього MicroDAO (update в таблиці citizens/agents). 3. Перевірити сторінку `/microdao/daarion`: * лого + title; * метрики не ламаються (0 — ок, NaN — ні); * відображаються кімнати DAARION; * видно список 6+ громадян DAARION; * CTA "Поспілкуватися з DAARWIZZ" працює. --- ## 7. Що показувати на презентації 1. **МікроDAO список** — DAARION, Energy Union, GreenFood, Soul наверху (pin). 2. **DAARION DAO сторінка**: * красивий hero-блок як "панель керування DAO"; * блок "Новини DAARION"; * блок "Кімнати DAARION"; * блок "Команда DAARION". 3. **City Rooms** — показати, що одна з кімнат — головний публічний чат (DAARION City Lobby) з DAARWIZZ. 4. **Citizens / Agents** — як реєстр агентів, які прив'язані до DAARION. Це вже виглядає як **живий кабінет DAO**, а не просто сайт. --- Якщо хочеш, наступним кроком можу згенерувати окремий `TASK_PHASE_MICRODAO_DASHBOARD_v1_CURSOR_PROMPT.md` з готовим промтом для Cursor (щоб він сам крок-за-кроком виконав усе з цього таска).