Menu d'actions rapides par carte; barre d'outils tri + regroupement par propriétaire (sections repliables, préférences localStorage); bascule Grille/Liste persistée avec tableaux triables réutilisant jalon/santé des cartes; dashboard.js dédié.
This commit is contained in:
@@ -24,7 +24,22 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<main x-data="{ tab: 'active', search: '' }">
|
||||
<script>
|
||||
// État du dashboard (#21/#22) : la vue grille/liste survit au
|
||||
// rechargement (localStorage), lue avant qu'Alpine démarre.
|
||||
function dashState() {
|
||||
var view = 'grid';
|
||||
try { view = localStorage.getItem('kanbante-dash-view') || 'grid'; } catch (e) { /* défaut */ }
|
||||
return {
|
||||
tab: 'active', search: '', view: view,
|
||||
setView: function (v) {
|
||||
this.view = v;
|
||||
try { localStorage.setItem('kanbante-dash-view', v); } catch (e) { /* non-fatal */ }
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
<main x-data="dashState()">
|
||||
<!-- Tabs: "actif" = mis à jour dans la fenêtre glissante;
|
||||
"archivé" = le vrai drapeau Gitea (repo.archived). -->
|
||||
<div class="tabs">
|
||||
@@ -46,10 +61,34 @@
|
||||
<kbd class="kbd">/</kbd>
|
||||
</div>
|
||||
|
||||
<!-- Outils d'affichage (#21 tri/regroupement, #22 vue liste) :
|
||||
pilotés par dashboard.js, préférences en localStorage. -->
|
||||
<div class="dash-tools">
|
||||
<label class="dash-tool">↕️ Tri
|
||||
<select id="dash-sort">
|
||||
<option value="default">Par défaut</option>
|
||||
<option value="updated">Activité Gitea récente</option>
|
||||
<option value="activity">Activité kanban récente</option>
|
||||
<option value="open">Issues ouvertes ↓</option>
|
||||
<option value="name">Nom A → Z</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="dash-tool dash-tool-check">
|
||||
<input type="checkbox" id="dash-group"> 🗂️ Grouper par propriétaire
|
||||
</label>
|
||||
<div class="dash-view-toggle" role="group" aria-label="Mode d'affichage">
|
||||
<button type="button" :class="{ active: view === 'grid' }" @click="setView('grid')">▦ Grille</button>
|
||||
<button type="button" :class="{ active: view === 'list' }" @click="setView('list')">☰ Liste</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% macro project_card(repo, archived=False) %}
|
||||
{% set full = repo.full_name %}
|
||||
<div class="project-card{% if archived %} archived{% endif %}"
|
||||
data-full="{{ full }}" data-home="{{ repo.home_tab or '' }}"
|
||||
data-name="{{ repo.name|lower }}" data-owner="{{ repo.owner.login }}"
|
||||
data-updated="{{ repo.updated_at }}" data-open="{{ repo.open_issues_count or 0 }}"
|
||||
data-activity="{{ repo.activity.last_at if repo.activity else '' }}"
|
||||
data-search="{{ (repo.name ~ ' ' ~ repo.full_name ~ ' ' ~ (repo.description or ''))|lower }}"
|
||||
x-show="search === '' || $el.dataset.search.includes(search.toLowerCase())">
|
||||
<div class="card-header">
|
||||
@@ -60,6 +99,19 @@
|
||||
<span class="date">{{ repo.updated_at[:10] }}</span>
|
||||
{% if not archived %}
|
||||
<button type="button" class="star-btn" data-full="{{ full }}" data-starred="{{ 1 if repo.starred else 0 }}" title="{{ 'Désépingler (étoile Gitea)' if repo.starred else 'Épingler (étoile Gitea)' }}" aria-label="Épingler {{ repo.name }}">{{ '⭐' if repo.starred else '☆' }}</button>
|
||||
<div class="card-menu-wrap">
|
||||
<button type="button" class="card-menu-btn" data-menu-btn aria-haspopup="true" aria-expanded="false" title="Actions rapides pour {{ repo.name }}" aria-label="Actions rapides pour {{ repo.name }}">⋯</button>
|
||||
<div class="card-menu" hidden>
|
||||
<a href="/board/{{ full }}">📋 Board</a>
|
||||
<a href="/capture?repo={{ full|urlencode }}">⚡ Capturer ici</a>
|
||||
<a href="/triage/{{ full }}">📥 Triage du backlog</a>
|
||||
<a href="/health/{{ full }}">🩺 Santé</a>
|
||||
<a href="/sprint/{{ full }}">📖 Récit de sprint</a>
|
||||
{% if repo.html_url %}
|
||||
<a href="{{ repo.html_url }}" target="_blank" rel="noopener">🔗 Gitea ↗</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<h3>
|
||||
@@ -107,9 +159,62 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if repo.activity %}
|
||||
<div class="activity-line">
|
||||
{% if repo.activity.closed or repo.activity.moved %}
|
||||
📈 {{ repo.activity.closed }} fermée(s) · {{ repo.activity.moved }} déplacement(s) cette semaine
|
||||
{% elif repo.activity.idle_days is not none and repo.activity.idle_days > 7 %}
|
||||
💤 aucune activité kanban depuis {{ repo.activity.idle_days }} j
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro project_row(repo, archived=False) %}
|
||||
{% set full = repo.full_name %}
|
||||
{% set st = stats.get(full, {}) %}
|
||||
<tr data-name="{{ repo.name|lower }}" data-owner="{{ repo.owner.login|lower }}"
|
||||
data-open="{{ repo.open_issues_count or 0 }}"
|
||||
data-inflight="{{ (st.get('in_progress', 0) + st.get('qa', 0) + st.get('review', 0)) }}"
|
||||
data-updated="{{ repo.updated_at }}"
|
||||
data-search="{{ (repo.name ~ ' ' ~ repo.full_name ~ ' ' ~ (repo.description or ''))|lower }}"
|
||||
x-show="search === '' || $el.dataset.search.includes(search.toLowerCase())">
|
||||
<td><a href="/project/{{ full }}"><strong>{{ repo.name }}</strong></a></td>
|
||||
<td>{{ repo.owner.login }}</td>
|
||||
<td>{{ repo.open_issues_count or 0 }}</td>
|
||||
<td>{{ st.get('in_progress', 0) + st.get('qa', 0) + st.get('review', 0) }}</td>
|
||||
<td>{% if repo.next_milestone %}🎯 {{ repo.next_milestone.title }}{% if repo.next_milestone.late %} ⚠️{% endif %}{% else %}—{% endif %}</td>
|
||||
<td>{% if repo.health_dot %}<a href="/health/{{ full }}" class="health-dot {{ repo.health_dot.level }}" title="{{ repo.health_dot.tooltip }}" aria-label="Santé : {{ repo.health_dot.tooltip }}"></a>{% else %}—{% endif %}</td>
|
||||
<td>{{ repo.updated_at[:10] }}</td>
|
||||
</tr>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro project_table(repos, tab_name, archived=False) %}
|
||||
<div x-show="tab === '{{ tab_name }}'">
|
||||
<table class="proj-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-sort="name">Projet</th>
|
||||
<th data-sort="owner">Propriétaire</th>
|
||||
<th data-sort="open">Ouvertes</th>
|
||||
<th data-sort="inflight">En vol</th>
|
||||
<th>Prochain jalon</th>
|
||||
<th>Santé</th>
|
||||
<th data-sort="updated">Dernière activité</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for repo in repos %}{{ project_row(repo, archived) }}{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% if not repos %}
|
||||
<div class="empty-state">Aucun projet dans cette vue.</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
<div x-show="view === 'grid'">
|
||||
<!-- Épinglés (#20) : les dépôts étoilés dans Gitea, hors onglets. -->
|
||||
<section id="pinned-section"{% if not pinned_repos %} hidden{% endif %}>
|
||||
<h3 class="pinned-title">⭐ Épinglés</h3>
|
||||
@@ -138,6 +243,14 @@
|
||||
<div class="empty-state">🗄️ Aucun projet archivé. L'archivage se fait dans Gitea (Réglages du dépôt).</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Vue liste dense (#22) : mêmes onglets, mêmes données. -->
|
||||
<div x-show="view === 'list'">
|
||||
{{ project_table(active_repos, 'active') }}
|
||||
{{ project_table(inactive_repos, 'inactive') }}
|
||||
{{ project_table(archived_repos, 'archived', archived=True) }}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
@@ -146,6 +259,7 @@
|
||||
<script src="/static/js/live.js" defer></script>
|
||||
<script>window.KANBANTE_CSRF = "{{ csrf_token }}";</script>
|
||||
<script src="/static/js/stars.js" defer></script>
|
||||
<script src="/static/js/dashboard.js" defer></script>
|
||||
<script>
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.key === '/' && !/INPUT|TEXTAREA/.test(document.activeElement?.tagName || '')) {
|
||||
|
||||
Reference in New Issue
Block a user