139 lines
7.0 KiB
HTML
139 lines
7.0 KiB
HTML
<!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 defer src="/static/vendor/alpine.min.js"></script>
|
|
</head>
|
|
<body>
|
|
<header class="app-header">
|
|
<div class="brand">
|
|
<a href="/" class="logo" aria-label="Kanbante — accueil"><span class="logo-mark">📋</span><span class="gradient-text">Kanbante</span></a>
|
|
</div>
|
|
<nav class="nav-pills">
|
|
<a href="/" class="nav-pill active">🏠 Projets</a>
|
|
<a href="/focus" class="nav-pill">🎯 Focus</a>
|
|
<a href="/my" class="nav-pill">👤 Mes issues</a>
|
|
<a href="/activity" class="nav-pill">📰 Activité</a>
|
|
<a href="/briefing" class="nav-pill">📊 Briefing</a>
|
|
<a href="/search" class="nav-pill">🔍 Recherche</a>
|
|
<a href="/capture" class="nav-pill">⚡ Capture</a>
|
|
<a href="/wall" class="nav-pill">🖥️ Wallboard</a>
|
|
</nav>
|
|
</header>
|
|
|
|
<section class="hero">
|
|
<div class="hero-text">
|
|
<h2>Bonjour 👋 Voici vos projets Gitea, en un coup d'œil.</h2>
|
|
<p>Glissez les cartes, suivez ce qui avance, et laissez Gitea rester la source de vérité.</p>
|
|
</div>
|
|
<div class="stat-tiles">
|
|
<div class="stat-tile"><span class="stat-num">{{ active_repos|length }}</span><span class="stat-label">projets actifs</span></div>
|
|
<div class="stat-tile"><span class="stat-num">{{ metrics.closed }}</span><span class="stat-label">issues fermées · 30 j</span></div>
|
|
<div class="stat-tile"><span class="stat-num">{{ metrics.moved }}</span><span class="stat-label">déplacements · 30 j</span></div>
|
|
<div class="stat-tile"><span class="stat-num">{% if metrics.avg_cycle_days is not none %}{{ metrics.avg_cycle_days }} j{% else %}—{% endif %}</span><span class="stat-label">temps de cycle moyen</span></div>
|
|
</div>
|
|
</section>
|
|
|
|
<main x-data="{ tab: 'active', search: '' }">
|
|
<!-- Tabs: "actif" = mis à jour dans la fenêtre glissante;
|
|
"archivé" = le vrai drapeau Gitea (repo.archived). -->
|
|
<div class="tabs">
|
|
<button :class="{ active: tab === 'active' }" @click="tab = 'active'">
|
|
🟢 Actifs ({{ active_repos|length }})
|
|
</button>
|
|
<button :class="{ active: tab === 'inactive' }" @click="tab = 'inactive'">
|
|
💤 Inactifs ({{ inactive_repos|length }})
|
|
</button>
|
|
<button :class="{ active: tab === 'archived' }" @click="tab = 'archived'">
|
|
🗄️ Archivés ({{ archived_repos|length }})
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Search: the repo name lives in a data-* attribute (HTML-escaped
|
|
by Jinja), never interpolated into a JS string literal. -->
|
|
<div class="search-bar">
|
|
<input type="text" id="project-search" placeholder="🔍 Filtrer les projets..." x-model="search" autocomplete="off">
|
|
<kbd class="kbd">/</kbd>
|
|
</div>
|
|
|
|
{% macro project_card(repo, archived=False) %}
|
|
{% set full = repo.full_name %}
|
|
<div class="project-card{% if archived %} archived{% endif %}"
|
|
data-search="{{ (repo.name ~ ' ' ~ repo.full_name ~ ' ' ~ (repo.description or ''))|lower }}"
|
|
x-show="search === '' || $el.dataset.search.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>
|
|
{% if full in stats %}
|
|
{% set total = stats[full].values()|sum %}
|
|
{% set done_n = stats[full].get('done', 0) %}
|
|
{% if total > 0 %}
|
|
<div class="progress" title="{{ done_n }}/{{ total }} terminées">
|
|
<span style="width: {{ (100 * done_n / total)|round|int }}%"></span>
|
|
</div>
|
|
{% endif %}
|
|
{% endif %}
|
|
<div class="card-footer">
|
|
<span>{{ repo.open_issues_count or 0 }} issues ouvertes</span>
|
|
{% if full in stats %}
|
|
<div class="mini-progress">
|
|
{% for col in ['backlog','todo','in_progress','qa','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>
|
|
{% endmacro %}
|
|
|
|
<div x-show="tab === 'active'" class="project-grid">
|
|
{% for repo in active_repos %}{{ project_card(repo) }}{% endfor %}
|
|
{% if not active_repos %}
|
|
<div class="empty-state">🌱 Aucun projet actif en ce moment. Les projets bougent dès qu'une issue ou un commit arrive dans Gitea.</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div x-show="tab === 'inactive'" class="project-grid">
|
|
{% for repo in inactive_repos %}{{ project_card(repo) }}{% endfor %}
|
|
{% if not inactive_repos %}
|
|
<div class="empty-state">✨ Rien d'endormi : tous vos projets ont eu de l'activité récemment.</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div x-show="tab === 'archived'" class="project-grid">
|
|
{% for repo in archived_repos %}{{ project_card(repo, archived=True) }}{% endfor %}
|
|
{% if not archived_repos %}
|
|
<div class="empty-state">🗄️ Aucun projet archivé. L'archivage se fait dans Gitea (Réglages du dépôt).</div>
|
|
{% endif %}
|
|
</div>
|
|
</main>
|
|
|
|
<footer>
|
|
Kanbante v{{ version }} · {{ active_repos|length + inactive_repos|length + archived_repos|length }} projets sur Gitea · actifs = mis à jour depuis {{ active_window_days }} jours
|
|
</footer>
|
|
<script src="/static/js/live.js" defer></script>
|
|
<script>
|
|
document.addEventListener('keydown', function (e) {
|
|
if (e.key === '/' && !/INPUT|TEXTAREA/.test(document.activeElement?.tagName || '')) {
|
|
e.preventDefault();
|
|
document.getElementById('project-search')?.focus();
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|