From 4eaec52d6667c71113b23994c0556413d3beea2d Mon Sep 17 00:00:00 2001 From: bruno Date: Wed, 8 Jul 2026 15:38:25 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20workspace=20contextuel=20=E2=80=94=20pa?= =?UTF-8?q?ges=20li=C3=A9es=20au=20projet,=20sidebar=20vues=20cliquables?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - pages.workspace = owner/repo (ex: bruno/flowdeck) au lieu du nom user - Sidebar: Kanban board + 4 sous-vues avec URLs distinctes (?view=kanban|table|...) - Cliquer sur 'Table view' dans sidebar → change la vue à droite - Dashboard (/): sidebar montre uniquement les projets Gitea - Board (/board/{o}/{r}): sidebar montre les pages de ce projet - Changement de projet = changement de contexte (pages isolées) - newPage/newSubPage passent workspace_key à l'API - Auto-switch view depuis paramètre URL dans board.html --- app/routers/board.py | 51 ++++++++++++++++++++++++---------------- app/routers/dashboard.py | 15 ++---------- app/templates/base.html | 7 ++++-- app/templates/board.html | 9 +++++++ 4 files changed, 47 insertions(+), 35 deletions(-) diff --git a/app/routers/board.py b/app/routers/board.py index a18708c..65c0f82 100644 --- a/app/routers/board.py +++ b/app/routers/board.py @@ -117,32 +117,41 @@ def _map_issue_to_card(issue: dict, owner: str = "", repo: str = "") -> dict: def _sidebar_data(request: Request, owner: str = "", repo: str = "") -> dict: user = SessionManager.decode_session(request.cookies.get("flowdeck_session", "")) - ws = user.get("login", "Bruno") if user else "Bruno" + ws_name = user.get("login", "Bruno") if user else "Bruno" + ws_key = f"{owner}/{repo}" if owner and repo else ws_name # workspace = project context recent = [] if owner and repo: - recent = [ - {"id": f"{owner}/{repo}", "name": "Kanban board", "icon": "📁", - "url": f"/board/{owner}/{repo}", "active": True, "indent": 0}, - ] - for sv in ["Detailed board", "Table view", "Status overview", "Team Load"]: - recent.append({"id": f"{owner}/{repo}/{sv.lower().replace(' ', '-')}", - "name": sv, "icon": "·", "url": f"/board/{owner}/{repo}", - "active": False, "indent": 1}) - # Load user-created pages from DB + view_map = { + "Kanban board": "kanban", "Detailed board": "detailed", + "Table view": "table", "Status overview": "status", "Team Load": "teamload", + } + first = True + for label, view in view_map.items(): + indent = 0 if first else 1 + active = first + recent.append({ + "id": f"{owner}/{repo}/{view}", + "name": label, "icon": "📁" if first else "·", + "url": f"/board/{owner}/{repo}?view={view}", + "active": active, "indent": indent, + }) + first = False + # Load pages for this project workspace with get_conn() as conn: pages = conn.execute( "SELECT id, title, updated_at FROM pages WHERE workspace=? ORDER BY updated_at DESC LIMIT 10", - (ws,), + (ws_key,), ).fetchall() for p in pages: recent.append({ "id": f"page/{p['id']}", "name": p["title"], "icon": "📄", "url": f"/pages/{p['id']}", "active": False, "indent": 0, }) - return {"workspace_name": ws, "workspace_initial": ws[0].upper() if ws else "B", + return {"workspace_name": ws_name, "workspace_initial": ws_name[0].upper() if ws_name else "B", "current_page": repo or "Dashboard", "last_edited": "now", "recent_pages": recent, "private_pages": [r for r in recent if r.get("active") or r["id"].startswith("page/")], - "favorite_pages": [], "user": user} + "favorite_pages": [], "user": user, + "workspace_key": ws_key} def _extract_ai_keywords(owner: str, repo: str, labels: list[dict], body: str = ""): @@ -261,12 +270,13 @@ async def library_page(request: Request): # ═══════════ Board page ═══════════ @router.get("/{owner}/{repo}", response_class=HTMLResponse) -async def board(request: Request, owner: str, repo: str): +async def board(request: Request, owner: str, repo: str, view: str = Query(default="")): from jinja2 import Environment, FileSystemLoader env = Environment(loader=FileSystemLoader("app/templates")) sidebar = _sidebar_data(request, owner, repo) template = env.get_template("board.html") - return template.render(request=request, owner=owner, repo=repo, groups=[], **sidebar) + return template.render(request=request, owner=owner, repo=repo, groups=[], + initial_view=view, **sidebar) # ═══════════ View fragments ═══════════ @@ -428,18 +438,19 @@ async def extract_ai_keywords(owner: str, repo: str): @router.post("/api/pages") async def create_page(request: Request, title: str = Query(default="New page"), - section: str = Query(default="Private")): - """Create a new Markdown page.""" + section: str = Query(default="Private"), + project: str = Query(default="")): + """Create a new Markdown page in the given project workspace.""" user = SessionManager.decode_session(request.cookies.get("flowdeck_session", "")) - ws = user.get("login", "Bruno") if user else "Bruno" + ws_key = project if project else (user.get("login", "Bruno") if user else "Bruno") with get_conn() as conn: cur = conn.execute( "INSERT INTO pages (workspace, title, parent_section) VALUES (?,?,?)", - (ws, title, section), + (ws_key, title, section), ) conn.commit() page_id = cur.lastrowid - return {"status": "ok", "id": page_id, "title": title} + return {"status": "ok", "id": page_id, "title": title, "workspace": ws_key} @router.get("/api/pages/{page_id}") diff --git a/app/routers/dashboard.py b/app/routers/dashboard.py index 7655416..ede0bee 100644 --- a/app/routers/dashboard.py +++ b/app/routers/dashboard.py @@ -31,25 +31,14 @@ def _sidebar_data(request: Request, repos: list[dict]) -> dict: "indent": 0, }) - # Load user-created pages - with get_conn() as conn: - pages = conn.execute( - "SELECT id, title FROM pages WHERE workspace=? ORDER BY updated_at DESC LIMIT 10", - (ws,), - ).fetchall() - for p in pages: - recent_pages.append({ - "id": f"page/{p['id']}", "name": p["title"], "icon": "📄", - "url": f"/pages/{p['id']}", "active": False, "indent": 0, - }) - return { "workspace_name": ws, "workspace_initial": initial, "current_page": "Dashboard", "last_edited": "now", "recent_pages": recent_pages, - "private_pages": [p for p in recent_pages if p["id"].startswith("page/")], + "private_pages": [], "favorite_pages": [], "user": user, + "workspace_key": "", } diff --git a/app/templates/base.html b/app/templates/base.html index 50bd190..c0e31db 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -342,6 +342,7 @@ pageIcon: '{% block page_icon %}📁{% endblock %}', pageTitle: '{% block page_title %}FlowDeck{% endblock %}', lastEdited: '{{ last_edited }}', + workspaceKey: '{{ workspace_key }}', currentView: 'home', contextMenu: { visible: false, x: 0, y: 0, pageId: null, pageName: '' }, contextActions: {}, @@ -383,7 +384,8 @@ return m ? m[1] : ''; }, newPage(section) { - fetch(`/board/api/pages?title=New%20page§ion=${encodeURIComponent(section)}`, { + const project = this.workspaceKey || ''; + fetch(`/board/api/pages?title=New%20page§ion=${encodeURIComponent(section)}&project=${encodeURIComponent(project)}`, { method: 'POST', headers: { 'X-CSRF-Token': this.getCsrfToken() } }) @@ -392,7 +394,8 @@ .catch(err => { alert('Failed to create page: ' + err.message); }); }, newSubPage(parentId) { - fetch(`/board/api/pages?title=New%20page§ion=Private`, { + const project = this.workspaceKey || ''; + fetch(`/board/api/pages?title=New%20page§ion=Private&project=${encodeURIComponent(project)}`, { method: 'POST', headers: { 'X-CSRF-Token': this.getCsrfToken() } }) diff --git a/app/templates/board.html b/app/templates/board.html index 700013a..172621f 100644 --- a/app/templates/board.html +++ b/app/templates/board.html @@ -153,6 +153,15 @@