- Layout 3 zones (sidebar 240px + topbar 44px + contenu) - Sidebar: sections hiérarchiques, hover states, menu contextuel - CSS design system Notion: #191919/#222/#333, Inter, coins 4-6px - Vues: Kanban (groupes+colonnes), Dashboard Notion-style - Templates: base.html refondu, card.html partial, board.html v2 - Backend: données sidebar + board Notion (groups/cards) - Docs: ROADMAP v0.4→v1.0, WORKLOAD.md, README - Backward compatible: conserve _issue_column pour api.py - Images de référence + resized (analyse 2026-07-08)
230 lines
7.9 KiB
HTML
230 lines
7.9 KiB
HTML
{% extends "base.html" %}
|
|
{% block page_icon %}📁{% endblock %}
|
|
{% block page_title %}Kanban board new{% endblock %}
|
|
|
|
{% block content %}
|
|
<!-- Cover image -->
|
|
<div class="page-cover">
|
|
<div class="page-cover-actions">
|
|
<button class="cover-btn">Change</button>
|
|
<button class="cover-btn">Reposition</button>
|
|
<button class="cover-btn">↓</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Page title -->
|
|
<div class="page-title-area">
|
|
<div class="page-title">
|
|
<span class="page-icon-lg">📁</span>
|
|
<h1>Kanban board new</h1>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Description -->
|
|
<div class="page-description" x-data="{ hideDesc: false }">
|
|
<template x-if="!hideDesc">
|
|
<div>
|
|
<p>Use this Kanban board to track tasks across your project. Group by team, filter by status, and sort by priority.</p>
|
|
<p style="margin-top: 8px;">
|
|
<strong>Properties</strong> like Status, Assignee, and Deadline are synced with Gitea issues.<br>
|
|
Enable <strong>Sub-groups</strong> to organize by team (Design, Engineering, etc.).
|
|
</p>
|
|
</div>
|
|
</template>
|
|
<button class="text-xs text-dim" @click="hideDesc = !hideDesc" style="margin-top: 4px;">
|
|
<span x-text="hideDesc ? 'Show description ▾' : 'Hide description ▴'"></span>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- View tabs -->
|
|
<div class="view-tabs">
|
|
<button class="view-tab active" @click="switchView('kanban')">
|
|
<span class="tab-icon">📊</span> Kanban board
|
|
</button>
|
|
<button class="view-tab" @click="switchView('detailed')">
|
|
<span class="tab-icon">📋</span> Detailed board
|
|
</button>
|
|
<button class="view-tab" @click="switchView('table')">
|
|
<span class="tab-icon">☰</span> Table view
|
|
</button>
|
|
<button class="view-tab" @click="switchView('status')">
|
|
<span class="tab-icon">📈</span> Status overview
|
|
</button>
|
|
<button class="view-tab" @click="switchView('teamload')">
|
|
<span class="tab-icon">👥</span> Team Load
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Toolbar -->
|
|
<div class="view-toolbar">
|
|
<!-- Filter bar -->
|
|
<div class="filter-bar" x-data="filterSystem()">
|
|
<template x-for="(f, i) in activeFilters" :key="i">
|
|
<div class="filter-pill">
|
|
<span x-text="f.property + ': ' + f.value"></span>
|
|
<button class="remove-filter" @click="removeFilter(i)">✕</button>
|
|
</div>
|
|
</template>
|
|
<div class="filter-pill" style="cursor:pointer;" @click="openFilterPanel()">
|
|
<span>Status: </span>
|
|
<span class="text-dim" x-text="statusFilter || 'All'"></span>
|
|
<span style="font-size:10px;">▾</span>
|
|
</div>
|
|
<button class="filter-add-btn" @click="addFilter()">+ Filter</button>
|
|
<button class="filter-reset-btn" @click="resetFilters()" x-show="activeFilters.length > 0">Reset</button>
|
|
</div>
|
|
|
|
<div class="view-toolbar-spacer"></div>
|
|
|
|
<button class="toolbar-btn" title="Sort">⇅</button>
|
|
<button class="toolbar-btn" title="Properties">⚡</button>
|
|
<button class="toolbar-btn" title="Search">🔍</button>
|
|
<button class="toolbar-btn">⋯</button>
|
|
<button class="btn-new">New ▾</button>
|
|
</div>
|
|
|
|
<!-- Kanban Board -->
|
|
<div class="kanban-container" x-data="kanbanBoard()" id="kanban-root">
|
|
<!-- To-do Column -->
|
|
<div class="kanban-column">
|
|
<div class="kanban-column-header">
|
|
<span class="col-dot" style="background: var(--gray);"></span>
|
|
To-do <span class="col-count">1</span>
|
|
</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.todo }}</span>
|
|
</div>
|
|
<div class="kanban-cards" x-show="!collapsedGroups.includes('{{ group.id }}')"
|
|
data-group="{{ group.id }}" data-status="todo"
|
|
id="kanban-{{ group.id }}-todo">
|
|
{% for card in group.cards if card.status == 'todo' %}
|
|
{% include "card.html" %}
|
|
{% endfor %}
|
|
<button class="kanban-new-card" @click="newCard('{{ group.id }}', 'todo')">+ New page</button>
|
|
</div>
|
|
{% endfor %}
|
|
<button class="kanban-new-group" @click="newGroup()" style="margin-top: 16px;">+ New group</button>
|
|
</div>
|
|
|
|
<!-- In Progress Column -->
|
|
<div class="kanban-column">
|
|
<div class="kanban-column-header">
|
|
<span class="col-dot" style="background: var(--blue);"></span>
|
|
In progress <span class="col-count">3</span>
|
|
</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.progress }}</span>
|
|
</div>
|
|
<div class="kanban-cards" x-show="!collapsedGroups.includes('{{ group.id }}')"
|
|
data-group="{{ group.id }}" data-status="progress"
|
|
id="kanban-{{ group.id }}-progress">
|
|
{% for card in group.cards if card.status == 'progress' %}
|
|
{% include "card.html" %}
|
|
{% endfor %}
|
|
<button class="kanban-new-card" @click="newCard('{{ group.id }}', 'progress')">+ New page</button>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<!-- Complete Column -->
|
|
<div class="kanban-column">
|
|
<div class="kanban-column-header">
|
|
<span class="col-dot" style="background: var(--green);"></span>
|
|
Complete <span class="col-count">1</span>
|
|
</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.done }}</span>
|
|
</div>
|
|
<div class="kanban-cards" x-show="!collapsedGroups.includes('{{ group.id }}')"
|
|
data-group="{{ group.id }}" data-status="done"
|
|
id="kanban-{{ group.id }}-done">
|
|
{% for card in group.cards if card.status == 'done' %}
|
|
{% include "card.html" %}
|
|
{% endfor %}
|
|
<button class="kanban-new-card" @click="newCard('{{ group.id }}', 'done')">+ New page</button>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
function switchView(view) {
|
|
const tabs = document.querySelectorAll('.view-tab');
|
|
tabs.forEach(t => t.classList.remove('active'));
|
|
event.target.classList.add('active');
|
|
|
|
// TODO: Load view content via HTMX
|
|
console.log('Switch to view:', view);
|
|
}
|
|
|
|
function kanbanBoard() {
|
|
return {
|
|
collapsedGroups: [],
|
|
toggleGroup(id) {
|
|
const idx = this.collapsedGroups.indexOf(id);
|
|
if (idx >= 0) this.collapsedGroups.splice(idx, 1);
|
|
else this.collapsedGroups.push(id);
|
|
},
|
|
newCard(groupId, status) {
|
|
console.log('New card in', groupId, status);
|
|
// TODO: Create issue in Gitea
|
|
},
|
|
newGroup() {
|
|
console.log('New group');
|
|
// TODO: Create new group
|
|
}
|
|
};
|
|
}
|
|
|
|
function filterSystem() {
|
|
return {
|
|
activeFilters: [],
|
|
statusFilter: 'All',
|
|
openFilterPanel() {
|
|
console.log('Open filter panel');
|
|
},
|
|
addFilter() {
|
|
console.log('Add filter');
|
|
},
|
|
removeFilter(i) {
|
|
this.activeFilters.splice(i, 1);
|
|
},
|
|
resetFilters() {
|
|
this.activeFilters = [];
|
|
}
|
|
};
|
|
}
|
|
|
|
// Initialize SortableJS
|
|
document.addEventListener('alpine:initialized', () => {
|
|
document.querySelectorAll('.kanban-cards').forEach(el => {
|
|
new Sortable(el, {
|
|
group: 'kanban',
|
|
animation: 200,
|
|
ghostClass: 'sortable-ghost',
|
|
dragClass: 'sortable-drag',
|
|
onEnd: function(evt) {
|
|
const cardId = evt.item.dataset.cardId;
|
|
const fromCol = evt.from.dataset.status;
|
|
const toCol = evt.to.dataset.status;
|
|
const group = evt.to.dataset.group;
|
|
console.log('Moved card', cardId, 'from', fromCol, 'to', toCol, 'group', group);
|
|
// TODO: POST /api/move
|
|
}
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
{% endblock %}
|