feat: v0.5.0→v0.8.0 — multi-vues, filtres, tri, modale, checklists
CI / test (push) Failing after 4s
CI / lint (push) Failing after 3s

v0.5.0 (Kanban complet):
- Card detail modal avec checklists + commentaires Gitea
- Création d'issues inline (formulaire + POST /api/issues)
- Édition inline (contenteditable)

v0.6.0 (Table view):
- table_view.html: colonnes Name/Status/Assign/Deadline/Team/Keywords
- Tri par colonne (clic en-tête, asc/desc)
- Groupes rétractables (Design/Engineering)

v0.7.0 (Filtres & Tri):
- Filtres cumulables AND (backend _apply_filters + UI pastilles)
- Status dropdown avec checkboxes + dots colorés
- Sort panel multi-critères (+ Add sort / Delete sort)
- Backend _apply_sorts avec asc/desc

v0.8.0 (Vues spéciales):
- status_overview.html: SVG donut chart + légende + pourcentages
- team_load.html: barres empilées par avatar
- detailed_board.html: cartes avec toutes propriétés

Backend:
- /board/{o}/{r}/view/{view} — 5 fragments HTMX
- /api/issues/{o}/{r}/{id}?format=html — modal HTML
- _map_issue_to_card, STATUS_COLORS/LABELS exportés
This commit is contained in:
2026-07-08 14:29:33 -04:00
parent ff555c6fd4
commit 751f13869a
10 changed files with 871 additions and 364 deletions
+47 -45
View File
@@ -1,58 +1,60 @@
<div class="board-columns" id="board-columns">
{% for col in columns %}
<div class="board-column" data-column="{{ col }}" id="col-{{ loop.index }}">
<div class="column-header">
<span class="column-title">{{ col }}</span>
<div class="column-header-right">
<span class="column-count">{{ column_map[col]|length }}</span>
<button class="col-config-btn" onclick="toggleColConfig(event, '{{ owner }}','{{ repo }}','{{ col }}')" title="Configurer la colonne">⚙</button>
</div>
<!-- Kanban Board Fragment — loaded via HTMX -->
<div id="view-content" class="kanban-container" x-data="kanbanBoard()">
{% set statuses = [('todo', 'To-do', 'var(--gray)', 0), ('progress', 'In progress', 'var(--blue)', 1), ('done', 'Complete', 'var(--green)', 2)] %}
{% for status, label, color, _ in statuses %}
<div class="kanban-column">
<div class="kanban-column-header">
<span class="col-dot" style="background: {{ color }};"></span>
{{ label }}
<span class="col-count">{{ groups|map(attribute='counts')|map(attribute=status)|sum }}</span>
</div>
<div class="col-config-panel" id="col-config-{{ col|replace(' ', '_') }}" style="display:none">
<div class="col-config-inner">
<label class="form-label">Label Gitea → cette colonne</label>
<select class="form-select" id="col-label-{{ col|replace(' ', '_') }}">
<option value="">— Aucun mapping —</option>
</select>
<div class="col-config-actions">
<button class="btn btn-sm btn-primary" onclick="saveColMapping('{{ owner }}','{{ repo }}','{{ col }}')">Mapper</button>
<button class="btn btn-sm" onclick="deleteColMapping('{{ owner }}','{{ repo }}','{{ col }}')">✕ Supprimer</button>
</div>
</div>
{% for group in groups %}
<div class="kanban-group-header" @click="toggleGroup('{{ group.id }}')">
<span class="group-chevron" :class="{ 'open': !collapsedGroups.includes('{{ group.id }}') }">▶</span>
{{ group.name }}
<span class="group-count">{{ group.counts[status] }}</span>
</div>
<div class="column-cards sortable" data-column="{{ col }}" data-owner="{{ owner }}" data-repo="{{ repo }}">
{% for issue in column_map[col] %}
<div class="card" data-issue-id="{{ issue.number }}" data-column="{{ col }}" draggable="true"
ondblclick="openCardDetail('{{ owner }}','{{ repo }}',{{ issue.number }})">
<div class="card-labels">
{% for lbl in issue.labels %}
<span class="card-label" style="background:#{{ lbl.color or '666' }}33; color:#{{ lbl.color or 'ccc' }}">{{ lbl.name }}</span>
{% endfor %}
</div>
<div class="card-title">
<a href="{{ issue.html_url }}" target="_blank" onclick="event.stopPropagation()">#{{ issue.number }}</a>
<span class="card-title-text">{{ issue.title[:80] }}{% if issue.title|length > 80 %}...{% endif %}</span>
<div class="kanban-cards" x-show="!collapsedGroups.includes('{{ group.id }}')"
data-group="{{ group.id }}" data-status="{{ status }}"
id="kanban-{{ group.id }}-{{ status }}">
{% for card in group.cards if card.status == status %}
<div class="kanban-card card-status-{{ card.status }}"
data-card-id="{{ card.id }}"
@click="openCardDetail('{{ card.id }}')">
<div class="card-header">
<span class="card-icon">{{ card.icon }}</span>
<span class="card-title">{{ card.title }}</span>
</div>
<div class="card-meta">
{% if issue.assignee %}
<span class="card-assignee" title="{{ issue.assignee.login }}">
<img src="{{ issue.assignee.avatar_url }}" alt="" width="16" height="16" class="avatar">
{{ issue.assignee.login }}
</span>
{% if card.assignee %}
<div class="card-assignee">
<div class="card-avatar" title="{{ card.assignee }}">{{ card.assignee[0] }}</div>
<span>{{ card.assignee }}</span>
</div>
{% endif %}
{% if issue.milestone %}
<span class="card-milestone">📅 {{ issue.milestone.title }}</span>
{% if card.tag %}
<span class="card-tag" style="background: {{ card.tag_color }}20; color: {{ card.tag_color }};">{{ card.tag }}</span>
{% endif %}
{% if card.due_date %}
<div class="card-date"><span>📅</span> {{ card.due_date }}</div>
{% endif %}
</div>
{% if issue._card and issue._card.priority %}
<div class="card-priority priority-{{ issue._card.priority|lower }}">{{ issue._card.priority }}</div>
{% endif %}
{% if issue._card and issue._card.due_date %}
<div class="card-due {% if issue._card.due_date < now %}overdue{% endif %}">⏰ {{ issue._card.due_date[:10] }}</div>
{% endif %}
</div>
{% endfor %}
<button class="kanban-new-card" @click="newCard('{{ group.id }}', '{{ status }}')">+ New page</button>
</div>
{% endfor %}
<button class="kanban-new-group" @click="newGroup()" style="margin-top: 12px;">+ New group</button>
</div>
{% endfor %}
</div>
<script>
function openCardDetail(id) {
htmx.ajax('GET', `/api/issues/${owner}/${repo}/${id}?format=html`, {
target: '#card-modal-content', swap: 'innerHTML'
});
document.getElementById('card-modal').style.display = 'flex';
document.getElementById('card-modal-title').textContent = 'Issue #' + id;
}
</script>