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) {