settings = $settings; $this->ensureDefaultBoards(); $this->tab = new Container('flex flex-col p-4 gap-4 bg-gray-50'); $headerRow = new Container('flex flex-row items-center gap-2'); $headerRow->addComponent(new Label('Kanban Tasks', 'text-xl font-bold text-black flex-1')); $this->newBoardInput = new TextInput( 'Neues Board...', 'w-60 border border-gray-300 rounded px-3 py-2 bg-white text-black text-sm', ); $addBoardButton = new Button( 'Board hinzufügen', 'px-3 py-2 bg-blue-600 rounded hover:bg-blue-700', null, 'text-white text-sm', ); $headerRow->addComponent($this->newBoardInput); $headerRow->addComponent($addBoardButton); $this->tab->addComponent($headerRow); $this->boardsContainer = new Container( 'flex flex-row gap-4 flex-1 overflow-auto bg-gray-100 rounded border border-gray-300 p-3', ); $this->tab->addComponent($this->boardsContainer); $kanbanTab = $this; $addBoardButton->setOnClick(function () use ($kanbanTab) { $name = trim($kanbanTab->newBoardInput->getValue()); if ($name === '') { return; } $boards = $kanbanTab->settings->get('kanban.boards', []); if (!is_array($boards)) { $boards = []; } if (!in_array($name, $boards, true)) { $boards[] = $name; $kanbanTab->settings->set('kanban.boards', $boards); $kanbanTab->settings->save(); } $kanbanTab->newBoardInput->setValue(''); $kanbanTab->renderBoards(); }); // Edit modal for tasks $this->createEditModal(); $this->renderBoards(); } public function getContainer(): Container { return $this->tab; } public function refresh(): void { $this->renderBoards(); } private function ensureDefaultBoards(): void { $boards = $this->settings->get('kanban.boards', null); if (!is_array($boards) || empty($boards)) { $boards = ['neu', 'in arbeit', 'fertig']; $this->settings->set('kanban.boards', $boards); $this->settings->save(); } } private function rebuildBoardButtons(): void { if (!isset($this->editBoardButtonsContainer)) { return; } $this->editBoardButtonsContainer->clearChildren(); $boards = $this->settings->get('kanban.boards', []); if (!is_array($boards) || empty($boards)) { $boards = ['neu', 'in arbeit', 'fertig']; } foreach ($boards as $boardName) { $isSelected = ($boardName === $this->currentEditingBoard); $style = 'px-2 py-1 rounded text-xs border '; if ($isSelected) { $style .= 'bg-blue-600 text-white border-blue-600'; } else { $style .= 'bg-white text-gray-700 border-gray-300 hover:bg-gray-100'; } $button = new Button($boardName, $style); $kanbanTab = $this; $button->setOnClick(function () use ($kanbanTab, $boardName) { $kanbanTab->currentEditingBoard = $boardName; $kanbanTab->rebuildBoardButtons(); }); $this->editBoardButtonsContainer->addComponent($button); } } private function createEditModal(): void { $content = new Container('flex flex-col bg-white rounded-lg shadow-xl p-4 gap-3 w-[480] max-h-[420]'); $content->setUseTextureCache(false); $content->addComponent(new Label('Task bearbeiten', 'text-lg font-bold text-black')); // Title $content->addComponent(new Label('Titel', 'text-xs text-gray-600')); $this->editTitleInput = new TextInput( 'Titel', 'w-full border border-gray-300 rounded px-3 py-2 bg-white text-black text-sm', ); $content->addComponent($this->editTitleInput); // Details $content->addComponent(new Label('Details', 'text-xs text-gray-600')); $this->editDetailsArea = new TextArea( '', 'Details...', 'flex-1 border border-gray-300 rounded px-3 py-2 bg-white text-black text-xs', ); $this->editDetailsArea->setUseTextureCache(false); $content->addComponent($this->editDetailsArea); // Board selection (selectbox-artig) $content->addComponent(new Label('Board', 'text-xs text-gray-600')); $this->editBoardButtonsContainer = new Container('flex flex-row flex-wrap gap-1'); $this->rebuildBoardButtons(); $content->addComponent($this->editBoardButtonsContainer); // Buttons $buttonRow = new Container('flex flex-row justify-end gap-2 mt-2'); $cancelButton = new Button( 'Abbrechen', 'px-3 py-2 bg-gray-300 rounded hover:bg-gray-400', null, 'text-black text-sm', ); $saveButton = new Button( 'Speichern', 'px-3 py-2 bg-green-600 rounded hover:bg-green-700', null, 'text-white text-sm', ); $kanbanTab = $this; $cancelButton->setOnClick(function () use ($kanbanTab) { $kanbanTab->editModal->setVisible(false); }); $saveButton->setOnClick(function () use ($kanbanTab) { $kanbanTab->saveEditedTask(); }); $buttonRow->addComponent($cancelButton); $buttonRow->addComponent($saveButton); $content->addComponent($buttonRow); $this->editModal = new Modal($content); $this->tab->addComponent($this->editModal); } private function openEditModal(array $task): void { $this->currentEditingTaskId = $task['id'] ?? null; if ($this->currentEditingTaskId === null) { return; } $title = (string) ($task['title'] ?? ''); $details = (string) ($task['details'] ?? ''); $board = (string) ($task['board'] ?? 'neu'); $this->editTitleInput->setValue($title); $this->editDetailsArea->setValue($details); $this->currentEditingBoard = $board; $this->rebuildBoardButtons(); $this->editModal->setVisible(true); } private function saveEditedTask(): void { if ($this->currentEditingTaskId === null) { $this->editModal->setVisible(false); return; } $title = trim($this->editTitleInput->getValue()); $details = trim($this->editDetailsArea->getValue()); $board = trim($this->currentEditingBoard); if ($title === '') { $title = 'Ohne Titel'; } if ($board === '') { $board = 'neu'; } $tasks = $this->settings->get('kanban.tasks', []); if (!is_array($tasks)) { $tasks = []; } foreach ($tasks as &$task) { if (($task['id'] ?? null) === $this->currentEditingTaskId) { $task['title'] = $title; $task['details'] = $details; $task['board'] = $board; break; } } unset($task); // Ensure board exists $boards = $this->settings->get('kanban.boards', []); if (!is_array($boards)) { $boards = []; } if (!in_array($board, $boards, true)) { $boards[] = $board; $this->settings->set('kanban.boards', $boards); } $this->settings->set('kanban.tasks', $tasks); $this->settings->save(); $this->editModal->setVisible(false); $this->renderBoards(); } private function renderBoards(): void { $this->boardsContainer->clearChildren(); $boards = $this->settings->get('kanban.boards', []); if (!is_array($boards)) { $boards = []; } $tasks = $this->settings->get('kanban.tasks', []); if (!is_array($tasks)) { $tasks = []; } foreach ($boards as $boardName) { $column = new Container('flex flex-col bg-white rounded shadow-md w-64 max-h-full'); $columnHeader = new Container('px-3 py-2 border-b border-gray-300 bg-gray-100'); $columnHeader->addComponent(new Label($boardName, 'text-sm font-semibold text-gray-800')); $column->addComponent($columnHeader); $columnBody = new Container('flex flex-col gap-2 p-2 overflow-auto'); $boardTasks = array_values(array_filter( $tasks, static fn($task) => ($task['board'] ?? 'neu') === $boardName, )); if (empty($boardTasks)) { $columnBody->addComponent(new Label('Keine Tasks', 'text-xs text-gray-400 italic')); } else { foreach ($boardTasks as $task) { $title = (string) ($task['title'] ?? ''); $serverId = $task['server_id'] ?? null; $serverLabel = $serverId !== null ? ('Server #' . $serverId) : 'Kein Server'; $taskId = $task['id'] ?? null; $card = new Container( 'flex flex-col gap-1 px-3 py-2 bg-white border border-gray-200 rounded shadow-sm', ); // Header row with title and action icons $headerRow = new Container('flex flex-row items-center gap-1'); $headerRow->addComponent(new Label($title, 'text-xs text-gray-900 flex-1')); // Edit icon button $editButton = new Button( '', 'p-1 rounded hover:bg-blue-100', null, 'text-blue-600', ); $editIcon = new Icon(IconName::edit, 12, 'text-blue-600'); $editButton->setIcon($editIcon); $kanbanTab = $this; $editButton->setOnClick(function () use ($kanbanTab, $task) { $kanbanTab->openEditModal($task); }); // Delete icon button $deleteButton = new Button( '', 'p-1 rounded hover:bg-red-100', null, 'text-red-600', ); $deleteIcon = new Icon(IconName::trash, 12, 'text-red-600'); $deleteButton->setIcon($deleteIcon); $deleteButton->setOnClick(function () use ($kanbanTab, $taskId) { if ($taskId === null) { return; } $tasks = $kanbanTab->settings->get('kanban.tasks', []); if (!is_array($tasks)) { return; } $tasks = array_values(array_filter( $tasks, static fn($t) => ($t['id'] ?? null) !== $taskId, )); $kanbanTab->settings->set('kanban.tasks', $tasks); $kanbanTab->settings->save(); $kanbanTab->renderBoards(); }); $headerRow->addComponent($editButton); $headerRow->addComponent($deleteButton); $card->addComponent($headerRow); // Server label $card->addComponent(new Label($serverLabel, 'text-[10px] text-gray-500')); $columnBody->addComponent($card); } } $column->addComponent($columnBody); $this->boardsContainer->addComponent($column); } } }