Skip to content

# 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`:

    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`:

    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`

Додати:

    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-методи:

    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`

Додати:

    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`:

    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:

    @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`

Додати:

    export async function fetchMicrodaoDashboard(slug: string): Promise<MicrodaoDashboard> {

      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<MicrodaoActivity[]> {

      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. Розбити на секції:

    <MicrodaoHeaderCard dashboard={dashboard} />        // hero

    <MicrodaoDashboardGrid>

      <MicrodaoActivitySection activity={dashboard.recent_activity} />

      <MicrodaoRoomsSection rooms={dashboard.rooms} slug={slug} />

      <MicrodaoTeamSection citizens={dashboard.citizens} />

      <MicrodaoProjectsSection microdao={dashboard.microdao} />   // placeholder

      <MicrodaoTasksSection microdao={dashboard.microdao} />      // placeholder

    </MicrodaoDashboardGrid>

Нові компоненти:

- `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. Створити компоненти:

4. `MicrodaoHeaderCard.tsx`

5. `MicrodaoActivitySection.tsx`

6. `MicrodaoTeamSection.tsx`

7. (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`:

4. лого + title;

5. метрики не ламаються (0 — ок, NaN — ні);

6. відображаються кімнати DAARION;

7. видно список 6+ громадян DAARION;

8. CTA "Поспілкуватися з DAARWIZZ" працює.

## 7. Що показувати на презентації

1. МікроDAO список — DAARION, Energy Union, GreenFood, Soul наверху (pin).

2. DAARION DAO сторінка:

3. красивий hero-блок як "панель керування DAO";

4. блок "Новини DAARION";

5. блок "Кімнати DAARION";

6. блок "Команда DAARION".

7. City Rooms — показати, що одна з кімнат — головний публічний чат (DAARION City Lobby) з DAARWIZZ.

8. Citizens / Agents — як реєстр агентів, які прив'язані до DAARION.

Це вже виглядає як живий кабінет DAO, а не просто сайт.

Якщо хочеш, наступним кроком можу згенерувати окремий `TASK_PHASE_MICRODAO_DASHBOARD_v1_CURSOR_PROMPT.md` з готовим промтом для Cursor (щоб він сам крок-за-кроком виконав усе з цього таска).