feat: sidebar user menu Notion-style + settings page + tag colors + preview images/PDF
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped

- Sidebar header: user menu dropdown (Settings, Switch workspace, Log out)
  avec avatar + nom + email, chevron animé, fond opaque #1E1E1E
- Nouvelle page /settings avec gestion globale des tags:
  créer tag avec couleur, changer couleur, supprimer, liste avec compteurs
- API tag management: POST/PUT/DELETE /api/settings/tags, GET /api/settings/tags/all
- Preview panel: affichage images (img tag), PDF (lien viewer), détection format
- workspace-chevron CSS: rotation 180° quand menu ouvert
This commit is contained in:
2026-07-12 12:35:17 -04:00
parent 17f158ca18
commit 004c47df34
5 changed files with 292 additions and 168 deletions
+74
View File
@@ -1080,6 +1080,80 @@ async def select_workspace(request: Request, ws_id: int):
return response
# ═══════════ Settings Page ═══════════
@router.get("/settings", response_class=HTMLResponse)
async def app_settings_page(request: Request):
"""Settings & configuration page."""
from jinja2 import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader("app/templates"))
sidebar = _sidebar_data(request, [])
user = SessionManager.decode_session(request.cookies.get("flowdeck_session", ""))
if not user:
return RedirectResponse("/auth/login?provider=local", status_code=302)
ctx = {**sidebar, "user": user}
template = env.get_template("settings.html")
return template.render(**ctx)
# ═══════════ Tag Management API (global) ═══════════
@router.post("/api/settings/tags")
async def create_tag_global(request: Request):
"""Create a tag with optional color."""
import json
body = await request.json()
tag_name = body.get("name", "").strip().lower()
color = body.get("color", "#787774")
if not tag_name:
return {"error": "Tag name required"}, 400
with get_conn() as conn:
tag = conn.execute("SELECT id FROM tags WHERE name = ?", (tag_name,)).fetchone()
if tag:
conn.execute("UPDATE tags SET color = ? WHERE id = ?", (color, tag["id"]))
conn.commit()
return {"tag": {"id": tag["id"], "name": tag_name, "color": color}}
cursor = conn.execute("INSERT INTO tags (name, color) VALUES (?, ?)", (tag_name, color))
conn.commit()
return {"tag": {"id": cursor.lastrowid, "name": tag_name, "color": color}}
@router.put("/api/settings/tags/{tag_id:int}")
async def update_tag_global(tag_id: int, request: Request):
"""Update a tag (name or color)."""
import json
body = await request.json()
with get_conn() as conn:
if "name" in body:
conn.execute("UPDATE tags SET name = ? WHERE id = ?", (body["name"].strip().lower(), tag_id))
if "color" in body:
conn.execute("UPDATE tags SET color = ? WHERE id = ?", (body["color"], tag_id))
conn.commit()
return {"status": "ok"}
@router.delete("/api/settings/tags/{tag_id:int}")
async def delete_tag_global(tag_id: int):
"""Delete a tag and its page associations."""
with get_conn() as conn:
conn.execute("DELETE FROM page_tags WHERE tag_id = ?", (tag_id,))
conn.execute("DELETE FROM tags WHERE id = ?", (tag_id,))
conn.commit()
return {"status": "ok"}
@router.get("/api/settings/tags/all")
async def list_all_tags_global():
"""List all tags with counts."""
with get_conn() as conn:
rows = conn.execute(
"SELECT t.id, t.name, t.color, COUNT(pt.page_id) as count "
"FROM tags t LEFT JOIN page_tags pt ON pt.tag_id = t.id "
"GROUP BY t.id ORDER BY t.name"
).fetchall()
return {"tags": [dict(r) for r in rows]}
# ═══════════ Tags API ═══════════
@router.get("/api/local-workspace/tags")