402 lines
14 KiB
PHP
402 lines
14 KiB
PHP
<?php
|
|
|
|
namespace ServerManager\UI;
|
|
|
|
use PHPNative\Framework\Settings;
|
|
use PHPNative\Tailwind\Data\Icon as IconName;
|
|
use PHPNative\Ui\Widget\Button;
|
|
use PHPNative\Ui\Widget\Container;
|
|
use PHPNative\Ui\Widget\Icon;
|
|
use PHPNative\Ui\Widget\Label;
|
|
use PHPNative\Ui\Widget\Modal;
|
|
use PHPNative\Ui\Widget\TextArea;
|
|
use PHPNative\Ui\Widget\TextInput;
|
|
|
|
class KanbanTab
|
|
{
|
|
private Container $tab;
|
|
private Settings $settings;
|
|
private Container $boardsContainer;
|
|
private TextInput $newBoardInput;
|
|
private Modal $editModal;
|
|
private TextInput $editTitleInput;
|
|
private TextArea $editDetailsArea;
|
|
private Container $editBoardButtonsContainer;
|
|
private string $currentEditingBoard = 'neu';
|
|
private null|string $currentEditingTaskId = null;
|
|
private null|ServerListTab $serverListTab = null;
|
|
|
|
public function __construct(Settings $settings)
|
|
{
|
|
$this->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 setServerListTab(ServerListTab $serverListTab): void
|
|
{
|
|
$this->serverListTab = $serverListTab;
|
|
}
|
|
|
|
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();
|
|
|
|
if ($this->serverListTab !== null) {
|
|
$this->serverListTab->refreshCurrentServerTasks();
|
|
}
|
|
}
|
|
|
|
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 = [];
|
|
}
|
|
|
|
// Map server IDs to names if the server list tab is available
|
|
$serverNames = [];
|
|
if ($this->serverListTab !== null) {
|
|
foreach ($this->serverListTab->currentServerData as $row) {
|
|
$id = $row['id'] ?? null;
|
|
$name = $row['name'] ?? null;
|
|
if ($id !== null && $name !== null) {
|
|
$serverNames[(int) $id] = (string) $name;
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach ($boards as $boardName) {
|
|
$column = new Container('flex flex-col bg-white rounded shadow-md w-128 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;
|
|
$serverName = null;
|
|
if ($serverId !== null && isset($serverNames[(int) $serverId])) {
|
|
$serverName = $serverNames[(int) $serverId];
|
|
}
|
|
if ($serverName !== null) {
|
|
$serverLabel = $serverName . ' (#' . $serverId . ')';
|
|
} else {
|
|
$serverLabel = $serverId !== null ? ('Server #' . $serverId) : 'Kein Server';
|
|
}
|
|
$taskId = $task['id'] ?? null;
|
|
|
|
$card = new Container(
|
|
'flex flex-col gap-1 px-3 py-2 bg-lime-100 border border-red-500 rounded-sm shadow-lg',
|
|
);
|
|
|
|
// 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();
|
|
|
|
if ($kanbanTab->serverListTab !== null) {
|
|
$kanbanTab->serverListTab->refreshCurrentServerTasks();
|
|
}
|
|
});
|
|
|
|
$headerRow->addComponent($editButton);
|
|
$headerRow->addComponent($deleteButton);
|
|
$card->addComponent($headerRow);
|
|
|
|
// Server label
|
|
$card->addComponent(new Label($serverLabel, 'text-sm text-gray-500'));
|
|
|
|
$columnBody->addComponent($card);
|
|
}
|
|
}
|
|
|
|
$column->addComponent($columnBody);
|
|
$this->boardsContainer->addComponent($column);
|
|
}
|
|
}
|
|
}
|