Created task creation page

This commit is contained in:
Marta Borgia Leiva 2026-02-10 11:49:14 +01:00
parent 782f58a4a2
commit dd043b3385
5 changed files with 254 additions and 13 deletions

View file

@ -6,7 +6,7 @@ export const routes: Routes = [
// Public routes // Public routes
{ {
path: 'login', path: 'login',
component: LoginComponent component: LoginComponent,
}, },
// Protected routes - require authentication // Protected routes - require authentication
@ -16,16 +16,27 @@ export const routes: Routes = [
children: [ children: [
{ {
path: '', path: '',
loadComponent: () => import('./pages/home/home.component').then(m => m.HomeComponent) loadComponent: () => import('./pages/home/home.component').then((m) => m.HomeComponent),
}, },
{ {
path: 'projects/new', 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', 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),
},
],
},
]; ];

View file

@ -1,7 +1,12 @@
export interface Task {
export interface Task{
id: number; id: number;
title: string; title: string;
description?: string; description?: string;
status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'STASHED' | 'FAILED'; status: 'pending' | 'in_progress' | 'completed' | 'stashed' | 'failed';
}
export interface CreateTaskRequest {
title: string;
description?: string;
status: Task['status'];
} }

View file

@ -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;
}

View file

@ -0,0 +1,56 @@
<div class="create-task">
<div class="card">
<h1>Create task</h1>
@if (errorMessage()) {
<div class="error">{{ errorMessage() }}</div>
}
<form (ngSubmit)="onSubmit()" #taskForm="ngForm">
<div class="form-group">
<label for="title">Task title</label>
<input
id="title"
type="text"
name="title"
[(ngModel)]="title"
placeholder="Enter a task title"
required
/>
</div>
<div class="form-group">
<label for="description">Description</label>
<textarea
id="description"
name="description"
[(ngModel)]="description"
rows="4"
placeholder="Enter a task description"
></textarea>
</div>
<div class="form-group">
<label for="status">Status</label>
<select id="status" name="status" [(ngModel)]="status" required>
<option value="pending">Pending</option>
<option value="in_progress">In progress</option>
<option value="completed">Completed</option>
<option value="stashed">Stashed</option>
<option value="failed">Failed</option>
</select>
</div>
<div class="actions">
<button type="button" class="btn-secondary" (click)="onCancel()">Cancel</button>
<button type="submit" class="btn-primary" [disabled]="isSaving() || !taskForm.form.valid">
@if (isSaving()) {
<span>Creating...</span>
} @else {
<span>Create task</span>
}
</button>
</div>
</form>
</div>
</div>

View file

@ -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<Task>(`/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(['/']);
}
}
}