diff --git a/modules/planka/api.php b/modules/planka/api.php index b5d58a1..06dce12 100644 --- a/modules/planka/api.php +++ b/modules/planka/api.php @@ -96,6 +96,18 @@ if ($action === 'set_active_board') { tOk(['updated' => true]); } +if ($action === 'create_list') { + $board_id = $_GET['board_id'] ?? null; if (!$board_id) tErr('board_id manquant'); + $name = trim($_GET['name'] ?? ''); if (!$name) tErr('name manquant'); + $position = (int) ($_GET['position'] ?? 65535); + $resp = planka_api('POST', '/api/boards/' . $board_id . '/lists', [ + 'name' => $name, + 'position' => $position, + ], $token); + if (empty($resp['item'])) tErr($resp['message'] ?? 'Planka n\'a pas créé la liste', 502); + tOk($resp['item']); +} + if ($action === 'create_card') { $list_id = $_GET['list_id'] ?? null; if (!$list_id) tErr('list_id manquant'); $board_id = $_GET['board_id'] ?? null; if (!$board_id) tErr('board_id manquant'); diff --git a/modules/planka/assets/planka.css b/modules/planka/assets/planka.css index 0e418f4..eb8ab33 100644 --- a/modules/planka/assets/planka.css +++ b/modules/planka/assets/planka.css @@ -463,3 +463,28 @@ border-radius: 8px; margin-bottom: .35rem; } + +.pk-list-new { + background: transparent; + border: 2px dashed var(--border-light, #e5e7eb); + display: flex; + align-items: flex-start; + justify-content: center; + padding: .75rem .5rem; +} +.pk-add-list-btn { + background: none; + border: none; + cursor: pointer; + color: var(--text-muted, #9ca3af); + font-size: .9rem; + font-weight: 600; + padding: .4rem .75rem; + border-radius: 8px; + transition: background .15s, color .15s; + white-space: nowrap; +} +.pk-add-list-btn:hover { + background: var(--bg-panel, #f3f4f6); + color: var(--primary, #4361ee); +} diff --git a/planka.php b/planka.php index 6b9e6d0..4af8c94 100644 --- a/planka.php +++ b/planka.php @@ -94,7 +94,6 @@ require __DIR__ . '/header.php'; const API = 'modules/planka/api.php'; let state = { boards: [], activeBoardId: null, lists: [], cards: [], labels: [], cardLabels: [], tasks: [] }; let editingCard = null; -let dragCardId = null; async function apiFetch(action, params = {}, method = 'GET', body = null) { const qs = new URLSearchParams({ action, ...params }).toString(); @@ -146,19 +145,30 @@ async function loadBoard(id) { function renderBoard() { const area = document.getElementById('pk-board-area'); - if (!state.lists.length) { area.innerHTML = '
Aucune liste dans ce board.
'; return; } const sorted = [...state.lists].sort((a, b) => (a.position ?? 0) - (b.position ?? 0)); - area.innerHTML = sorted.map(list => renderList(list)).join(''); + area.innerHTML = sorted.map(list => renderList(list)).join('') + ` +
+ +
`; area.querySelectorAll('.pk-card').forEach(el => { el.addEventListener('click', () => openCardModal(el.dataset.id)); - el.addEventListener('dragstart', e => { dragCardId = el.dataset.id; el.classList.add('is-dragging'); e.stopPropagation(); }); - el.addEventListener('dragend', e => { el.classList.remove('is-dragging'); dragCardId = null; }); + el.addEventListener('dragstart', e => { + e.dataTransfer.setData('text/plain', el.dataset.id); + e.dataTransfer.effectAllowed = 'move'; + el.classList.add('is-dragging'); + }); + el.addEventListener('dragend', e => { el.classList.remove('is-dragging'); }); }); - // Drop target = toute la colonne .pk-cards (pas juste une zone invisible de 4px) + // Drop target = toute la colonne .pk-cards area.querySelectorAll('.pk-cards').forEach(el => { - el.addEventListener('dragover', e => { e.preventDefault(); el.classList.add('drag-over'); }); + el.addEventListener('dragover', e => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; el.classList.add('drag-over'); }); el.addEventListener('dragleave', e => { if (!el.contains(e.relatedTarget)) el.classList.remove('drag-over'); }); - el.addEventListener('drop', e => { e.preventDefault(); el.classList.remove('drag-over'); dropCard(el.dataset.listId); }); + el.addEventListener('drop', e => { + e.preventDefault(); + el.classList.remove('drag-over'); + const cardId = e.dataTransfer.getData('text/plain'); + if (cardId) dropCard(el.dataset.listId, cardId); + }); }); } @@ -201,20 +211,19 @@ function renderCard(card) { `; } -async function dropCard(listId) { - if (!dragCardId) return; - const card = state.cards.find(c => c.id === dragCardId); +async function dropCard(listId, cardId) { + const card = state.cards.find(c => c.id === cardId); if (!card || card.listId === listId) return; // Planka requires position when changing listId const targetCards = state.cards.filter(c => c.listId === listId); const maxPos = targetCards.reduce((m, c) => Math.max(m, c.position || 0), 0); const position = maxPos + 65535; - const res = await apiFetch('update_card', { id: dragCardId }, 'PATCH', { listId, position }); - if (!res.ok) { showToast('Erreur déplacement : ' + (res.error || 'inconnu'), 'error'); return; } + const res = await apiFetch('update_card', { id: cardId }, 'PATCH', { listId, position }); + if (!res.ok) { showToast('Erreur : ' + (res.error || 'déplacement impossible'), 'error'); return; } card.listId = listId; card.position = position; renderBoard(); - showToast('Carte déplacée'); + showToast('Carte déplacée ✓'); } async function quickAddCard(listId) { @@ -327,6 +336,17 @@ async function saveSettings() { setTimeout(() => init(), 800); } +async function promptCreateList() { + const name = prompt('Nom de la nouvelle liste :'); + if (!name || !name.trim()) return; + const maxPos = state.lists.reduce((m, l) => Math.max(m, l.position || 0), 0); + const res = await apiFetch('create_list', { board_id: state.activeBoardId, name: name.trim(), position: maxPos + 65535 }); + if (!res.ok) { showToast('Erreur : ' + (res.error || 'création impossible'), 'error'); return; } + state.lists.push(res.data); + renderBoard(); + showToast('Liste créée ✓'); +} + function esc(s) { const d = document.createElement('div'); d.textContent = s || ''; return d.innerHTML; } function formatDate(iso) {