A project management app derived from Mind-Vision-Code
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

210 rindas
7.8KB

  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Controllers;
  4. use App\Repositories\ActivityRepository;
  5. use App\Repositories\ProjectRepository;
  6. use Core\Controller;
  7. use Core\Validator;
  8. class ProjectController extends Controller
  9. {
  10. private const STATUS_OPTIONS = ['planned', 'active', 'at-risk', 'paused', 'done'];
  11. private const TASK_STATUS_OPTIONS = ['backlog', 'in-progress', 'review', 'blocked', 'done'];
  12. private const PRIORITY_OPTIONS = ['low', 'normal', 'high', 'urgent'];
  13. public function index()
  14. {
  15. $search = trim((string) request()->input('search', ''));
  16. $status = trim((string) request()->input('status', ''));
  17. $repository = new ProjectRepository();
  18. return $this->view('projects.index', [
  19. 'pageTitle' => 'Projects',
  20. 'projects' => $repository->all($search, $status),
  21. 'summary' => $repository->dashboardSummary(),
  22. 'search' => $search,
  23. 'status' => $status,
  24. 'statusOptions' => self::STATUS_OPTIONS,
  25. ]);
  26. }
  27. public function create()
  28. {
  29. return $this->view('projects.create', [
  30. 'pageTitle' => 'Create project',
  31. 'statusOptions' => self::STATUS_OPTIONS,
  32. 'old' => $this->defaultProjectForm(),
  33. 'errors' => [],
  34. ]);
  35. }
  36. public function store()
  37. {
  38. $this->requirePost(request());
  39. if (!verify_csrf_token((string) request()->input('_token'))) {
  40. return $this->renderCreateWithErrors([
  41. '_token' => ['The security token expired. Refresh the page and try again.'],
  42. ], $this->defaultProjectForm(request()->all()));
  43. }
  44. $data = $this->projectDataFromRequest();
  45. $errors = $this->validateProject($data);
  46. if ($errors !== []) {
  47. return $this->renderCreateWithErrors($errors, $data);
  48. }
  49. $projectId = (new ProjectRepository())->create($data);
  50. return $this->redirect('/projects/' . $projectId);
  51. }
  52. public function show(string $id)
  53. {
  54. $projectId = (int) $id;
  55. $repository = new ProjectRepository();
  56. $project = $repository->findBoard($projectId);
  57. if ($project === null) {
  58. return \Core\Response::notFound('Project not found.');
  59. }
  60. return $this->view('projects.show', [
  61. 'pageTitle' => $project['name'],
  62. 'project' => $project,
  63. 'taskStatusOptions' => self::TASK_STATUS_OPTIONS,
  64. 'priorityOptions' => self::PRIORITY_OPTIONS,
  65. 'activity' => (new ActivityRepository())->forProject($projectId, 12),
  66. 'taskErrors' => [],
  67. 'taskOld' => $this->defaultTaskForm(),
  68. ]);
  69. }
  70. public function updateStatus(string $id)
  71. {
  72. $this->requirePost(request());
  73. if (!verify_csrf_token((string) request()->input('_token'))) {
  74. return $this->redirect('/projects/' . (int) $id . '?error=token');
  75. }
  76. $status = trim((string) request()->input('status', ''));
  77. if (!in_array($status, self::STATUS_OPTIONS, true)) {
  78. return $this->redirect('/projects/' . (int) $id . '?error=status');
  79. }
  80. (new ProjectRepository())->updateStatus((int) $id, $status);
  81. return $this->redirect('/projects/' . (int) $id . '?updated=1');
  82. }
  83. private function renderCreateWithErrors(array $errors, array $old)
  84. {
  85. return $this->view('projects.create', [
  86. 'pageTitle' => 'Create project',
  87. 'statusOptions' => self::STATUS_OPTIONS,
  88. 'errors' => $errors,
  89. 'old' => array_merge($this->defaultProjectForm(), $old),
  90. ]);
  91. }
  92. private function defaultProjectForm(array $input = []): array
  93. {
  94. return [
  95. 'name' => (string) ($input['name'] ?? ''),
  96. 'code' => (string) ($input['code'] ?? ''),
  97. 'client_name' => (string) ($input['client_name'] ?? ''),
  98. 'description' => (string) ($input['description'] ?? ''),
  99. 'status' => (string) ($input['status'] ?? 'planned'),
  100. 'start_date' => (string) ($input['start_date'] ?? date('Y-m-d')),
  101. 'due_date' => (string) ($input['due_date'] ?? ''),
  102. 'budget_cents' => (string) ($input['budget_cents'] ?? '0'),
  103. 'owner_name' => (string) ($input['owner_name'] ?? ''),
  104. 'color_token' => (string) ($input['color_token'] ?? 'teal'),
  105. 'members_text' => (string) ($input['members_text'] ?? ''),
  106. ];
  107. }
  108. private function defaultTaskForm(array $input = []): array
  109. {
  110. return [
  111. 'title' => (string) ($input['title'] ?? ''),
  112. 'description' => (string) ($input['description'] ?? ''),
  113. 'priority' => (string) ($input['priority'] ?? 'normal'),
  114. 'status' => (string) ($input['status'] ?? 'backlog'),
  115. 'assignee' => (string) ($input['assignee'] ?? ''),
  116. 'estimate_hours' => (string) ($input['estimate_hours'] ?? ''),
  117. 'due_date' => (string) ($input['due_date'] ?? ''),
  118. ];
  119. }
  120. private function projectDataFromRequest(): array
  121. {
  122. $input = request()->all();
  123. return [
  124. 'name' => trim((string) ($input['name'] ?? '')),
  125. 'code' => trim((string) ($input['code'] ?? '')),
  126. 'client_name' => trim((string) ($input['client_name'] ?? '')),
  127. 'description' => trim((string) ($input['description'] ?? '')),
  128. 'status' => trim((string) ($input['status'] ?? 'planned')),
  129. 'start_date' => trim((string) ($input['start_date'] ?? '')),
  130. 'due_date' => trim((string) ($input['due_date'] ?? '')),
  131. 'budget_cents' => max(0, (int) preg_replace('/[^0-9]/', '', (string) ($input['budget_cents'] ?? '0'))),
  132. 'owner_name' => trim((string) ($input['owner_name'] ?? '')),
  133. 'color_token' => trim((string) ($input['color_token'] ?? 'teal')),
  134. 'members' => $this->parseMembers((string) ($input['members_text'] ?? '')),
  135. 'members_text' => (string) ($input['members_text'] ?? ''),
  136. ];
  137. }
  138. private function validateProject(array $data): array
  139. {
  140. $validator = new Validator();
  141. $validator->required('name', $data['name'], 'Project name is required.')->maxLength('name', $data['name'], 120);
  142. $validator->required('client_name', $data['client_name'], 'Client name is required.')->maxLength('client_name', $data['client_name'], 120);
  143. $validator->required('owner_name', $data['owner_name'], 'Project owner is required.')->maxLength('owner_name', $data['owner_name'], 120);
  144. $validator->required('description', $data['description'], 'Project summary is required.')->maxLength('description', $data['description'], 800);
  145. $validator->required('status', $data['status'], 'Choose a project status.');
  146. $validator->required('start_date', $data['start_date'], 'Start date is required.');
  147. if ($data['due_date'] !== '' && !preg_match('/^\d{4}-\d{2}-\d{2}$/', $data['due_date'])) {
  148. $validator->required('due_date', null, 'Use a valid due date.');
  149. }
  150. if (!in_array($data['status'], self::STATUS_OPTIONS, true)) {
  151. $validator->required('status', null, 'Choose a valid project status.');
  152. }
  153. return $validator->errors();
  154. }
  155. private function parseMembers(string $value): array
  156. {
  157. $lines = preg_split('/[\r\n,]+/', $value) ?: [];
  158. $members = [];
  159. foreach ($lines as $index => $line) {
  160. $name = trim($line);
  161. if ($name === '') {
  162. continue;
  163. }
  164. $members[] = [
  165. 'full_name' => $name,
  166. 'role' => $index === 0 ? 'Project Lead' : 'Contributor',
  167. 'allocation_percent' => $index === 0 ? 40 : 20,
  168. ];
  169. }
  170. return $members;
  171. }
  172. }

Powered by TurnKey Linux.