feat: Refonte UI Notion-complete — sidebar, topbar, multi-vues, dark mode
CI / test (push) Failing after 10s
CI / lint (push) Failing after 4s

- 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)
This commit is contained in:
2026-07-08 14:13:41 -04:00
parent d0818f48de
commit cc680a46c2
12 changed files with 2601 additions and 1018 deletions
+122 -11
View File
@@ -1,12 +1,123 @@
// FlowDeck — Theme toggle
(function() {
const saved = localStorage.getItem('flowdeck-theme');
if (saved) document.documentElement.setAttribute('data-theme', saved);
})();
// FlowDeck Notion UI — Client-side logic
// Alpine.js + SortableJS + HTMX
function toggleTheme() {
const current = document.documentElement.getAttribute('data-theme');
const next = current === 'light' ? 'dark' : 'light';
document.documentElement.setAttribute('data-theme', next);
localStorage.setItem('flowdeck-theme', next);
}
(function() {
'use strict';
// Theme persistence
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
document.documentElement.setAttribute('data-theme', savedTheme);
}
// Close context menu on click outside
document.addEventListener('click', function(e) {
const menu = document.getElementById('context-menu');
if (menu && menu.classList.contains('visible')) {
if (!menu.contains(e.target)) {
menu.classList.remove('visible');
}
}
});
// Close context menu on escape
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
const menu = document.getElementById('context-menu');
if (menu) menu.classList.remove('visible');
}
});
// Tooltip system
document.addEventListener('mouseover', function(e) {
const target = e.target.closest('[data-tooltip]');
if (!target) return;
const tip = document.getElementById('tooltip');
if (!tip) return;
tip.textContent = target.getAttribute('data-tooltip');
tip.style.display = 'block';
const rect = target.getBoundingClientRect();
tip.style.left = rect.left + 'px';
tip.style.top = (rect.bottom + 4) + 'px';
});
document.addEventListener('mouseout', function(e) {
const target = e.target.closest('[data-tooltip]');
if (!target) return;
const tip = document.getElementById('tooltip');
if (tip) tip.style.display = 'none';
});
// Keyboard shortcuts
document.addEventListener('keydown', function(e) {
// Ctrl+O: New chat (placeholder)
if (e.ctrlKey && e.key === 'o') {
e.preventDefault();
console.log('New chat shortcut');
}
});
// Modal functions
window.openModal = function(title) {
const modal = document.getElementById('card-modal');
const titleEl = document.getElementById('card-modal-title');
if (modal && titleEl) {
titleEl.textContent = title || 'Détail';
modal.style.display = 'flex';
}
};
window.closeModal = function() {
const modal = document.getElementById('card-modal');
if (modal) modal.style.display = 'none';
};
// Close modal on overlay click
document.addEventListener('click', function(e) {
if (e.target.id === 'card-modal') {
closeModal();
}
});
// HTMX event handlers
document.addEventListener('htmx:afterSwap', function(evt) {
// Reinitialize Sortable after HTMX swaps
if (evt.target.id === 'kanban-root' || evt.target.closest('.kanban-container')) {
initSortable();
}
});
// Initialize SortableJS on kanban columns
function initSortable() {
document.querySelectorAll('.kanban-cards').forEach(function(el) {
if (el._sortable) return; // Already initialized
el._sortable = 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: Send move to API
// htmx.ajax('POST', '/api/move', { ... });
}
});
});
}
// Init on page load
document.addEventListener('DOMContentLoaded', function() {
initSortable();
});
// Re-init when Alpine initializes
document.addEventListener('alpine:initialized', function() {
setTimeout(initSortable, 100);
});
})();