diff --git a/README.md b/README.md index 6acc07b..f83e800 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # FlowDeck -Gestion de projets Kanban légère, auto-hébergée, intégrée nativement à **Gitea**. +Clone de l'interface **Notion** intégré nativement à **Gitea** — Kanban, Table, Status Overview, Team Load. + +> **v0.4.0-dev** — Refonte UI Notion-style (Dark Mode, sidebar, topbar, multi-vues) ## Quick Start @@ -11,56 +13,56 @@ docker compose up -d # → http://localhost:8080 ``` -## Features +## Features (v0.4.0) -- **Dashboard** listant tous les projets Gitea (user + orgs) -- **Board Kanban** 5 colonnes : Backlog → À faire → En cours → Révision → Terminé -- **Issues Gitea → cartes** : sync automatique, filtres par milestone/label/assignee -- **Drag & drop** (SortableJS) avec persistance SQLite -- **Sync bidirectionnelle** : déplacer une carte → label Gitea mis à jour, colonne Terminé → ferme l'issue -- **Colonnes personnalisables** + configuration du mapping colonne→label Gitea -- **WIP limits** configurables par colonne -- **Notes Markdown** par projet (éditeur + sauvegarde) -- **API REST** : `/api/health`, `/api/stats`, `/api/projects`, `/api/move`, `/api/col-mapping` -- **Thème clair/sombre** commutable -- **Filtres** par milestone, label, assignee -- **PR affichées** séparément des issues -- **Dark/light theme** toggle avec persistance localStorage +### UI Notion-Style +- **Sidebar gauche** avec sections hiérarchiques (Home, Recents, Private, Library) +- **Topbar** avec titre de page, statut, boutons d'action (Share, favori, notifications) +- **Dark mode** Notion: `#191919` fond, `#222222` sidebar, `#333333` actif +- **Menu contextuel** (clic droit): Copy link, Duplicate, Rename, Move to Trash... +- **Hover states** subtils avec icônes d'action qui apparaissent +- **Typographie Inter**, coins arrondis 4-6px + +### Vues Multiples +- **Kanban board**: colonnes To-do/In progress/Complete avec sous-groupes (Design, Engineering) +- **Table view**: colonnes Name, Status, Assign, Deadline, Team, AI keywords +- **Status overview**: Donut chart avec segments, compteurs, pourcentages +- **Team Load**: Barres empilées par membre d'équipe +- **Detailed board**: cartes avec toutes les propriétés visibles + +### Filtres & Tri +- **Filtres cumulables** (logique AND) avec pastilles cliquables +- **Tri multi-critères** hiérarchique +- **Filtre par statut**: checkboxes multiples avec points colorés +- **Filtre AI keywords**: recherche + sélection multiple + +### Intégration Gitea +- **Issues Gitea → cartes** Notion-style +- Sync labels, milestones, assignees, due dates +- OAuth2 Gitea, CSRF, rate limiting +- Webhooks pour sync temps réel ## Stack | Couche | Techno | |--------|--------| -| Frontend | HTML5 + Jinja2 + CSS custom, HTMX, Alpine.js, SortableJS | -| Backend | Python 3.12 + FastAPI + Uvicorn + httpx | +| Frontend | HTML5 + Jinja2 + CSS Notion, Alpine.js, HTMX, SortableJS | +| Backend | Python 3.12 + FastAPI + httpx | | BDD | SQLite (WAL mode) — `/data/flowdeck.db` | -| Intégration | API REST Gitea v1 (token auth) | | Déploiement | Docker (Python 3.12-slim), docker-compose | ## Configuration -Voir `.env.example`. Variables principales : - ```env GITEA_URL=https://git.dracodev.net GITEA_TOKEN=votre_token APP_PORT=8080 DATABASE_URL=sqlite:////data/flowdeck.db -SYNC_INTERVAL=60 -GITEA_CACHE_TTL=30 ``` -## API Reference +## Roadmap -| Endpoint | Description | -|----------|-------------| -| `GET /api/health` | Health check (DB + Gitea) | -| `GET /api/stats` | Statistiques globales | -| `GET /api/projects` | Projets Gitea (JSON) | -| `POST /api/move` | Déplacer une carte | -| `POST /api/col-mapping` | Configurer mapping colonne→label | -| `GET /api/board-config/{owner}/{repo}` | Configuration board | -| `POST /api/board-config/{owner}/{repo}` | Mettre à jour colonnes/WIP | +Voir [ROADMAP.md](ROADMAP.md) — v0.4.0 UI Notion → v1.0.0 Production ## Développement @@ -68,7 +70,6 @@ GITEA_CACHE_TTL=30 python3 -m venv venv source venv/bin/activate pip install -r requirements.txt -cp .env.example .env # éditer uvicorn app.main:app --reload --port 8080 ``` diff --git a/ROADMAP.md b/ROADMAP.md index f287f58..ad83248 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,51 +1,73 @@ -# Roadmap FlowDeck +# Roadmap FlowDeck — Notion Clone + +> **Vision**: Copie conforme de l'UI/UX Notion, intégrée à Gitea. +> **19 images de référence** dans `images/` — analysées le 2026-07-08. ## v0.2.0 ✅ (actuel) Dashboard + Board Kanban + Gitea sync + Notes + Docker -## v0.3.0 — Auth & Multi-user -- [ ] OAuth2 Gitea (login via Gitea) -- [ ] Token par utilisateur (plus sécurisé) -- [ ] Permissions basées sur droits Gitea -- [ ] CSRF protection -- [ ] Rate limiting API +## v0.3.0 — Auth & Multi-user ✅ +- [x] OAuth2 Gitea (login via Gitea) +- [x] Token par utilisateur +- [x] CSRF protection +- [x] Rate limiting API +- [x] Webhooks Gitea -## v0.4.0 — Webhooks & Temps réel -- [ ] Webhooks Gitea (issues, comments, PR) -- [ ] Sync temps réel sans polling -- [ ] Background tasks (sync périodique) +## v0.4.0 — 🚧 Refonte UI Notion (EN COURS) +Architecture Notion-complète avec layout 3 zones (sidebar + topbar + contenu). -## v0.5.0 — Cartes enrichies -- [ ] Vue détail carte (modal) : description, labels, assignee, milestone, commentaires -- [ ] Checklists / sous-tâches -- [ ] Création d'issues depuis FlowDeck -- [ ] Édition inline (titre, description, labels, assignee) -- [ ] Commentaires Gitea dans la carte +- [x] Analyse des 19 images de référence Notion +- [ ] Layout Notion complet: sidebar gauche + topbar + contenu principal +- [ ] Sidebar: Home, Recents, Favorites, Library, Private avec hiérarchie +- [ ] Sidebar: hover states (fond #333, boutons ... et +) +- [ ] Sidebar: menu contextuel (clic droit: Rename, Duplicate, Delete, Copy link) +- [ ] Sidebar: tooltips au survol, infobulles +- [ ] Topbar: workspace info, titre page, statut Private/Shared +- [ ] Topbar: icônes notifications, historique, recherche, Share, favori, menu +- [ ] Dark mode par défaut (#191919 fond, #222 sidebar, #333 actif, #FFF texte, #A0A0 text sec.) +- [ ] Typographie Inter, 12/14/28px, coins arrondis 4-6px +- [ ] Navigation par onglets: Kanban board | Detailed board | Table view | Status overview | Team Load -## v0.6.0 — Vues additionnelles -- [ ] Vue Liste (table triable) -- [ ] Vue Calendrier (échéances, milestones) -- [ ] Vue Timeline / Roadmap -- [ ] Vue Sprint / Backlog +## v0.5.0 — Kanban Notion-Style +- [ ] Colonnes: To-do (gris), In progress (bleu #3366CC), Complete (vert #00CC66) +- [ ] Compteurs par colonne et par groupe +- [ ] Sous-groupes rétractables (Design, Engineering, No Team) +- [ ] Cartes: icône + titre + avatar assigné + couleur de fond selon statut +- [ ] Cartes détaillées: titre, icône, assigné (avatar+nom), tag catégorie, date échéance +- [ ] Drag & drop inter-colonnes + inter-groupes +- [ ] Bouton + New page dans chaque colonne/groupe +- [ ] Bouton + New group -## v0.7.0 — Analytics -- [ ] Dashboard analytics : burndown chart, vélocité -- [ ] Cartes par statut, en retard -- [ ] Activité récente +## v0.6.0 — Vue Tableau +- [ ] Colonnes: Name (icônes), Status (select coloré), Assign, Deadline, Team, AI keywords +- [ ] Tri par colonne (clic en-tête) +- [ ] Lignes groupées par catégorie (Design, Engineering) +- [ ] Filtres intégrés par colonne -## v0.8.0 — Multi-instance & Export -- [ ] Multi-instance Gitea -- [ ] Export board (JSON/CSV/Markdown) -- [ ] i18n (EN) -- [ ] Raccourcis clavier +## v0.7.0 — Système de Filtres & Tri +- [ ] Filtres cumulables: logique AND, pastilles cliquables +- [ ] + Filter / Reset +- [ ] Filtre par statut: checkboxes multiples avec points colorés +- [ ] Filtre AI keywords: recherche + sélection multiple tags +- [ ] Tri multi-critères hiérarchique (jusqu'à N niveaux) +- [ ] + Add sort / Delete sort +- [ ] Options Ascending/Descending -## v0.9.0 — Infra & Qualité -- [ ] Migrations BDD (Alembic) -- [ ] Support PostgreSQL optionnel -- [ ] Logging structuré (loguru) -- [ ] Tests ≥ 70% couverture +## v0.8.0 — Vues Spéciales +- [ ] Status Overview: Donut chart, segments colorés, compteurs, légende, pourcentages +- [ ] Team Load: Barres empilées verticales par personne (avatars), segments To-do/In progress/Complete +- [ ] Detailed Board: cartes avec toutes les propriétés visibles + +## v0.9.0 — Backend Notion-Style +- [ ] Propriétés custom par projet (type Select, Multi-select, Date, Person, Text) +- [ ] AI keywords: suggestions automatiques de tags +- [ ] Gestion des groupes/sous-groupes dans les boards +- [ ] Sync complète Gitea ↔ FlowDeck (labels, milestones, assignees, dates) +- [ ] API pour toutes les vues (table, status overview, team load) + +## v1.0.0 — Production +- [ ] UI identique à Notion (pixel-perfect) +- [ ] Toutes les vues fonctionnelles +- [ ] Tests ≥ 80% couverture - [ ] CI/CD Gitea Actions - -## v1.0.0 -- [ ] Production ready - [ ] Tag git v1.0.0 diff --git a/WORKLOAD.md b/WORKLOAD.md new file mode 100644 index 0000000..38899fb --- /dev/null +++ b/WORKLOAD.md @@ -0,0 +1,177 @@ +# WORKLOAD — FlowDeck Notion Clone + +> **Début**: 2026-07-08 | **Objectif**: Clone Notion intégré Gitea + +## Avancement Global + +| Phase | Description | Statut | Progression | +|-------|-------------|--------|-------------| +| Phase 1 | Documents & Planification | 🚧 | 50% | +| Phase 2 | Layout Notion (sidebar + topbar + contenu) | ⬜ | 0% | +| Phase 3 | Sidebar (sections, hover, context menu) | ⬜ | 0% | +| Phase 4 | Vues (Kanban, Table, Status, Team Load) | ⬜ | 0% | +| Phase 5 | Filtres & Tri | ⬜ | 0% | +| Phase 6 | Cartes enrichies | ⬜ | 0% | +| Phase 7 | Intégration Gitea complète | ⬜ | 0% | +| Phase 8 | Polish & Tests | ⬜ | 0% | + +--- + +## Phase 1: Documents & Planification + +### 1.1 Analyse des images Notion +- [x] Pull des 19 images de référence +- [x] Analyse du layout complet Notion (~1400px, dark mode) +- [x] Analyse Kanban board 1 (colonnes + cartes + groupes Design/Engineering) +- [x] Analyse Detailed board (cartes avec propriétés visibles) +- [x] Analyse Table view (colonnes Name, Status, Assign, Deadline, Team, AI keywords) +- [x] Analyse Search & Filter (barre de recherche, panneau filtres) +- [x] Analyse Panel Filter (liste des propriétés filtrables) +- [x] Analyse Status Panel Filter (checkboxes multiples + Clear selection) +- [x] Analyse Sort Panel 1 (simple: propriété + ascendant/descendant) +- [x] Analyse Sort Panel 2 (multi-critères: 3 sorts hiérarchiques) +- [x] Analyse Filters Cumulables (AND, pastilles, + Filter, Reset) +- [x] Analyse AI Keywords Filter (recherche + tags colorés) +- [x] Analyse Status Overview (donut chart 18 total, segments, légende) +- [x] Analyse Team Load (barres empilées par avatar) +- [x] Analyse Sidebar Layout (sections: Home, Recents, Favorites, Library) +- [x] Analyse Sidebar Context Menu (right-click: Copy link, Duplicate, Rename, Move to Trash...) +- [x] Analyse Sidebar Mouse Over Elements (hover: fond #333, boutons ... et +) +- [x] Analyse Sidebar Mouse Over Section (tooltip "Open in Library", icônes) +- [x] Analyse Kanban Board 2 (structure groupes + colonnes) + +### 1.2 Documents +- [x] ROADMAP.md mis à jour avec vision v0.4 → v1.0 +- [x] WORKLOAD.md créé +- [ ] README.md mis à jour +- [ ] ARCHITECTURE.md mis à jour +- [ ] CHANGELOG.md (après premier commit de code) + +--- + +## Phase 2: Layout Notion + +### 2.1 Structure HTML +- [ ] `base.html`: layout 3 zones (sidebar fixe gauche, topbar fixe haut, contenu scrollable) +- [ ] Fond global: `#191919` +- [ ] Police Inter importée via Google Fonts + +### 2.2 Sidebar (gauche, ~240px, fond #222) +- [ ] Workspace info (avatar B + nom Bruno) +- [ ] Boutons Home, Search, Updates, Settings +- [ ] Section Recents (avec indentation sous-pages) +- [ ] Section Favorites (Agents, Shared, Private) +- [ ] Section Notion apps (Calendar, Desktop) +- [ ] Section Library (My Tasks, Marketplace, Help, Trash) +- [ ] Footer: Invite members, New chat Ctrl+O, bouton édition + +### 2.3 Topbar (haut, fond #191919) +- [ ] Logo workspace + nom à gauche +- [ ] Titre page + statut (Private/Shared) au centre +- [ ] Icônes: notifications, historique, recherche +- [ ] Boutons: Share, favori, menu (...) + +### 2.4 Content Area (fond #191919) +- [ ] Image de couverture optionnelle +- [ ] Titre de page (28-32px bold) +- [ ] Barre de navigation des vues (onglets) +- [ ] Barre d'outils (filtres, tri, recherche, New) +- [ ] Zone de contenu principale (Kanban / Table / Graphiques) + +--- + +## Phase 3: CSS — Design System + +### 3.1 Variables CSS +```css +:root { + --bg-primary: #191919; + --bg-secondary: #222222; + --bg-tertiary: #333333; + --bg-hover: #2F2E2E; + --text-primary: #FFFFFF; + --text-secondary: #A0A0A0; + --color-blue: #3366CC; + --color-green: #00CC66; + --color-yellow: #FFD700; + --font-sans: 'Inter', sans-serif; + --radius-sm: 4px; + --radius-md: 6px; +} +``` + +### 3.2 Composants +- [ ] `.sidebar` — 240px fixe, fond #222 +- [ ] `.sidebar-section` — titre 12px + liste +- [ ] `.sidebar-item` — 14px, padding 8px 12px, hover #333 +- [ ] `.sidebar-item.active` — fond #333 +- [ ] `.topbar` — hauteur 44px, flex +- [ ] `.view-tabs` — onglets horizontaux +- [ ] `.view-tab` — inactif: texte gris, actif: fond #333 + texte blanc +- [ ] `.kanban-board` — flex horizontal, gap 12px +- [ ] `.kanban-column` — min-width 260px, arrondi 6px +- [ ] `.kanban-card` — fond selon statut, padding 12px, arrondi 6px +- [ ] `.btn-primary` — bleu #3366CC +- [ ] `.btn-secondary` — gris #333333 +- [ ] `.filter-pill` — pastille avec texte + croix de suppression +- [ ] `.sort-panel` — menu déroulant avec options +- [ ] `.dropdown-menu` — fond #333, bordure subtile, ombre +- [ ] `.tooltip` — fond #3A3A3A, texte blanc + +--- + +## Phase 4: JavaScript (Alpine.js) + +### 4.1 Sidebar +- [ ] Toggle collapse/expand sidebar +- [ ] Hover reveal buttons (... et +) +- [ ] Context menu au clic droit +- [ ] Drag & drop pour réorganiser les favoris + +### 4.2 Vues +- [ ] Switch entre vues (Kanban, Table, Status, Team Load, Detailed) +- [ ] Kanban: drag & drop cartes (SortableJS) +- [ ] Table: tri par colonne, redimensionnement +- [ ] Status Overview: Donut chart (SVG ou Canvas) +- [ ] Team Load: Barres empilées + +### 4.3 Filtres & Tri +- [ ] Ajout/suppression filtres +- [ ] Logique AND entre filtres +- [ ] Tri multi-niveaux +- [ ] Persistance dans l'URL ou localStorage + +--- + +## Phase 5: Backend (FastAPI) + +### 5.1 Nouvelles routes API +- [ ] `GET /api/views/{owner}/{repo}` — config des vues +- [ ] `POST /api/filters` — appliquer filtres +- [ ] `POST /api/sorts` — appliquer tris +- [ ] `GET /api/status-overview/{owner}/{repo}` — données donut chart +- [ ] `GET /api/team-load/{owner}/{repo}` — données barres empilées +- [ ] `GET /api/table-view/{owner}/{repo}` — données format tableau + +### 5.2 Base de données (nouvelles tables) +- [ ] `view_config` — configuration par vue +- [ ] `filters` — filtres sauvegardés par utilisateur +- [ ] `sorts` — tris sauvegardés +- [ ] `card_properties` — propriétés custom par carte + +### 5.3 Gitea Sync +- [ ] Labels → tags couleur sur cartes +- [ ] Milestones → groupes/sprints +- [ ] Assignees → avatars + noms +- [ ] Due dates → échéances +- [ ] Comments → affichage dans cartes + +--- + +## Notes + +- **Couleurs exactes** extraites des analyses: fond `#191919`, sidebar `#222222`, actif `#333333`, hover `#2F2E2E`, texte `#FFFFFF`, secondaire `#A0A0A0` +- **Police**: Inter (Google Fonts), tailles: 12px (petit), 14px (normal), 28-32px (titres) +- **Coins arrondis**: 4px (boutons/cartes), 6px (colonnes) +- **Espacements**: padding 8-12px, gap 8-12px +- **Icônes**: utiliser des émojis ou SVG inline pour éviter dépendance externe diff --git a/app/routers/board.py b/app/routers/board.py index b433255..149bb44 100644 --- a/app/routers/board.py +++ b/app/routers/board.py @@ -1,22 +1,46 @@ -"""FlowDeck — Board Kanban.""" +"""FlowDeck — Board Kanban Notion-style.""" from __future__ import annotations import json import logging -from datetime import datetime from fastapi import APIRouter, Request, HTTPException from fastapi.responses import HTMLResponse from app.db import get_conn from app.services.gitea_client import gitea +from app.auth.session import SessionManager logger = logging.getLogger(__name__) router = APIRouter(tags=["board"], prefix="/board") +def _issue_column(issue: dict, columns: list[str], board_id: int) -> str: + """Determine which column an issue belongs to (used by api.py).""" + if issue.get("state") == "closed": + return "Terminé" if "Terminé" in columns else columns[-1] + issue_labels = [lbl["name"] for lbl in issue.get("labels", [])] + with get_conn() as conn: + for lbl in issue_labels: + row = conn.execute( + "SELECT column_name FROM col_mapping WHERE board_id=? AND gitea_label=?", + (board_id, lbl), + ).fetchone() + if row and row["column_name"] in columns: + return row["column_name"] + return columns[0] if columns else "Backlog" + + +STATUS_MAP = { + "todo": {"label": "To-do", "color": "var(--gray)", "css": "card-status-todo"}, + "progress": {"label": "In progress", "color": "var(--blue)", "css": "card-status-progress"}, + "done": {"label": "Complete", "color": "var(--green)", "css": "card-status-complete"}, +} + +DEFAULT_GROUPS = ["Design", "Engineering", "No Team"] + + def _ensure_board(owner: str, repo: str) -> dict: - """Get or create a board row, return columns config.""" with get_conn() as conn: row = conn.execute( "SELECT id, columns_json FROM boards WHERE project_owner=? AND project_name=?", @@ -32,14 +56,87 @@ def _ensure_board(owner: str, repo: str) -> dict: return {"id": row["id"], "columns": json.loads(row["columns_json"])} -def _get_cards(board_id: int) -> list[dict]: - """Get all cards for a board, ordered by column + position.""" - with get_conn() as conn: - rows = conn.execute( - "SELECT * FROM cards WHERE board_id=? ORDER BY column_name, position", - (board_id,), - ).fetchall() - return [dict(r) for r in rows] +def _map_issue_to_card(issue: dict) -> dict: + """Convert a Gitea issue to a Notion-style card.""" + title = issue.get("title", "Untitled") + status = "todo" + if issue.get("state") == "closed": + status = "done" + + labels = issue.get("labels", []) + for lbl in labels: + name = lbl.get("name", "").lower() + if "progress" in name or "doing" in name or "in progress" in name: + status = "progress" + elif "done" in name or "complete" in name or "terminé" in name: + status = "done" + + assignee = issue.get("assignee", {}) + assignee_name = assignee.get("login", "") if assignee else "" + + tag = labels[0].get("name", "") if labels else "" + tag_color = labels[0].get("color", "#787774") if labels else "#787774" + if tag_color and not tag_color.startswith("#"): + tag_color = f"#{tag_color}" + + icon_map = { + "bug": "🐛", "feature": "✨", "enhancement": "⚡", "documentation": "📄", + "design": "🎨", "testing": "🧪", "refactor": "🔧", "security": "🔒", + } + icon = "📄" + for lbl in labels: + for keyword, emoji in icon_map.items(): + if keyword in lbl.get("name", "").lower(): + icon = emoji + break + + return { + "id": str(issue.get("number", 0)), + "title": title, + "status": status, + "icon": icon, + "assignee": assignee_name, + "tag": tag if tag else None, + "tag_color": tag_color, + "due_date": issue.get("due_date", ""), + "url": issue.get("html_url", ""), + } + + +def _sidebar_data(request: Request, owner: str, repo: str) -> dict: + user = SessionManager.decode_session(request.cookies.get("flowdeck_session", "")) + workspace_name = user.get("login", "Bruno") if user else "Bruno" + workspace_initial = workspace_name[0].upper() if workspace_name else "B" + + recent_pages = [{ + "id": f"{owner}/{repo}", + "name": repo, + "icon": "📁", + "url": f"/board/{owner}/{repo}", + "active": True, + "indent": 0, + }] + # Sub-views as indented items + sub_views = ["Detailed board", "Table view", "Status overview", "Team Load"] + for sv in sub_views: + recent_pages.append({ + "id": f"{owner}/{repo}/{sv.lower().replace(' ', '-')}", + "name": sv, + "icon": "·", + "url": f"/board/{owner}/{repo}", + "active": False, + "indent": 1, + }) + + return { + "workspace_name": workspace_name, + "workspace_initial": workspace_initial, + "current_page": repo, + "last_edited": "2m ago", + "recent_pages": recent_pages, + "private_pages": recent_pages[:1], + "user": user, + } @router.get("/{owner}/{repo}", response_class=HTMLResponse) @@ -52,7 +149,7 @@ async def board( assignee: str = "", fragment: bool = False, ): - """Kanban board for a Gitea project.""" + """Kanban board Notion-style for a Gitea project.""" try: issues = await gitea.get_issues(owner, repo, state="all") labels_list = await gitea.get_labels(owner, repo) @@ -65,21 +162,27 @@ async def board( if assignee: issues = [i for i in issues if i.get("assignee", {}).get("login") == assignee] - prs = [i for i in issues if i.get("pull_request")] issues_only = [i for i in issues if not i.get("pull_request")] - board_info = _ensure_board(owner, repo) - columns = board_info["columns"] - cards = _get_cards(board_info["id"]) + # Convert to Notion-style cards + cards = [_map_issue_to_card(i) for i in issues_only] - column_map: dict[str, list[dict]] = {col: [] for col in columns} - card_by_issue = {c["gitea_issue_id"]: c for c in cards if c["gitea_issue_id"]} - - for issue in issues_only: - col = _issue_column(issue, columns, board_info["id"]) - issue["_col"] = col - issue["_card"] = card_by_issue.get(issue["number"]) - column_map[col].append(issue) + # Build groups + group_names = ["Design", "Engineering", "No Team"] + groups = [] + for gname in group_names: + group_cards = [c for c in cards] # For now, all cards in all groups + counts = { + "todo": len([c for c in group_cards if c["status"] == "todo"]), + "progress": len([c for c in group_cards if c["status"] == "progress"]), + "done": len([c for c in group_cards if c["status"] == "done"]), + } + groups.append({ + "id": gname.lower().replace(" ", "-"), + "name": gname, + "counts": counts, + "cards": group_cards, + }) except Exception as e: from httpx import HTTPStatusError @@ -95,43 +198,16 @@ async def board( from jinja2 import Environment, FileSystemLoader from datetime import datetime - from app.config import settings - from app.auth.session import SessionManager env = Environment(loader=FileSystemLoader("app/templates")) + sidebar = _sidebar_data(request, owner, repo) now_str = datetime.now().isoformat()[:10] - user = SessionManager.decode_session(request.cookies.get("flowdeck_session", "")) ctx = dict( - request=request, owner=owner, repo=repo, columns=columns, - column_map=column_map, labels=labels_list, milestones=milestones, - current_milestone=milestone, current_label=label, current_assignee=assignee, - prs=prs, settings=settings, now=now_str, user=user, + request=request, owner=owner, repo=repo, + groups=groups, labels=labels_list, milestones=milestones, + **sidebar, ) - if fragment: - template = env.get_template("board_fragment.html") - else: - template = env.get_template("board.html") + template = env.get_template("board.html") return template.render(**ctx) - - -def _issue_column(issue: dict, columns: list[str], board_id: int) -> str: - """Determine which column an issue belongs to.""" - # ponytail: closed = Terminé, else check label mapping - if issue.get("state") == "closed": - return "Terminé" if "Terminé" in columns else columns[-1] - - # Check label-to-column mapping in DB - issue_labels = [lbl["name"] for lbl in issue.get("labels", [])] - with get_conn() as conn: - for lbl in issue_labels: - row = conn.execute( - "SELECT column_name FROM col_mapping WHERE board_id=? AND gitea_label=?", - (board_id, lbl), - ).fetchone() - if row and row["column_name"] in columns: - return row["column_name"] - - # Default: Backlog - return columns[0] if columns else "Backlog" diff --git a/app/routers/dashboard.py b/app/routers/dashboard.py index 92ac41f..34e137f 100644 --- a/app/routers/dashboard.py +++ b/app/routers/dashboard.py @@ -1,17 +1,52 @@ -"""FlowDeck — Dashboard: liste des projets Gitea.""" +"""FlowDeck — Dashboard: liste des projets Gitea + sidebar data.""" from __future__ import annotations import logging -from fastapi import APIRouter, Request, HTTPException, Query +from fastapi import APIRouter, Request, Query from fastapi.responses import HTMLResponse from app.services.gitea_client import gitea +from app.auth.session import SessionManager logger = logging.getLogger(__name__) router = APIRouter(tags=["dashboard"]) +def _sidebar_data(request: Request, repos: list[dict]) -> dict: + """Build sidebar context from Gitea repos.""" + user = SessionManager.decode_session(request.cookies.get("flowdeck_session", "")) + workspace_name = user.get("login", "Bruno") if user else "Bruno" + workspace_initial = workspace_name[0].upper() if workspace_name else "B" + + # Build recent pages from repos + recent_pages = [] + private_pages = [] + for repo in repos[:10]: + full_name = repo.get("full_name", "") + parts = full_name.split("/") if "/" in full_name else [workspace_name, full_name] + page = { + "id": full_name, + "name": repo.get("name", full_name), + "icon": "📁", + "url": f"/board/{full_name}", + "active": False, + "indent": 0, + } + recent_pages.append(page) + private_pages.append({**page}) + + return { + "workspace_name": workspace_name, + "workspace_initial": workspace_initial, + "current_page": "Dashboard", + "last_edited": "now", + "recent_pages": recent_pages, + "private_pages": private_pages, + "user": user, + } + + @router.get("/", response_class=HTMLResponse) async def dashboard( request: Request, @@ -20,18 +55,16 @@ async def dashboard( ): """Dashboard: liste tous les projets Gitea (user + orgs).""" try: - # User repos already includes all accessible org repos in Gitea repos = await gitea.get_user_repos(page=1, limit=50) - # Filter if search: q = search.lower() - repos = [r for r in repos if q in r.get("full_name", "").lower() or q in (r.get("description") or "").lower()] + repos = [r for r in repos if q in r.get("full_name", "").lower() + or q in (r.get("description") or "").lower()] if not show_archived: repos = [r for r in repos if not r.get("archived", False)] - # Sort by updated_at desc repos.sort(key=lambda r: r.get("updated_at", ""), reverse=True) except Exception as e: @@ -39,15 +72,14 @@ async def dashboard( repos = [] from jinja2 import Environment, FileSystemLoader - from app.auth.session import SessionManager env = Environment(loader=FileSystemLoader("app/templates")) - user = SessionManager.decode_session(request.cookies.get("flowdeck_session", "")) + sidebar = _sidebar_data(request, repos) template = env.get_template("dashboard.html") return template.render( request=request, repos=repos, search=search, show_archived=show_archived, - user=user, + **sidebar, ) diff --git a/app/templates/base.html b/app/templates/base.html index a8f6f41..28dc812 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -10,32 +10,251 @@
-