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]); } }