bp/app/Modules/Tasks/Controllers/TasksController.php

438 lines
16 KiB
PHP

<?php
namespace App\Modules\Tasks\Controllers;
use App\Controllers\BaseController;
use App\Modules\Tasks\Services\TaskService;
use App\Modules\Tasks\Services\TaskBoardService;
use App\Models\OrganizationUserModel;
class TasksController extends BaseController
{
protected TaskService $taskService;
protected TaskBoardService $boardService;
public function __construct()
{
$this->taskService = new TaskService();
$this->boardService = new TaskBoardService();
}
/**
* Главная страница - список задач (использует DataTable)
*/
public function index()
{
$organizationId = $this->requireActiveOrg();
return $this->renderTwig('@Tasks/tasks/index', [
'title' => 'Задачи',
'tableHtml' => $this->renderTable($this->getTableConfig()),
'stats' => $this->taskService->getStats($organizationId),
'boards' => $this->boardService->getOrganizationBoards($organizationId),
]);
}
/**
* AJAX endpoint для таблицы задач
*/
public function table(?array $config = null, ?string $pageUrl = null)
{
return parent::table($this->getTableConfig(), '/tasks');
}
/**
* Конфигурация таблицы задач для DataTable
*/
protected function getTableConfig(): array
{
$organizationId = $this->getActiveOrgId();
return [
'id' => 'tasks-table',
'url' => '/tasks/table',
'model' => $this->taskService->getModel(),
'columns' => [
'title' => [
'label' => 'Задача',
'width' => '35%',
],
'column_name' => [
'label' => 'Статус',
'width' => '15%',
],
'priority' => [
'label' => 'Приоритет',
'width' => '10%',
],
'due_date' => [
'label' => 'Срок',
'width' => '10%',
],
'created_by_name' => [
'label' => 'Автор',
'width' => '15%',
],
],
'searchable' => ['title', 'column_name', 'created_by_name'],
'sortable' => ['title', 'priority', 'due_date', 'created_at', 'column_name'],
'defaultSort' => 'created_at',
'order' => 'desc',
'actions' => ['label' => '', 'width' => '15%'],
'actionsConfig' => [
[
'label' => '',
'url' => '/tasks/{id}',
'icon' => 'fa-solid fa-eye',
'class' => 'btn-outline-primary btn-sm',
'title' => 'Просмотр',
],
[
'label' => '',
'url' => '/tasks/{id}/edit',
'icon' => 'fa-solid fa-pen',
'class' => 'btn-outline-primary btn-sm',
'title' => 'Редактировать',
'type' => 'edit',
],
[
'label' => '',
'url' => '/tasks/{id}/delete',
'icon' => 'fa-solid fa-trash',
'class' => 'btn-outline-danger btn-sm',
'title' => 'Удалить',
'type' => 'delete',
],
],
'emptyMessage' => 'Задач пока нет',
'emptyIcon' => 'fa-solid fa-check-square',
'emptyActionUrl' => '/tasks/new',
'emptyActionLabel' => 'Создать задачу',
'emptyActionIcon' => 'fa-solid fa-plus',
'can_edit' => true,
'can_delete' => true,
'fieldMap' => [
'column_name' => 'tc.name',
'created_by_name' => 'u.name',
],
'scope' => function($builder) use ($organizationId) {
$builder->from('tasks')
->select('tasks.id, tasks.title, tasks.description, tasks.priority, tasks.due_date, tasks.completed_at, tasks.created_at, tc.name as column_name, tc.color as column_color, u.name as created_by_name')
->join('task_columns tc', 'tasks.column_id = tc.id', 'left')
->join('users u', 'tasks.created_by = u.id', 'left')
->where('tasks.organization_id', $organizationId);
},
];
}
/**
* Канбан-доска задач
*/
public function kanban()
{
$organizationId = $this->requireActiveOrg();
$boardId = (int) ($this->request->getGet('board') ?? 0);
// Получаем доску
if (!$boardId) {
$boards = $this->boardService->getOrganizationBoards($organizationId);
$boardId = $boards[0]['id'] ?? 0;
}
$board = $this->boardService->getBoardWithColumns($boardId, $organizationId);
if (!$board) {
return redirect()->to('/tasks')->with('error', 'Доска не найдена');
}
$kanbanData = $this->taskService->getTasksForKanban($boardId);
// Формируем колонки для компонента
$kanbanColumns = [];
foreach ($board['columns'] as $column) {
$columnTasks = $kanbanData[$column['id']]['tasks'] ?? [];
$kanbanColumns[] = [
'id' => $column['id'],
'name' => $column['name'],
'color' => $column['color'],
'items' => $columnTasks,
];
}
return $this->renderTwig('@Tasks/tasks/kanban', [
'title' => 'Задачи — Канбан',
'kanbanColumns' => $kanbanColumns,
'board' => $board,
'boards' => $this->boardService->getOrganizationBoards($organizationId),
'stats' => $this->taskService->getStats($organizationId),
]);
}
/**
* Календарь задач
*/
public function calendar()
{
$organizationId = $this->requireActiveOrg();
$month = $this->request->getGet('month') ?? date('Y-m');
$currentTimestamp = strtotime($month . '-01');
$daysInMonth = date('t', $currentTimestamp);
$firstDayOfWeek = date('N', $currentTimestamp) - 1;
$tasks = $this->taskService->getTasksForCalendar($organizationId, $month);
$eventsByDate = [];
foreach ($tasks as $task) {
if ($task['due_date']) {
$dateKey = date('Y-m-d', strtotime($task['due_date']));
$eventsByDate[$dateKey][] = [
'id' => $task['id'],
'title' => $task['title'],
'date' => $task['due_date'],
'column_color' => $task['column_color'] ?? '#6B7280',
'priority' => $task['priority'],
'url' => '/tasks/' . $task['id'],
];
}
}
return $this->renderTwig('@Tasks/tasks/calendar', [
'title' => 'Задачи — Календарь',
'eventsByDate' => $eventsByDate,
'currentMonth' => $month,
'monthName' => date('F Y', $currentTimestamp),
'daysInMonth' => $daysInMonth,
'firstDayOfWeek' => $firstDayOfWeek,
'prevMonth' => date('Y-m', strtotime('-1 month', $currentTimestamp)),
'nextMonth' => date('Y-m', strtotime('+1 month', $currentTimestamp)),
'today' => date('Y-m-d'),
'stats' => $this->taskService->getStats($organizationId),
]);
}
/**
* Страница создания задачи
*/
public function create()
{
$organizationId = $this->requireActiveOrg();
$boardId = (int) ($this->request->getGet('board') ?? 0);
// Получаем доски организации, если нет - создаём дефолтную
$boards = $this->boardService->getOrganizationBoards($organizationId);
if (empty($boards)) {
$boardId = $this->boardService->createBoard([
'organization_id' => $organizationId,
'name' => 'Мои задачи',
'description' => 'Основная доска задач',
], $this->getCurrentUserId());
$boards = $this->boardService->getOrganizationBoards($organizationId);
}
if (!$boardId && !empty($boards)) {
$boardId = $boards[0]['id'];
}
// Получаем пользователей организации
$orgUserModel = new OrganizationUserModel();
$orgUsers = $orgUserModel->getOrganizationUsers($organizationId);
$users = [];
foreach ($orgUsers as $user) {
$users[$user['user_id']] = $user['user_name'] ?: $user['user_email'];
}
return $this->renderTwig('@Tasks/tasks/form', [
'title' => 'Новая задача',
'actionUrl' => '/tasks',
'boards' => $boards,
'selectedBoard' => $boardId,
'users' => $users,
'currentUserId' => $this->getCurrentUserId(),
'priorities' => [
'low' => 'Низкий',
'medium' => 'Средний',
'high' => 'Высокий',
'urgent' => 'Срочный',
],
]);
}
/**
* Сохранить новую задачу
*/
public function store()
{
$organizationId = $this->requireActiveOrg();
$userId = $this->getCurrentUserId();
$data = [
'organization_id' => $organizationId,
'board_id' => $this->request->getPost('board_id'),
'column_id' => $this->request->getPost('column_id'),
'title' => $this->request->getPost('title'),
'description' => $this->request->getPost('description'),
'priority' => $this->request->getPost('priority') ?? 'medium',
'due_date' => $this->request->getPost('due_date') ?: null,
];
$taskId = $this->taskService->createTask($data, $userId);
if ($taskId) {
// Добавляем исполнителей
$assignees = $this->request->getPost('assignees') ?? [];
if (!empty($assignees)) {
$this->taskService->updateAssignees($taskId, $assignees);
}
return redirect()->to('/tasks')->with('success', 'Задача успешно создана');
}
return redirect()->back()->with('error', 'Ошибка при создании задачи')->withInput();
}
/**
* Просмотр задачи
*/
public function show(int $id)
{
$organizationId = $this->requireActiveOrg();
$task = $this->taskService->getTask($id, $organizationId);
if (!$task) {
return redirect()->to('/tasks')->with('error', 'Задача не найдена');
}
return $this->renderTwig('@Tasks/tasks/show', [
'title' => $task['title'],
'task' => (object) $task,
]);
}
/**
* Страница редактирования задачи
*/
public function edit(int $id)
{
$organizationId = $this->requireActiveOrg();
$task = $this->taskService->getTask($id, $organizationId);
if (!$task) {
return redirect()->to('/tasks')->with('error', 'Задача не найдена');
}
// Получаем пользователей организации
$orgUserModel = new OrganizationUserModel();
$orgUsers = $orgUserModel->getOrganizationUsers($organizationId);
$users = [];
foreach ($orgUsers as $user) {
$users[$user['user_id']] = $user['user_name'] ?: $user['user_email'];
}
// Получаем доски
$boards = $this->boardService->getOrganizationBoards($organizationId);
return $this->renderTwig('@Tasks/tasks/form', [
'title' => 'Редактирование задачи',
'actionUrl' => "/tasks/{$id}",
'task' => (object) $task,
'boards' => $boards,
'users' => $users,
'currentUserId' => $this->getCurrentUserId(),
'priorities' => [
'low' => 'Низкий',
'medium' => 'Средний',
'high' => 'Высокий',
'urgent' => 'Срочный',
],
]);
}
/**
* Обновить задачу
*/
public function update(int $id)
{
$organizationId = $this->requireActiveOrg();
$userId = $this->getCurrentUserId();
$data = [
'board_id' => $this->request->getPost('board_id'),
'column_id' => $this->request->getPost('column_id'),
'title' => $this->request->getPost('title'),
'description' => $this->request->getPost('description'),
'priority' => $this->request->getPost('priority') ?? 'medium',
'due_date' => $this->request->getPost('due_date') ?: null,
];
$result = $this->taskService->updateTask($id, $data, $userId);
if ($result) {
// Обновляем исполнителей
$assignees = $this->request->getPost('assignees') ?? [];
$this->taskService->updateAssignees($id, $assignees);
return redirect()->to("/tasks/{$id}")->with('success', 'Задача обновлена');
}
return redirect()->back()->with('error', 'Ошибка при обновлении задачи')->withInput();
}
/**
* Удалить задачу
*/
public function destroy(int $id)
{
$organizationId = $this->requireActiveOrg();
$userId = $this->getCurrentUserId();
$this->taskService->deleteTask($id, $userId);
return redirect()->to('/tasks')->with('success', 'Задача удалена');
}
/**
* API: перемещение задачи между колонками (drag-and-drop)
*/
public function moveColumn()
{
$organizationId = $this->requireActiveOrg();
$userId = $this->getCurrentUserId();
$taskId = $this->request->getPost('task_id');
$newColumnId = $this->request->getPost('column_id');
$result = $this->taskService->changeColumn($taskId, $newColumnId, $userId);
$csrfToken = csrf_hash();
$csrfHash = csrf_token();
return $this->response
->setHeader('X-CSRF-TOKEN', $csrfToken)
->setHeader('X-CSRF-HASH', $csrfHash)
->setJSON(['success' => $result]);
}
/**
* API: завершить задачу
*/
public function complete(int $id)
{
$userId = $this->getCurrentUserId();
$result = $this->taskService->completeTask($id, $userId);
return $this->response->setJSON(['success' => $result]);
}
/**
* API: возобновить задачу
*/
public function reopen(int $id)
{
$userId = $this->getCurrentUserId();
$result = $this->taskService->reopenTask($id, $userId);
return $this->response->setJSON(['success' => $result]);
}
}