From dd043b33855ed6cf9cffe25296ad2071e0324a59 Mon Sep 17 00:00:00 2001 From: Borgia Leiva Date: Tue, 10 Feb 2026 11:49:14 +0100 Subject: [PATCH] Created task creation page --- src/app/app.routes.ts | 25 +++-- src/app/models/tasks.models.ts | 17 ++-- .../task-create/task-create.component.css | 91 +++++++++++++++++++ .../task-create/task-create.component.html | 56 ++++++++++++ .../task-create/task-create.component.ts | 78 ++++++++++++++++ 5 files changed, 254 insertions(+), 13 deletions(-) create mode 100644 src/app/pages/task-create/task-create.component.css create mode 100644 src/app/pages/task-create/task-create.component.html create mode 100644 src/app/pages/task-create/task-create.component.ts diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 30d1fbd..af14fe9 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -6,7 +6,7 @@ export const routes: Routes = [ // Public routes { path: 'login', - component: LoginComponent + component: LoginComponent, }, // Protected routes - require authentication @@ -16,16 +16,27 @@ export const routes: Routes = [ children: [ { path: '', - loadComponent: () => import('./pages/home/home.component').then(m => m.HomeComponent) + loadComponent: () => import('./pages/home/home.component').then((m) => m.HomeComponent), }, { path: 'projects/new', - loadComponent: () => import('./pages/project-create/project-create.component').then(m => m.ProjectCreateComponent) + loadComponent: () => + import('./pages/project-create/project-create.component').then( + (m) => m.ProjectCreateComponent, + ), }, { path: 'projects/:id', - loadComponent: () => import('./pages/project-details/project-details.component').then(m => m.ProjectDetailsComponent) - } - ] - } + loadComponent: () => + import('./pages/project-details/project-details.component').then( + (m) => m.ProjectDetailsComponent, + ), + }, + { + path: 'projects/:id/tasks/new', + loadComponent: () => + import('./pages/task-create/task-create.component').then((m) => m.TaskCreateComponent), + }, + ], + }, ]; diff --git a/src/app/models/tasks.models.ts b/src/app/models/tasks.models.ts index ecde556..a063e92 100644 --- a/src/app/models/tasks.models.ts +++ b/src/app/models/tasks.models.ts @@ -1,7 +1,12 @@ +export interface Task { + id: number; + title: string; + description?: string; + status: 'pending' | 'in_progress' | 'completed' | 'stashed' | 'failed'; +} -export interface Task{ - id: number; - title: string; - description?: string; - status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'STASHED' | 'FAILED'; -} \ No newline at end of file +export interface CreateTaskRequest { + title: string; + description?: string; + status: Task['status']; +} diff --git a/src/app/pages/task-create/task-create.component.css b/src/app/pages/task-create/task-create.component.css new file mode 100644 index 0000000..e3d6140 --- /dev/null +++ b/src/app/pages/task-create/task-create.component.css @@ -0,0 +1,91 @@ +.create-task { + min-height: 100vh; + background-color: #f5f7fa; + padding: 40px; + display: flex; + justify-content: center; + align-items: flex-start; +} + +.card { + width: 100%; + max-width: 640px; + height: fit-content; + background: white; + border-radius: 12px; + padding: 32px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.card h1 { + margin: 0 0 24px 0; + font-size: 28px; + color: #2c3e50; +} + +.form-group { + margin-bottom: 20px; +} + +.form-group label { + display: block; + margin-bottom: 8px; + font-weight: 600; + color: #374151; +} + +.form-group input, +.form-group textarea, +.form-group select { + width: 100%; + padding: 12px 14px; + border: 1px solid #e5e7eb; + border-radius: 8px; + font-size: 14px; + box-sizing: border-box; + background-color: #ffffff; +} + +.form-group textarea { + resize: vertical; +} + +.actions { + display: flex; + justify-content: flex-end; + gap: 12px; + margin-top: 12px; +} + +.btn-primary { + padding: 10px 16px; + background-color: #10b981; + color: #fff; + border: none; + border-radius: 8px; + font-weight: 600; + cursor: pointer; +} + +.btn-primary:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.btn-secondary { + padding: 10px 16px; + background: none; + border: 1px solid #cbd5f0; + color: #334155; + border-radius: 8px; + font-weight: 600; + cursor: pointer; +} + +.error { + margin-bottom: 16px; + padding: 12px; + border-radius: 8px; + background-color: #fee2e2; + color: #b91c1c; +} diff --git a/src/app/pages/task-create/task-create.component.html b/src/app/pages/task-create/task-create.component.html new file mode 100644 index 0000000..c3545e1 --- /dev/null +++ b/src/app/pages/task-create/task-create.component.html @@ -0,0 +1,56 @@ +
+
+

Create task

+ + @if (errorMessage()) { +
{{ errorMessage() }}
+ } + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+
diff --git a/src/app/pages/task-create/task-create.component.ts b/src/app/pages/task-create/task-create.component.ts new file mode 100644 index 0000000..c9311cc --- /dev/null +++ b/src/app/pages/task-create/task-create.component.ts @@ -0,0 +1,78 @@ +import { Component, inject, signal } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; +import { ApiService } from '../../services/api.service'; +import { CreateTaskRequest, Task } from '../../models/tasks.models'; + +@Component({ + selector: 'app-task-create', + standalone: true, + imports: [CommonModule, FormsModule], + templateUrl: './task-create.component.html', + styleUrls: ['./task-create.component.css'], +}) +export class TaskCreateComponent { + private apiService = inject(ApiService); + private route = inject(ActivatedRoute); + private router = inject(Router); + + title = ''; + description = ''; + status: Task['status'] = 'pending'; + isSaving = signal(false); + errorMessage = signal(''); + private projectId: number | null = null; + + constructor() { + const idParam = this.route.snapshot.paramMap.get('id'); + const projectId = idParam ? Number(idParam) : Number.NaN; + + if (!Number.isFinite(projectId)) { + this.errorMessage.set('Invalid project id.'); + return; + } + + this.projectId = projectId; + } + + onSubmit() { + if (!this.title.trim()) { + this.errorMessage.set('Task title is required.'); + return; + } + + if (this.projectId == null) { + this.errorMessage.set('Invalid project id.'); + return; + } + + const payload: CreateTaskRequest = { + title: this.title.trim(), + description: this.description.trim() ? this.description.trim() : undefined, + status: this.status, + }; + + this.isSaving.set(true); + this.errorMessage.set(''); + + this.apiService.post(`/projects/${this.projectId}/tasks`, payload).subscribe({ + next: () => { + this.isSaving.set(false); + this.router.navigate(['/projects', this.projectId]); + }, + error: (error) => { + this.isSaving.set(false); + this.errorMessage.set(error?.error?.message || 'Failed to create task.'); + }, + }); + } + + onCancel() { + if (this.projectId != null) { + this.router.navigate(['/projects', this.projectId]); + } else { + this.router.navigate(['/']); + } + } +}