Refactor issue detail editing: streamline input handling and remove editing mode logic

This commit is contained in:
Cédric OLIVIER
2026-05-22 18:45:25 +02:00
parent ecf55392fd
commit dc6135ee95
2 changed files with 40 additions and 114 deletions
+10 -61
View File
@@ -4,7 +4,6 @@
<p>Informations de creation et de suivi de l'issue.</p> <p>Informations de creation et de suivi de l'issue.</p>
</div> </div>
<div class="header-meta"> <div class="header-meta">
@if (!isEditing) {
<div class="status-inline"> <div class="status-inline">
<span class="status-label">Status</span> <span class="status-label">Status</span>
<select <select
@@ -17,11 +16,8 @@
} }
</select> </select>
</div> </div>
}
<div class="header-actions"> <div class="header-actions">
@if (!isEditing) {
<button type="button" class="edit-button" (click)="startEdit()">Editer l'issue</button>
<div class="more-wrapper"> <div class="more-wrapper">
<button type="button" class="more-button" (click)="toggleMoreMenu()">More ▾</button> <button type="button" class="more-button" (click)="toggleMoreMenu()">More ▾</button>
@@ -33,7 +29,6 @@
</div> </div>
} }
</div> </div>
}
</div> </div>
</div> </div>
</header> </header>
@@ -48,125 +43,79 @@
<tr> <tr>
<th>Nom</th> <th>Nom</th>
<td> <td>
@if (isEditing) { <input type="text" [(ngModel)]="issue.name" (blur)="saveIssue()" />
<input type="text" [(ngModel)]="issue.name" />
} @else {
{{ issue.name || '-' }}
}
</td> </td>
</tr> </tr>
<tr> <tr>
<th>Type</th> <th>Type</th>
<td> <td>
@if (isEditing) { <select [(ngModel)]="issueTypeValue" (change)="saveIssue()">
<select [(ngModel)]="issueTypeValue">
@for (type of typeOptions; track type) { @for (type of typeOptions; track type) {
<option [value]="type">{{ type }}</option> <option [value]="type">{{ type }}</option>
} }
</select> </select>
} @else {
{{ issueTypeValue }}
}
</td> </td>
</tr> </tr>
<tr> <tr>
<th>Epic</th> <th>Epic</th>
<td> <td>
@if (isEditing) { <input type="text" [(ngModel)]="issue.epic" (blur)="saveIssue()" />
<input type="text" [(ngModel)]="issue.epic" />
} @else {
{{ issue.epic || '-' }}
}
</td> </td>
</tr> </tr>
<tr> <tr>
<th>Depend de</th> <th>Depend de</th>
<td> <td>
@if (isEditing) { <select multiple [(ngModel)]="dependencyIds" class="dependency-multiselect" (change)="saveIssue()">
<select multiple [(ngModel)]="dependencyIds" class="dependency-multiselect">
@for (candidate of dependencyCandidates; track candidate.id) { @for (candidate of dependencyCandidates; track candidate.id) {
<option [ngValue]="candidate.id"> <option [ngValue]="candidate.id">
#{{ candidate.id }} - {{ candidate.name || 'Sans nom' }} #{{ candidate.id }} - {{ candidate.name || 'Sans nom' }}
</option> </option>
} }
</select> </select>
} @else {
{{ resolveDependencyLabels(dependencyIds) }}
}
</td> </td>
</tr> </tr>
<tr> <tr>
<th>Assignee</th> <th>Assignee</th>
<td> <td>
@if (isEditing) { <input type="text" [(ngModel)]="issue.assignee" (blur)="saveIssue()" />
<input type="text" [(ngModel)]="issue.assignee" />
} @else {
{{ issue.assignee || '-' }}
}
</td> </td>
</tr> </tr>
<tr> <tr>
<th>Date d'echeance</th> <th>Date d'echeance</th>
<td> <td>
@if (isEditing) { <input type="date" [(ngModel)]="issue.dueDate" (blur)="saveIssue()" />
<input type="date" [(ngModel)]="issue.dueDate" />
} @else {
{{ issue.dueDate || '-' }}
}
</td> </td>
</tr> </tr>
<tr> <tr>
<th>Temps estimé</th> <th>Temps estimé</th>
<td> <td>
@if (isEditing) { <input type="number" min="0" step="0.5" [(ngModel)]="estimatedTimeValue" (blur)="saveIssue()" />
<input type="number" min="0" step="0.5" [(ngModel)]="estimatedTimeValue" />
} @else {
{{ estimatedTimeValue !== null ? estimatedTimeValue + ' h' : '-' }}
}
</td> </td>
</tr> </tr>
<tr> <tr>
<th>Description</th> <th>Description</th>
<td> <td>
@if (isEditing) { <textarea rows="4" [(ngModel)]="issue.description" (blur)="saveIssue()"></textarea>
<textarea rows="4" [(ngModel)]="issue.description"></textarea>
} @else {
{{ issue.description || '-' }}
}
</td> </td>
</tr> </tr>
<tr> <tr>
<th>Priorite</th> <th>Priorite</th>
<td> <td>
@if (isEditing) { <select [(ngModel)]="issue.priority" (change)="saveIssue()">
<select [(ngModel)]="issue.priority">
<option value="Basse">Basse</option> <option value="Basse">Basse</option>
<option value="Moyenne">Moyenne</option> <option value="Moyenne">Moyenne</option>
<option value="Haute">Haute</option> <option value="Haute">Haute</option>
</select> </select>
} @else {
{{ issue.priority }}
}
</td> </td>
</tr> </tr>
<tr> <tr>
<th>Progression</th> <th>Progression</th>
<td> <td>
@if (isEditing) { <input type="number" min="0" max="100" [(ngModel)]="issue.progress" (blur)="saveIssue()" />
<input type="number" min="0" max="100" [(ngModel)]="issue.progress" />
} @else {
{{ issue.progress }}%
}
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</section> </section>
@if (isEditing) {
<div class="form-actions">
<button type="button" class="cancel-button" (click)="cancelEdit()">Annuler</button>
<button type="button" class="save-button" (click)="saveIssue()">Enregistrer</button>
</div>
}
+3 -26
View File
@@ -13,10 +13,9 @@ export class IssueDetail {
private readonly route = inject(ActivatedRoute); private readonly route = inject(ActivatedRoute);
private readonly router = inject(Router); private readonly router = inject(Router);
private readonly issuesStore = inject(IssuesStore); private readonly issuesStore = inject(IssuesStore);
private readonly isNewIssueRoute = this.route.snapshot.routeConfig?.path === 'issues/new';
protected issue: IssueEntity = this.buildIssue(); protected issue: IssueEntity = this.buildIssue();
protected isEditing = this.route.snapshot.queryParamMap.get('mode') === 'edit';
private issueBeforeEdit: IssueEntity | null = null;
protected readonly issues = this.issuesStore.issues; protected readonly issues = this.issuesStore.issues;
protected moreMenuOpen = false; protected moreMenuOpen = false;
@@ -60,31 +59,12 @@ export class IssueDetail {
this.issue.type = value; this.issue.type = value;
} }
constructor() {
if (this.isEditing) {
this.issueBeforeEdit = this.cloneIssue(this.issue);
}
}
protected startEdit(): void {
this.issueBeforeEdit = this.cloneIssue(this.issue);
this.isEditing = true;
this.closeMoreMenu();
}
protected cancelEdit(): void {
if (this.issueBeforeEdit) {
this.issue = this.cloneIssue(this.issueBeforeEdit);
}
this.isEditing = false;
}
protected saveIssue(): void { protected saveIssue(): void {
this.issuesStore.upsert(this.issue); this.issuesStore.upsert(this.issue);
this.issueBeforeEdit = this.cloneIssue(this.issue); if (this.isNewIssueRoute) {
this.isEditing = false;
this.router.navigate(['/issues', this.issue.id]); this.router.navigate(['/issues', this.issue.id]);
} }
}
protected deleteIssue(): void { protected deleteIssue(): void {
this.issuesStore.deleteById(this.issue.id); this.issuesStore.deleteById(this.issue.id);
@@ -120,9 +100,6 @@ export class IssueDetail {
return this.issues().filter((issue) => issue.id !== this.issue.id); return this.issues().filter((issue) => issue.id !== this.issue.id);
} }
private cloneIssue(issue: IssueEntity): IssueEntity {
return { ...issue };
}
private buildIssue(): IssueEntity { private buildIssue(): IssueEntity {
const idParam = this.route.snapshot.paramMap.get('id'); const idParam = this.route.snapshot.paramMap.get('id');