feat: Kanbante v0.1.0 — Kanban Git-integrated PM tool

- Dashboard with all Gitea projects (active/archived)
- Kanban board per project (Backlog → À faire → En cours → Révision → Terminé)
- Drag & drop issues between columns (SortableJS)
- Project notes panel
- Gitea API integration (read issues, repos)
- SQLite local state for board positions
- Dark theme design system
- Docker-ready (Dockerfile + docker-compose.yml)
- FastAPI + Jinja2 + HTMX + Alpine.js stack
This commit is contained in:
2026-07-07 23:24:15 -04:00
commit 3732b5cb45
13 changed files with 1094 additions and 0 deletions
+84
View File
@@ -0,0 +1,84 @@
<!DOCTYPE html>
<html lang="fr" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ repo }} — Kanbante</title>
<link rel="stylesheet" href="/static/css/style.css">
<script src="https://unpkg.com/[email protected]"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/Sortable.min.js"></script>
<script src="/static/js/board.js" defer></script>
</head>
<body>
<header>
<h1>
<a href="/">📋 Kanbante</a>
<span class="sep">/</span>
<span>{{ owner }}/</span><strong>{{ repo }}</strong>
</h1>
<nav>
{% if repo_info.html_url %}
<a href="{{ repo_info.html_url }}" target="_blank" class="btn-sm">🔗 Gitea</a>
{% endif %}
</nav>
</header>
<main class="board-container">
<div class="kanban-board">
{% for col in ['backlog','todo','in_progress','review','done'] %}
<div class="kanban-column"
data-column="{{ col }}">
<div class="column-header" style="border-top: 3px solid {{ column_colors[col] }}">
<span class="column-title">{{ column_labels[col] }}</span>
<span class="column-count">{{ columns[col]|length }}</span>
</div>
<div class="column-cards" data-column="{{ col }}">
{% for issue in columns[col] %}
<div class="kanban-card"
data-issue="{{ issue.number }}"
data-repo="{{ full_name }}"
draggable="true">
<div class="card-id">#{{ issue.number }}</div>
<div class="card-title">{{ issue.title }}</div>
{% if issue.labels %}
<div class="card-labels">
{% for label in issue.labels %}
<span class="label-tag" style="background: #{{ label.color }}30; color: #{{ label.color }}">
{{ label.name }}
</span>
{% endfor %}
</div>
{% endif %}
<div class="card-meta">
{% if issue.assignee %}
<img src="{{ issue.assignee.avatar_url }}" class="avatar" alt="{{ issue.assignee.login }}">
{% endif %}
<span class="card-date">{{ issue.created_at[:10] }}</span>
</div>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
<!-- Notes panel -->
<aside class="notes-panel">
<h3>📝 Notes du projet</h3>
<form hx-post="/api/notes" hx-swap="none">
<input type="hidden" name="repo_full_name" value="{{ full_name }}">
<textarea name="note" rows="10"
placeholder="Notes, idées, liens...">{{ notes }}</textarea>
<button type="submit" class="btn-save">💾 Sauvegarder</button>
</form>
</aside>
</main>
<footer>
<span>{{ full_name }}</span>
{% if repo_info.updated_at %}
<span>Mis à jour {{ repo_info.updated_at[:10] }}</span>
{% endif %}
</footer>
</body>
</html>
+90
View File
@@ -0,0 +1,90 @@
<!DOCTYPE html>
<html lang="fr" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kanbante — Dashboard</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>📋</text></svg>">
<link rel="stylesheet" href="/static/css/style.css">
<script src="https://unpkg.com/[email protected]"></script>
<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js"></script>
</head>
<body>
<header>
<h1><a href="/">📋 <span class="gradient-text">Kanbante</span></a></h1>
<nav>
<span class="badge">{{ active_repos|length }} actifs</span>
<span class="badge secondary">{{ inactive_repos|length }} archivés</span>
</nav>
</header>
<main x-data="{ tab: 'active', search: '' }">
<!-- Tabs -->
<div class="tabs">
<button :class="{ active: tab === 'active' }" @click="tab = 'active'">
🟢 Actifs ({{ active_repos|length }})
</button>
<button :class="{ active: tab === 'inactive' }" @click="tab = 'inactive'">
⚪ Archivés ({{ inactive_repos|length }})
</button>
</div>
<!-- Search -->
<div class="search-bar">
<input type="text" placeholder="🔍 Filtrer les projets..." x-model="search">
</div>
<!-- Active Projects -->
<div x-show="tab === 'active'" class="project-grid">
{% for repo in active_repos %}
{% set full = repo.full_name %}
<div class="project-card"
x-show="search === '' || '{{ repo.name.lower() }}{{ repo.full_name.lower() }}'.includes(search.toLowerCase())">
<div class="card-header">
<span class="org-tag">{{ repo.owner.login }}</span>
<span class="date">{{ repo.updated_at[:10] }}</span>
</div>
<h3>
<a href="/board/{{ repo.owner.login }}/{{ repo.name }}">{{ repo.name }}</a>
</h3>
<p class="desc">{{ repo.description or '—' }}</p>
<div class="card-footer">
<span>{{ repo.open_issues_count or 0 }} issues</span>
{% if full in stats %}
<div class="mini-progress">
{% for col in ['backlog','todo','in_progress','review','done'] %}
{% set n = stats[full].get(col, 0) %}
{% if n > 0 %}
<span class="chip" style="background: {{ COLUMN_COLORS[col] }}20; color: {{ COLUMN_COLORS[col] }}">
{{ n }}
</span>
{% endif %}
{% endfor %}
</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
<!-- Inactive Projects -->
<div x-show="tab === 'inactive'" class="project-grid">
{% for repo in inactive_repos %}
<div class="project-card archived"
x-show="search === '' || '{{ repo.name.lower() }}'.includes(search.toLowerCase())">
<div class="card-header">
<span class="org-tag">{{ repo.owner.login }}</span>
<span class="date">{{ repo.updated_at[:10] }}</span>
</div>
<h3>{{ repo.name }}</h3>
<p class="desc">{{ repo.description or '—' }}</p>
</div>
{% endfor %}
</div>
</main>
<footer>
Kanbante v0.1 · {{ active_repos|length + inactive_repos|length }} projets sur Gitea
</footer>
</body>
</html>