A project management app derived from Mind-Vision-Code
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

161 linhas
5.9KB

  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Controllers;
  4. use App\Repositories\ActivityRepository;
  5. use App\Repositories\ProjectRepository;
  6. use App\Repositories\TaskRepository;
  7. use Core\Controller;
  8. use Core\Validator;
  9. class TaskController extends Controller
  10. {
  11. private const TASK_STATUS_OPTIONS = ['backlog', 'in-progress', 'review', 'blocked', 'done'];
  12. private const PRIORITY_OPTIONS = ['low', 'normal', 'high', 'urgent'];
  13. public function store(string $projectId)
  14. {
  15. $this->requirePost(request());
  16. if (!verify_csrf_token((string) request()->input('_token'))) {
  17. return $this->renderProjectWithTaskErrors((int) $projectId, ['_token' => ['The security token expired. Refresh the page and try again.']], $this->taskFormFromRequest());
  18. }
  19. $projectRepository = new ProjectRepository();
  20. $project = $projectRepository->findBoard((int) $projectId);
  21. if ($project === null) {
  22. return \Core\Response::notFound('Project not found.');
  23. }
  24. $data = $this->taskDataFromRequest();
  25. $errors = $this->validateTask($data);
  26. if ($errors !== []) {
  27. return $this->renderProjectWithTaskErrors((int) $projectId, $errors, $data);
  28. }
  29. $taskId = (new TaskRepository())->create((int) $projectId, $data);
  30. (new ActivityRepository())->record(
  31. 'task_created',
  32. 'Added task ' . $data['title'],
  33. 'Task created on the project board.',
  34. (int) $projectId,
  35. $taskId
  36. );
  37. return $this->redirect('/projects/' . (int) $projectId . '?task=created#task-' . $taskId);
  38. }
  39. public function updateStatus(string $id)
  40. {
  41. $this->requirePost(request());
  42. if (!verify_csrf_token((string) request()->input('_token'))) {
  43. return $this->redirect('/projects');
  44. }
  45. $status = trim((string) request()->input('status', ''));
  46. if (!in_array($status, self::TASK_STATUS_OPTIONS, true)) {
  47. return $this->redirect('/projects');
  48. }
  49. $projectId = (new TaskRepository())->updateStatus((int) $id, $status);
  50. if ($projectId === null) {
  51. return \Core\Response::notFound('Task not found.');
  52. }
  53. (new ActivityRepository())->record(
  54. 'task_status_changed',
  55. 'Task updated to ' . task_status_label($status),
  56. 'Task status changed on the project board.',
  57. $projectId,
  58. (int) $id
  59. );
  60. return $this->redirect('/projects/' . $projectId . '?task-updated=1#task-' . (int) $id);
  61. }
  62. private function taskFormFromRequest(): array
  63. {
  64. $input = request()->all();
  65. return [
  66. 'title' => (string) ($input['title'] ?? ''),
  67. 'description' => (string) ($input['description'] ?? ''),
  68. 'priority' => (string) ($input['priority'] ?? 'normal'),
  69. 'status' => (string) ($input['status'] ?? 'backlog'),
  70. 'assignee' => (string) ($input['assignee'] ?? ''),
  71. 'estimate_hours' => (string) ($input['estimate_hours'] ?? ''),
  72. 'due_date' => (string) ($input['due_date'] ?? ''),
  73. ];
  74. }
  75. private function taskDataFromRequest(): array
  76. {
  77. $input = request()->all();
  78. $estimateHours = trim((string) ($input['estimate_hours'] ?? ''));
  79. $dueDate = trim((string) ($input['due_date'] ?? ''));
  80. return [
  81. 'title' => trim((string) ($input['title'] ?? '')),
  82. 'description' => trim((string) ($input['description'] ?? '')),
  83. 'priority' => trim((string) ($input['priority'] ?? 'normal')),
  84. 'status' => trim((string) ($input['status'] ?? 'backlog')),
  85. 'assignee' => trim((string) ($input['assignee'] ?? '')),
  86. 'estimate_hours' => $estimateHours !== '' ? (float) $estimateHours : null,
  87. 'due_date' => $dueDate !== '' ? $dueDate : null,
  88. ];
  89. }
  90. private function validateTask(array $data): array
  91. {
  92. $validator = new Validator();
  93. $validator->required('title', $data['title'], 'Task title is required.')->maxLength('title', $data['title'], 140);
  94. $validator->required('description', $data['description'], 'Task description is required.')->maxLength('description', $data['description'], 800);
  95. $validator->required('assignee', $data['assignee'], 'Assign the task to someone.')->maxLength('assignee', $data['assignee'], 100);
  96. if (!in_array($data['priority'], self::PRIORITY_OPTIONS, true)) {
  97. $validator->required('priority', null, 'Choose a valid task priority.');
  98. }
  99. if (!in_array($data['status'], self::TASK_STATUS_OPTIONS, true)) {
  100. $validator->required('status', null, 'Choose a valid task status.');
  101. }
  102. if ($data['due_date'] !== null && !preg_match('/^\d{4}-\d{2}-\d{2}$/', $data['due_date'])) {
  103. $validator->required('due_date', null, 'Use a valid due date.');
  104. }
  105. if ($data['estimate_hours'] !== null && $data['estimate_hours'] < 0) {
  106. $validator->required('estimate_hours', null, 'Estimate hours must be 0 or more.');
  107. }
  108. return $validator->errors();
  109. }
  110. private function renderProjectWithTaskErrors(int $projectId, array $errors, array $old)
  111. {
  112. $projectRepository = new ProjectRepository();
  113. $project = $projectRepository->findBoard($projectId);
  114. if ($project === null) {
  115. return \Core\Response::notFound('Project not found.');
  116. }
  117. return $this->view('projects.show', [
  118. 'pageTitle' => $project['name'],
  119. 'project' => $project,
  120. 'taskStatusOptions' => self::TASK_STATUS_OPTIONS,
  121. 'priorityOptions' => self::PRIORITY_OPTIONS,
  122. 'activity' => (new ActivityRepository())->forProject($projectId, 12),
  123. 'taskErrors' => $errors,
  124. 'taskOld' => array_merge($this->taskFormFromRequest(), $old),
  125. ]);
  126. }
  127. }

Powered by TurnKey Linux.