Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40f034e86e | ||
|
|
911f07362f | ||
|
|
ee837972be | ||
|
|
4470e6ccbc | ||
|
|
d8cb4664e5 | ||
|
|
f2e702a3d9 | ||
|
|
435954c31b | ||
|
|
7f66775039 |
@@ -1,3 +1,8 @@
|
||||
## 0.44.0 — 2026-10-29 — Ergonomie : tuile Projets et pied de sidebar
|
||||
|
||||
- **Tuile Projets** : en-tête groupé (gauche : organisation + santé, droite : date + étoile + menu) au lieu de 5 éléments éparpillés ; avancement global labellisé (« Avancement — X/Y terminées ») au lieu d'une barre anonyme ; jalon séparé par un filet, avec étiquette « Prochain jalon » et échéance distincte ; pied en 2 rangées — (1) compte d'issues + bouton Board, (2) répartition par colonne labellisée (« En cours 8 », « Terminé 59 », au lieu de chiffres nus 8 / 59) + prévision ; activité de la semaine en texte secondaire (« Cette semaine : … »).
|
||||
- **Pied de sidebar** : 2 rangées séparées par un filet — (1) profil + bouton Sortir icône seule (⏻, aria-label, sans texte), (2) version + fraîcheur des données en pastille discrète (point vert + « à l'instant », infobulle expliquant les instantanés Gitea). En rail replié, la rangée méta est masquée, les icônes restent centrées.
|
||||
|
||||
## 0.43.0 — 2026-10-29 — Icônes monochromes sur tout le site (#59, suite)
|
||||
|
||||
- **Icônes colorées retirées de toutes les pages** : emoji décoratifs devant titres, boutons et liens supprimés (le texte porte le sens) ; icônes fonctionnelles passées en trait/monochrome — étoile ★/☆ (couleur texte, plus jaune), recherche en SVG, épingler déjà en SVG, thème ☀/☾ en symboles texte.
|
||||
|
||||
+1
-1
@@ -177,7 +177,7 @@ from urllib.parse import urlparse
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
APP_VERSION = "0.43.0"
|
||||
APP_VERSION = "0.44.0"
|
||||
|
||||
|
||||
def _env_bool(name: str, default: bool) -> bool:
|
||||
|
||||
+17
-13
@@ -137,19 +137,23 @@
|
||||
{% endfor %}
|
||||
</nav>
|
||||
<div class="side-foot">
|
||||
{% if data_freshness %}
|
||||
<span class="side-fresh side-label" title="Dernière actualisation des données Gitea (instantanés d'arrière-plan)">{{ data_freshness }}</span>
|
||||
{% endif %}
|
||||
{% if current_user %}
|
||||
<a href="/profile" class="side-logout" title="Mon profil"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true" style="width:18px;height:18px;display:inline-block;vertical-align:-3px"><circle cx="12" cy="8" r="3.5"/><path d="M4 20c0-3.5 3.5-5.5 8-5.5s8 2 8 5.5"/></svg><span class="side-label"> {{ current_user.display_name or current_user.username }}</span></a>
|
||||
{% endif %}
|
||||
<span class="side-version side-label">v{{ version }}</span>
|
||||
{% if not is_admin %}
|
||||
<button type="button" class="theme-toggle-foot" id="theme-toggle" title="Basculer entre thème sombre et clair" aria-label="Basculer entre thème sombre et clair"><span data-theme-ico>☀</span></button>
|
||||
{% endif %}
|
||||
{% if auth_enabled %}
|
||||
<a href="/logout" class="side-logout" title="Se déconnecter">⏻<span class="side-label"> Sortir</span></a>
|
||||
{% endif %}
|
||||
<div class="side-foot-user">
|
||||
{% if current_user %}
|
||||
<a href="/profile" class="side-logout side-profile" title="Mon profil"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true" style="width:18px;height:18px;display:inline-block;vertical-align:-3px"><circle cx="12" cy="8" r="3.5"/><path d="M4 20c0-3.5 3.5-5.5 8-5.5s8 2 8 5.5"/></svg><span class="side-label"> {{ current_user.display_name or current_user.username }}</span></a>
|
||||
{% endif %}
|
||||
{% if not is_admin %}
|
||||
<button type="button" class="theme-toggle-foot" id="theme-toggle" title="Basculer entre thème sombre et clair" aria-label="Basculer entre thème sombre et clair"><span data-theme-ico>☀</span></button>
|
||||
{% endif %}
|
||||
{% if auth_enabled %}
|
||||
<a href="/logout" class="side-logout logout-icon" title="Se déconnecter" aria-label="Se déconnecter">⏻</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="side-foot-meta">
|
||||
<span class="side-version side-label">v{{ version }}</span>
|
||||
{% if data_freshness %}
|
||||
<span class="side-fresh" title="Dernière actualisation des données Gitea (instantanés d'arrière-plan) — les données affichées datent de cet instantané"><span class="fresh-dot" aria-hidden="true"></span><span class="side-label">{{ data_freshness }}</span></span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<div class="side-backdrop" id="side-backdrop" hidden></div>
|
||||
|
||||
@@ -91,14 +91,17 @@
|
||||
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>
|
||||
{% if multi_conn %}<span class="conn-badge" title="Connexion {{ repo._conn_slug }}">{% if repo._conn_kind == 'github' %}GitHub{% else %}Gitea{% endif %} {{ repo._conn_slug }}</span>{% endif %}
|
||||
{% if repo.health_dot %}
|
||||
<a href="/health/{{ full }}" class="health-dot {{ repo.health_dot.level }}" title="Santé du flux — {{ repo.health_dot.tooltip }}" aria-label="Santé du flux : {{ repo.health_dot.tooltip }}"></a>
|
||||
{% endif %}
|
||||
<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-header-left">
|
||||
<span class="org-tag">{{ repo.owner.login }}</span>
|
||||
{% if multi_conn %}<span class="conn-badge" title="Connexion {{ repo._conn_slug }}">{% if repo._conn_kind == 'github' %}GitHub{% else %}Gitea{% endif %} {{ repo._conn_slug }}</span>{% endif %}
|
||||
{% if repo.health_dot %}
|
||||
<a href="/health/{{ full }}" class="health-dot {{ repo.health_dot.level }}" title="Santé du flux — {{ repo.health_dot.tooltip }}" aria-label="Santé du flux : {{ repo.health_dot.tooltip }}"></a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-header-actions">
|
||||
<span class="date" title="Dernière activité">{{ 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>
|
||||
@@ -115,26 +118,30 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<h3>
|
||||
<a href="/project/{{ repo.owner.login }}/{{ repo.name }}">{{ repo.name }}</a>
|
||||
</h3>
|
||||
<p class="desc">{{ repo.description or '—' }}</p>
|
||||
<p class="desc" title="{{ repo.description or '' }}">{{ 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 class="card-progress">
|
||||
<div class="progress-head"><span>Avancement</span><span>{{ done_n }}/{{ total }} terminées</span></div>
|
||||
<div class="progress" title="{{ done_n }}/{{ total }} terminées">
|
||||
<span style="width: {{ (100 * done_n / total)|round|int }}%"></span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if repo.next_milestone %}
|
||||
{% set ms = repo.next_milestone %}
|
||||
<div class="ms-block">
|
||||
<div class="ms-head"><span class="ms-kicker">Prochain jalon</span>{% if ms.due_label %}<span class="ms-due{% if ms.late %} late{% endif %}">échéance {{ ms.due_label }}</span>{% endif %}</div>
|
||||
<div class="ms-line">
|
||||
<a href="/forecast#ms-{{ repo.owner.login }}-{{ repo.name }}" class="ms-title" title="Voir ce jalon dans le tableau des prévisions">{{ ms.title }}</a>
|
||||
{% if ms.due_label %}<span class="ms-due{% if ms.late %} late{% endif %}">{{ '' if ms.late }}échéance {{ ms.due_label }}</span>{% endif %}
|
||||
</div>
|
||||
{% if ms.total %}
|
||||
<div class="progress ms-progress"><span style="width: {{ ms.pct }}%"></span></div>
|
||||
@@ -143,28 +150,32 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="card-footer">
|
||||
<span>{{ repo.open_issues_count or 0 }} issues ouvertes</span>
|
||||
<a href="/board/{{ full }}" class="card-board-link">Board</a>
|
||||
{% if repo.forecast_label %}
|
||||
<span class="forecast-line" title="Prévision d'après le débit réel du journal (fermetures par semaine)">{{ repo.forecast_label }}</span>
|
||||
{% endif %}
|
||||
{% if full in stats %}
|
||||
<div class="mini-progress">
|
||||
{% for col in column_keys %}
|
||||
{% set n = stats[full].get(col, 0) %}
|
||||
{% if n > 0 %}
|
||||
<span class="chip" style="background: color-mix(in srgb, {{ COLUMN_COLORS[col] }} 25%, transparent); color: var(--text)">
|
||||
{{ n }}
|
||||
</span>
|
||||
<div class="foot-main">
|
||||
<span class="foot-count">{{ repo.open_issues_count or 0 }} issues ouvertes</span>
|
||||
<a href="/board/{{ full }}" class="card-board-link btn-sm">Board →</a>
|
||||
</div>
|
||||
<div class="foot-dist">
|
||||
{% if full in stats %}
|
||||
<div class="mini-progress" aria-label="Répartition par colonne">
|
||||
{% for col in column_keys %}
|
||||
{% set n = stats[full].get(col, 0) %}
|
||||
{% if n > 0 %}
|
||||
<span class="chip" title="{{ COLUMN_LABELS[col] }}" style="background: color-mix(in srgb, {{ COLUMN_COLORS[col] }} 25%, transparent); color: var(--text)">
|
||||
{{ COLUMN_LABELS[col] }} {{ n }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if repo.forecast_label %}
|
||||
<span class="forecast-line" title="Prévision d'après le débit réel du journal (fermetures par semaine)">{{ repo.forecast_label }}</span>
|
||||
{% endif %}
|
||||
</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
|
||||
Cette semaine : {{ repo.activity.closed }} fermée(s) · {{ repo.activity.moved }} déplacement(s)
|
||||
{% elif repo.activity.idle_days is not none and repo.activity.idle_days > 7 %}
|
||||
aucune activité kanban depuis {{ repo.activity.idle_days }} j
|
||||
{% endif %}
|
||||
|
||||
+64
-13
@@ -315,8 +315,15 @@ a.kpi:hover { background: var(--surface-2); text-decoration: none; }
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.card-header-left, .card-header-actions {
|
||||
display: flex; align-items: center; gap: 8px; min-width: 0;
|
||||
}
|
||||
.card-header-actions { flex-shrink: 0; }
|
||||
.card-header .date { white-space: nowrap; }
|
||||
.card-header .star-btn { margin-left: 0; }
|
||||
.org-tag {
|
||||
font-size: 0.68rem;
|
||||
text-transform: uppercase;
|
||||
@@ -340,20 +347,33 @@ a.kpi:hover { background: var(--surface-2); text-decoration: none; }
|
||||
min-height: 2.4em;
|
||||
}
|
||||
.card-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-2);
|
||||
gap: 10px;
|
||||
display: flex; flex-direction: column; align-items: stretch;
|
||||
font-size: 0.8rem; color: var(--text-2); gap: 10px;
|
||||
border-top: 1px solid var(--border);
|
||||
padding-top: 12px; margin-top: 14px;
|
||||
}
|
||||
.mini-progress { display: flex; gap: 4px; }
|
||||
.foot-main, .foot-dist {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
gap: 10px; flex-wrap: wrap;
|
||||
}
|
||||
.foot-count { font-weight: 600; color: var(--text); }
|
||||
.card-board-link.btn-sm { text-decoration: none; }
|
||||
.mini-progress { display: flex; gap: 4px; flex-wrap: wrap; }
|
||||
.mini-progress .chip { white-space: nowrap; }
|
||||
.forecast-line { white-space: nowrap; }
|
||||
.chip {
|
||||
padding: 2px 9px;
|
||||
border-radius: var(--radius-full);
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.card-progress { margin-bottom: 12px; }
|
||||
.progress-head {
|
||||
display: flex; justify-content: space-between; align-items: baseline;
|
||||
gap: 10px; margin-bottom: 6px;
|
||||
font-size: 0.78rem; color: var(--text-2);
|
||||
}
|
||||
.progress-head span:last-child { font-weight: 600; color: var(--text); white-space: nowrap; }
|
||||
.progress {
|
||||
height: 6px;
|
||||
border-radius: var(--radius-full);
|
||||
@@ -1387,7 +1407,24 @@ body.nav-closed-admin [data-group="admin"] .side-chevron { transform: rotate(-90
|
||||
.theme-toggle-foot:hover { color: var(--text); }
|
||||
.side-foot {
|
||||
margin-top: auto; padding-top: 12px;
|
||||
display: flex; align-items: center; justify-content: space-between; gap: 8px;
|
||||
display: flex; flex-direction: column; align-items: stretch; gap: 8px;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
.side-foot-user { display: flex; align-items: center; gap: 8px; }
|
||||
.side-foot-user .side-profile { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
||||
.side-logout.logout-icon {
|
||||
width: 36px; height: 36px; flex-shrink: 0;
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
border: 1px solid var(--border); border-radius: var(--radius-sm);
|
||||
}
|
||||
.side-logout.logout-icon:hover { border-color: var(--accent, #0d9488); }
|
||||
.side-foot-meta {
|
||||
display: flex; justify-content: space-between; align-items: center; gap: 8px;
|
||||
}
|
||||
.fresh-dot {
|
||||
width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0;
|
||||
background: var(--accent-2); display: inline-block; margin-right: 6px;
|
||||
vertical-align: 1px;
|
||||
}
|
||||
.side-collapse {
|
||||
/* Depuis la 0.22.0 : bouton icône seule dans l'en-tête de la
|
||||
@@ -1403,7 +1440,7 @@ body.nav-closed-admin [data-group="admin"] .side-chevron { transform: rotate(-90
|
||||
color: var(--text); border-color: var(--accent, #0d9488);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.side-fresh { color: var(--text-3); font-size: 0.76rem; white-space: nowrap; }
|
||||
.side-fresh { color: var(--text-3); font-size: 0.76rem; white-space: nowrap; display: inline-flex; align-items: center; }
|
||||
.side-logout { color: var(--text-3); font-size: 0.85rem; white-space: nowrap; }
|
||||
.side-logout:hover { color: var(--text); text-decoration: none; }
|
||||
.side-collapse-ico { display: inline-block; transition: transform 0.18s ease; }
|
||||
@@ -1412,7 +1449,9 @@ body.sidebar-collapsed .side-collapse-ico { transform: rotate(180deg); }
|
||||
body.sidebar-collapsed .side-label { display: none; }
|
||||
body.sidebar-collapsed .side-link { justify-content: center; padding-left: 0; padding-right: 0; }
|
||||
body.sidebar-collapsed .side-logo { justify-content: center; padding-left: 0; padding-right: 0; }
|
||||
body.sidebar-collapsed .side-foot { justify-content: center; }
|
||||
body.sidebar-collapsed .side-foot { align-items: center; }
|
||||
body.sidebar-collapsed .side-foot-meta { display: none; }
|
||||
body.sidebar-collapsed .side-foot-user { justify-content: center; }
|
||||
.side-backdrop {
|
||||
position: fixed; inset: 0; z-index: 190;
|
||||
background: rgba(4, 6, 10, 0.55);
|
||||
@@ -1449,7 +1488,19 @@ body.sidebar-collapsed .side-foot { justify-content: center; }
|
||||
.star-btn:hover { transform: scale(1.2); }
|
||||
.star-btn:disabled { opacity: 0.5; }
|
||||
.card-board-link { font-weight: 600; }
|
||||
.ms-block { margin: 10px 0 2px; }
|
||||
.ms-block {
|
||||
margin: 14px 0 2px; padding-top: 12px;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
.ms-head {
|
||||
display: flex; justify-content: space-between; align-items: baseline;
|
||||
gap: 10px; margin-bottom: 6px;
|
||||
}
|
||||
.ms-kicker {
|
||||
font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.6px;
|
||||
color: var(--text-3); font-weight: 700;
|
||||
}
|
||||
.ms-block .ms-line { margin-bottom: 2px; }
|
||||
.ms-line { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; }
|
||||
.ms-title { font-weight: 600; font-size: 0.92rem; }
|
||||
.ms-due { font-size: 0.8rem; color: var(--text-2); white-space: nowrap; }
|
||||
@@ -1485,7 +1536,7 @@ body.sidebar-collapsed .side-foot { justify-content: center; }
|
||||
.proj-notes-body { white-space: pre-wrap; font-size: 0.93rem; background: var(--surface-2); border-radius: 10px; padding: 12px 14px; }
|
||||
|
||||
/* ── Dashboard vague 2 suite (0.18.0, #19 #21 #22 #23) ── */
|
||||
.activity-line { margin-top: 8px; font-size: 0.82rem; color: var(--text-2); }
|
||||
.activity-line { margin-top: 10px; font-size: 0.78rem; color: var(--text-3); }
|
||||
/* Barre d'outils unique du dashboard (0.20.0) : onglets
|
||||
segmentés, filtre, tri, regroupement et vue dans UN cadre. */
|
||||
.dash-toolbar {
|
||||
|
||||
@@ -19,7 +19,7 @@ def test_activity_line_recent_week(client, fake_gitea):
|
||||
from_column="backlog", to_column="todo",
|
||||
created_at=_ts(0))
|
||||
page = client.get("/")
|
||||
assert "2 fermée(s) · 1 déplacement(s) cette semaine" in page.text
|
||||
assert "Cette semaine : 2 fermée(s) · 1 déplacement(s)" in page.text
|
||||
|
||||
|
||||
def test_activity_line_idle_project(client, fake_gitea):
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
"""Tuile Projets et pied de sidebar : ergonomie refondue.
|
||||
|
||||
Tuile : en-tête groupé, avancement labellisé, jalon étiqueté,
|
||||
pied en 2 rangées avec répartition labellisée par colonne.
|
||||
Pied : Sortir icône seule, fraîcheur en pastille discrète.
|
||||
"""
|
||||
|
||||
|
||||
def test_tuile_structure(client, fake_gitea):
|
||||
page = client.get("/")
|
||||
assert 'class="card-header-left"' in page.text
|
||||
assert 'class="card-header-actions"' in page.text
|
||||
assert 'class="foot-main"' in page.text
|
||||
assert 'class="foot-dist"' in page.text
|
||||
# Répartition, avancement et jalon sont conditionnels (stats /
|
||||
# jalon) : on les vérifie dans le gabarit, pas dans la page de
|
||||
# test sans données.
|
||||
tpl = open("app/templates/dashboard.html", encoding="utf-8").read()
|
||||
assert 'aria-label="Répartition par colonne"' in tpl
|
||||
assert 'class="progress-head"' in tpl
|
||||
assert "Avancement" in tpl
|
||||
assert 'class="ms-kicker">Prochain jalon' in tpl
|
||||
assert "COLUMN_LABELS[col]" in tpl
|
||||
|
||||
|
||||
def test_pied_sidebar_structure():
|
||||
tpl = open("app/templates/_header.html", encoding="utf-8").read()
|
||||
assert 'class="side-foot-user"' in tpl
|
||||
assert 'class="side-foot-meta"' in tpl
|
||||
assert 'class="side-logout logout-icon"' in tpl
|
||||
assert 'aria-label="Se déconnecter"' in tpl
|
||||
assert " Sortir</span>" not in tpl
|
||||
assert 'class="fresh-dot"' in tpl
|
||||
css = open("static/css/style.css", encoding="utf-8").read()
|
||||
assert ".side-foot-meta" in css
|
||||
assert ".fresh-dot" in css
|
||||
assert ".logout-icon" in css
|
||||
Reference in New Issue
Block a user