mirror of
https://github.com/a-mayb3/KanbanCloneAngular.git
synced 2026-03-21 18:05:38 +01:00
minor changes /j
This commit is contained in:
parent
f7f12356de
commit
377c8b6146
11 changed files with 587 additions and 71 deletions
|
|
@ -0,0 +1,84 @@
|
|||
.collaborator-add {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f7fa;
|
||||
padding: 40px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.card {
|
||||
width: 100%;
|
||||
max-width: 520px;
|
||||
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 {
|
||||
width: 100%;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<div class="collaborator-add">
|
||||
<div class="card">
|
||||
<h1>Add collaborator</h1>
|
||||
|
||||
@if (errorMessage()) {
|
||||
<div class="error">{{ errorMessage() }}</div>
|
||||
}
|
||||
|
||||
<form (ngSubmit)="onSubmit()" #collaboratorForm="ngForm">
|
||||
<div class="form-group">
|
||||
<label for="email">Collaborator e-mail</label>
|
||||
<input
|
||||
id="email"
|
||||
type="email"
|
||||
name="email"
|
||||
[(ngModel)]="email"
|
||||
placeholder="Enter collaborator e-mail"
|
||||
required
|
||||
autocomplete="email"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button type="button" class="btn-secondary" (click)="onCancel()">Cancel</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn-primary"
|
||||
[disabled]="isSaving() || !collaboratorForm.form.valid"
|
||||
>
|
||||
@if (isSaving()) {
|
||||
<span>Adding...</span>
|
||||
} @else {
|
||||
<span>Add collaborator</span>
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
76
src/app/pages/collaborator-add/collaborator-add.component.ts
Normal file
76
src/app/pages/collaborator-add/collaborator-add.component.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
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 { AddCollaboratorRequest, AddCollaboratorResponse } from '../../models/projects.models';
|
||||
|
||||
@Component({
|
||||
selector: 'app-collaborator-add',
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule],
|
||||
templateUrl: './collaborator-add.component.html',
|
||||
styleUrl: './collaborator-add.component.css',
|
||||
})
|
||||
export class CollaboratorAddComponent {
|
||||
private apiService = inject(ApiService);
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
|
||||
email = '';
|
||||
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.email.trim()) {
|
||||
this.errorMessage.set('Collaborator email is required.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.projectId == null) {
|
||||
this.errorMessage.set('Invalid project id.');
|
||||
return;
|
||||
}
|
||||
|
||||
const payload: AddCollaboratorRequest = {
|
||||
user_email: this.email.trim(),
|
||||
};
|
||||
|
||||
this.isSaving.set(true);
|
||||
this.errorMessage.set('');
|
||||
|
||||
this.apiService
|
||||
.post<AddCollaboratorResponse>(`/projects/${this.projectId}/users/`, 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 add collaborator.');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onCancel() {
|
||||
if (this.projectId != null) {
|
||||
this.router.navigate(['/projects', this.projectId]);
|
||||
} else {
|
||||
this.router.navigate(['/']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,35 +1,10 @@
|
|||
.details-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 40px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.btn-back {
|
||||
padding: 8px 16px;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
background-color: #e2e8f0;
|
||||
color: #1f2933;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-back:hover {
|
||||
background-color: #cbd5e1;
|
||||
flex-direction: column;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
|
||||
.title h2 {
|
||||
|
|
@ -50,18 +25,27 @@
|
|||
.loading,
|
||||
.error-state,
|
||||
.empty-state {
|
||||
height: fit-content;
|
||||
background: #ffffff;
|
||||
padding: 24px;
|
||||
padding: 1rem;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.project-card {
|
||||
margin-bottom: 20px;
|
||||
.project-card h2{
|
||||
margin: 0;
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
|
||||
.collaborators-card {
|
||||
margin-bottom: 20px;
|
||||
.tasks-header,
|
||||
.collaborators-header {
|
||||
padding-top: .5rem;
|
||||
padding-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.tasks-header h4,
|
||||
.collaborators-header h4 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.danger-zone {
|
||||
|
|
@ -144,12 +128,6 @@
|
|||
background-color: #fee2e2;
|
||||
}
|
||||
|
||||
.project-card h3 {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 22px;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 0;
|
||||
color: #4b5563;
|
||||
|
|
@ -160,7 +138,6 @@
|
|||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.collaborators-header {
|
||||
|
|
@ -168,7 +145,6 @@
|
|||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.collaborators-count {
|
||||
|
|
@ -178,10 +154,14 @@
|
|||
|
||||
.collaborators-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.collaborators-grid--single {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.btn-add-task {
|
||||
width: 100%;
|
||||
padding: 8px 14px;
|
||||
|
|
@ -221,10 +201,10 @@
|
|||
color: #6b7280;
|
||||
}
|
||||
|
||||
.tasks-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 16px;
|
||||
.tasks-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.loading p,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,29 @@
|
|||
</p>
|
||||
</section>
|
||||
|
||||
<section class="tasks-card">
|
||||
<div class="tasks-header">
|
||||
<h4>Tasks</h4>
|
||||
<span class="tasks-count">
|
||||
{{ project()?.tasks?.length ?? 0 }} total • {{ taskCompletionPercentage }}% completed
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@if ((project()?.tasks?.length ?? 0) > 0) {
|
||||
<div class="tasks-list">
|
||||
@for (task of project()?.tasks ?? []; track task.id) {
|
||||
<app-task-item [task]="task" (statusChange)="onTaskStatusChange($event)" />
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<div class="empty-state">
|
||||
<p>No tasks yet.</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
<button class="btn-add-task" type="button" (click)="onAddTask()">Add task</button>
|
||||
</section>
|
||||
|
||||
<section class="collaborators-card">
|
||||
<div class="collaborators-header">
|
||||
<h4>Collaborators</h4>
|
||||
|
|
@ -24,7 +47,10 @@
|
|||
</div>
|
||||
|
||||
@if ((project()?.users?.length ?? 0) > 0) {
|
||||
<div class="collaborators-grid">
|
||||
<div
|
||||
class="collaborators-grid"
|
||||
[class.collaborators-grid--single]="(project()?.users?.length ?? 0) === 1"
|
||||
>
|
||||
@for (user of project()?.users ?? []; track user.id) {
|
||||
<app-collaborator-item [user]="user" (remove)="onRemoveCollaborator($event)" />
|
||||
}
|
||||
|
|
@ -35,28 +61,9 @@
|
|||
</div>
|
||||
}
|
||||
|
||||
<button class="btn-add-collaborator" type="button">Add collaborator</button>
|
||||
</section>
|
||||
|
||||
<section class="tasks-card">
|
||||
<div class="tasks-header">
|
||||
<h4>Tasks</h4>
|
||||
<span class="tasks-count">{{ project()?.tasks?.length ?? 0 }} total</span>
|
||||
</div>
|
||||
|
||||
@if ((project()?.tasks?.length ?? 0) > 0) {
|
||||
<div class="tasks-grid">
|
||||
@for (task of project()?.tasks ?? []; track task.id) {
|
||||
<app-task-item [task]="task" />
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<div class="empty-state">
|
||||
<p>No tasks yet.</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
<button class="btn-add-task" type="button" (click)="onAddTask()">Add task</button>
|
||||
<button class="btn-add-collaborator" type="button" (click)="onAddCollaborator()">
|
||||
Add collaborator
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<section class="danger-zone">
|
||||
|
|
@ -70,14 +77,18 @@
|
|||
<h4>Edit project details</h4>
|
||||
<p>Update the project name or description.</p>
|
||||
</div>
|
||||
<button class="btn-danger-outline" type="button">Edit project</button>
|
||||
<button class="btn-danger-outline" type="button" (click)="onEditProject()">
|
||||
Edit project
|
||||
</button>
|
||||
</div>
|
||||
<div class="danger-item">
|
||||
<div>
|
||||
<h4>Delete project</h4>
|
||||
<p>This will remove the project and all associated data.</p>
|
||||
</div>
|
||||
<button class="btn-danger" type="button">Delete project</button>
|
||||
<button class="btn-danger" type="button" (click)="onDeleteProject()">
|
||||
Delete project
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { ProjectFull } from '../../models/projects.models';
|
|||
import { CollaboratorItemComponent } from '../../components/collaborator-item/collaborator-item.component';
|
||||
import { TaskItemComponent } from '../../components/task-item/task-item.component';
|
||||
import { User } from '../../models/auth.models';
|
||||
import { Task } from '../../models/tasks.models';
|
||||
|
||||
@Component({
|
||||
selector: 'app-project-details',
|
||||
|
|
@ -24,6 +25,16 @@ export class ProjectDetailsComponent {
|
|||
isLoading = signal(true);
|
||||
errorMessage = signal('');
|
||||
|
||||
get taskCompletionPercentage(): number {
|
||||
const tasks = this.project()?.tasks ?? [];
|
||||
if (tasks.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const completedCount = tasks.filter((task) => task.status === 'completed').length;
|
||||
return Math.round((completedCount / tasks.length) * 100);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
const navState = this.router.getCurrentNavigation()?.extras.state ?? (history.state as any);
|
||||
const initialProject = navState?.project as ProjectFull | undefined;
|
||||
|
|
@ -105,4 +116,74 @@ export class ProjectDetailsComponent {
|
|||
|
||||
this.router.navigate(['/projects', this.projectId, 'tasks', 'new']);
|
||||
}
|
||||
|
||||
onTaskStatusChange(event: {
|
||||
task: Task;
|
||||
status: Task['status'];
|
||||
previousStatus: Task['status'];
|
||||
}) {
|
||||
if (this.projectId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.apiService
|
||||
.put<void>(`/projects/${this.projectId}/tasks/${event.task.id}`, {
|
||||
status: event.status,
|
||||
})
|
||||
.subscribe({
|
||||
error: (error) => {
|
||||
const current = this.project();
|
||||
if (!current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedTasks = (current.tasks ?? []).map((task) =>
|
||||
task.id === event.task.id
|
||||
? {
|
||||
...task,
|
||||
status: event.previousStatus,
|
||||
}
|
||||
: task,
|
||||
);
|
||||
|
||||
this.project.set({
|
||||
...current,
|
||||
tasks: updatedTasks,
|
||||
});
|
||||
|
||||
this.errorMessage.set(error?.error?.message || 'Failed to update task status.');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onAddCollaborator() {
|
||||
if (this.projectId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.router.navigate(['/projects', this.projectId, 'collaborators', 'new']);
|
||||
}
|
||||
|
||||
onDeleteProject() {
|
||||
if (this.projectId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.apiService.delete<void>(`/projects/${this.projectId}`).subscribe({
|
||||
next: () => {
|
||||
this.router.navigate(['/']);
|
||||
},
|
||||
error: (error) => {
|
||||
this.errorMessage.set(error?.error?.message || 'Failed to delete project.');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onEditProject() {
|
||||
if (this.projectId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.router.navigate(['/projects', this.projectId, 'edit']);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
94
src/app/pages/project-edit/project-edit.component.css
Normal file
94
src/app/pages/project-edit/project-edit.component.css
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
.edit-project {
|
||||
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 {
|
||||
width: 100%;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.loading {
|
||||
margin: 0;
|
||||
color: #6b7280;
|
||||
}
|
||||
54
src/app/pages/project-edit/project-edit.component.html
Normal file
54
src/app/pages/project-edit/project-edit.component.html
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<div class="edit-project">
|
||||
<div class="card">
|
||||
<h1>Edit project</h1>
|
||||
|
||||
@if (errorMessage()) {
|
||||
<div class="error">{{ errorMessage() }}</div>
|
||||
}
|
||||
|
||||
@if (isLoading()) {
|
||||
<p class="loading">Loading project...</p>
|
||||
} @else {
|
||||
<form (ngSubmit)="onSubmit()" #projectForm="ngForm">
|
||||
<div class="form-group">
|
||||
<label for="name">Project name</label>
|
||||
<input
|
||||
id="name"
|
||||
type="text"
|
||||
name="name"
|
||||
[(ngModel)]="name"
|
||||
placeholder="Enter a project name"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="description">Description</label>
|
||||
<textarea
|
||||
id="description"
|
||||
name="description"
|
||||
[(ngModel)]="description"
|
||||
rows="4"
|
||||
placeholder="Enter a project description"
|
||||
required
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button type="button" class="btn-secondary" (click)="onCancel()">Cancel</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn-primary"
|
||||
[disabled]="isSaving() || !projectForm.form.valid"
|
||||
>
|
||||
@if (isSaving()) {
|
||||
<span>Saving...</span>
|
||||
} @else {
|
||||
<span>Save changes</span>
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
95
src/app/pages/project-edit/project-edit.component.ts
Normal file
95
src/app/pages/project-edit/project-edit.component.ts
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
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 { Project, UpdateProjectRequest } from '../../models/projects.models';
|
||||
|
||||
@Component({
|
||||
selector: 'app-project-edit',
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule],
|
||||
templateUrl: './project-edit.component.html',
|
||||
styleUrl: './project-edit.component.css',
|
||||
})
|
||||
export class ProjectEditComponent {
|
||||
private apiService = inject(ApiService);
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
|
||||
name = '';
|
||||
description = '';
|
||||
isSaving = signal(false);
|
||||
isLoading = signal(true);
|
||||
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.isLoading.set(false);
|
||||
this.errorMessage.set('Invalid project id.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.projectId = projectId;
|
||||
|
||||
this.apiService.get<Project>(`/projects/${projectId}`).subscribe({
|
||||
next: (project) => {
|
||||
this.name = project.name ?? '';
|
||||
this.description = project.description ?? '';
|
||||
this.isLoading.set(false);
|
||||
},
|
||||
error: (error) => {
|
||||
this.isLoading.set(false);
|
||||
this.errorMessage.set(error?.error?.message || 'Failed to load project.');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
if (!this.name.trim()) {
|
||||
this.errorMessage.set('Project name is required.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.description.trim()) {
|
||||
this.errorMessage.set('Project description is required.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.projectId == null) {
|
||||
this.errorMessage.set('Invalid project id.');
|
||||
return;
|
||||
}
|
||||
|
||||
const payload: UpdateProjectRequest = {
|
||||
name: this.name.trim(),
|
||||
description: this.description.trim(),
|
||||
};
|
||||
|
||||
this.isSaving.set(true);
|
||||
this.errorMessage.set('');
|
||||
|
||||
this.apiService.put<Project>(`/projects/${this.projectId}`, 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 update project.');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onCancel() {
|
||||
if (this.projectId != null) {
|
||||
this.router.navigate(['/projects', this.projectId]);
|
||||
} else {
|
||||
this.router.navigate(['/']);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue