Modification ajoute de dépendence
This commit is contained in:
@@ -146,8 +146,61 @@
|
|||||||
background-color: #f3f4f6;
|
background-color: #f3f4f6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dependency-multiselect {
|
.dependency-list {
|
||||||
min-height: 8rem;
|
list-style: none;
|
||||||
|
margin: 0 0 0.5rem;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dependency-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.3rem 0.6rem;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 0.4rem;
|
||||||
|
background: #f9fafb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dependency-label {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dependency-remove {
|
||||||
|
flex-shrink: 0;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: #9ca3af;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
line-height: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0 0.15rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dependency-remove:hover {
|
||||||
|
color: #b91c1c;
|
||||||
|
background: #fee2e2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dependency-add-btn {
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dependency-add-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dependency-select {
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.epic-issues-card {
|
.epic-issues-card {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<!-- suppress HtmlUnknownAttribute -->
|
||||||
<header class="page-header mb-4">
|
<header class="page-header mb-4">
|
||||||
<div>
|
<div>
|
||||||
<h1>Detail de l'issue</h1>
|
<h1>Detail de l'issue</h1>
|
||||||
@@ -7,6 +8,7 @@
|
|||||||
<div class="status-inline d-flex align-items-center gap-2 bg-white border rounded-3 px-3 py-2 shadow-sm">
|
<div class="status-inline d-flex align-items-center gap-2 bg-white border rounded-3 px-3 py-2 shadow-sm">
|
||||||
<span class="status-label">Status</span>
|
<span class="status-label">Status</span>
|
||||||
<select
|
<select
|
||||||
|
aria-label="Status"
|
||||||
class="status-select form-select form-select-sm w-auto"
|
class="status-select form-select form-select-sm w-auto"
|
||||||
[ngModel]="issue.status"
|
[ngModel]="issue.status"
|
||||||
(ngModelChange)="updateStatus($event)"
|
(ngModelChange)="updateStatus($event)"
|
||||||
@@ -43,13 +45,13 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Nom</th>
|
<th>Nom</th>
|
||||||
<td>
|
<td>
|
||||||
<input class="form-control form-control-sm" type="text" [(ngModel)]="issue.name" (blur)="saveIssue()" />
|
<input aria-label="Nom" class="form-control form-control-sm" type="text" [(ngModel)]="issue.name" (blur)="saveIssue()" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Type</th>
|
<th>Type</th>
|
||||||
<td>
|
<td>
|
||||||
<select class="form-select form-select-sm" [(ngModel)]="issueTypeValue" (change)="saveIssue()">
|
<select aria-label="Type" class="form-select form-select-sm" [(ngModel)]="issueTypeValue" (change)="saveIssue()">
|
||||||
@for (type of typeOptions; track type) {
|
@for (type of typeOptions; track type) {
|
||||||
<option [value]="type">{{ type }}</option>
|
<option [value]="type">{{ type }}</option>
|
||||||
}
|
}
|
||||||
@@ -60,7 +62,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Epic</th>
|
<th>Epic</th>
|
||||||
<td>
|
<td>
|
||||||
<select class="form-select form-select-sm" [(ngModel)]="issue.epic" (change)="saveIssue()">
|
<select aria-label="Epic" class="form-select form-select-sm" [(ngModel)]="issue.epic" (change)="saveIssue()">
|
||||||
<option value="">-</option>
|
<option value="">-</option>
|
||||||
@for (epicIssue of epicIssues; track epicIssue.id) {
|
@for (epicIssue of epicIssues; track epicIssue.id) {
|
||||||
<option [value]="epicIssue.name">{{ epicIssue.name }}</option>
|
<option [value]="epicIssue.name">{{ epicIssue.name }}</option>
|
||||||
@@ -72,43 +74,60 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Depend de</th>
|
<th>Depend de</th>
|
||||||
<td>
|
<td>
|
||||||
<select multiple class="dependency-multiselect form-select form-select-sm" [(ngModel)]="dependencyIds" (change)="saveIssue()">
|
@if (dependencyIds.length > 0) {
|
||||||
@for (candidate of dependencyCandidates; track candidate.id) {
|
<ul class="dependency-list">
|
||||||
<option [ngValue]="candidate.id">
|
@for (depId of dependencyIds; track depId) {
|
||||||
#{{ candidate.id }} - {{ candidate.name || 'Sans nom' }}
|
<li class="dependency-item">
|
||||||
</option>
|
<span class="dependency-label">#{{ depId }} - {{ resolveDependency(depId)?.name || 'Sans nom' }}</span>
|
||||||
}
|
<button type="button" class="dependency-remove" (click)="removeDependency(depId)" title="Supprimer">×</button>
|
||||||
</select>
|
</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
@if (showAddDependency) {
|
||||||
|
<div class="dependency-add-row">
|
||||||
|
<select aria-label="Choisir une dépendance" class="form-select form-select-sm dependency-select" [(ngModel)]="selectedCandidateId">
|
||||||
|
<option [ngValue]="null">Choisir une issue...</option>
|
||||||
|
@for (candidate of availableCandidates; track candidate.id) {
|
||||||
|
<option [ngValue]="candidate.id">#{{ candidate.id }} - {{ candidate.name || 'Sans nom' }}</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
<button type="button" class="btn btn-sm btn-primary" (click)="confirmAddDependency()" [disabled]="selectedCandidateId === null">Ajouter</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary" (click)="cancelAddDependency()">Annuler</button>
|
||||||
|
</div>
|
||||||
|
} @else {
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-primary dependency-add-btn" (click)="openAddDependency()">+ Ajouter une dépendance</button>
|
||||||
|
}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Assignee</th>
|
<th>Assignee</th>
|
||||||
<td>
|
<td>
|
||||||
<input class="form-control form-control-sm" type="text" [(ngModel)]="issue.assignee" (blur)="saveIssue()" />
|
<input aria-label="Assignee" class="form-control form-control-sm" type="text" [(ngModel)]="issue.assignee" (blur)="saveIssue()" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Date d'echeance</th>
|
<th>Date d'echeance</th>
|
||||||
<td>
|
<td>
|
||||||
<input class="form-control form-control-sm" type="date" [(ngModel)]="issue.dueDate" (blur)="saveIssue()" />
|
<input aria-label="Date d'échéance" class="form-control form-control-sm" type="date" [(ngModel)]="issue.dueDate" (blur)="saveIssue()" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Temps estimé</th>
|
<th>Temps estimé</th>
|
||||||
<td>
|
<td>
|
||||||
<input class="form-control form-control-sm" type="number" min="0" step="0.5" [(ngModel)]="estimatedTimeValue" (blur)="saveIssue()" />
|
<input aria-label="Temps estimé" class="form-control form-control-sm" type="number" min="0" step="0.5" [(ngModel)]="estimatedTimeValue" (blur)="saveIssue()" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Description</th>
|
<th>Description</th>
|
||||||
<td>
|
<td>
|
||||||
<textarea class="form-control form-control-sm" rows="4" [(ngModel)]="issue.description" (blur)="saveIssue()"></textarea>
|
<textarea aria-label="Description" class="form-control form-control-sm" rows="4" [(ngModel)]="issue.description" (blur)="saveIssue()"></textarea>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Priorite</th>
|
<th>Priorite</th>
|
||||||
<td>
|
<td>
|
||||||
<select class="form-select form-select-sm" [(ngModel)]="issue.priority" (change)="saveIssue()">
|
<select aria-label="Priorité" class="form-select form-select-sm" [(ngModel)]="issue.priority" (change)="saveIssue()">
|
||||||
<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>
|
||||||
@@ -118,7 +137,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Progression</th>
|
<th>Progression</th>
|
||||||
<td>
|
<td>
|
||||||
<input class="form-control form-control-sm" type="number" min="0" max="100" [(ngModel)]="issue.progress" (blur)="saveIssue()" />
|
<input aria-label="Progression" class="form-control form-control-sm" type="number" min="0" max="100" [(ngModel)]="issue.progress" (blur)="saveIssue()" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -151,5 +170,3 @@
|
|||||||
}
|
}
|
||||||
</section>
|
</section>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ export class IssueDetail {
|
|||||||
protected issue: IssueEntity = this.buildIssue();
|
protected issue: IssueEntity = this.buildIssue();
|
||||||
protected readonly issues = this.issuesStore.issues;
|
protected readonly issues = this.issuesStore.issues;
|
||||||
protected moreMenuOpen = false;
|
protected moreMenuOpen = false;
|
||||||
|
protected showAddDependency = false;
|
||||||
|
protected selectedCandidateId: number | null = null;
|
||||||
|
|
||||||
protected readonly statusOptions: IssueEntity['status'][] = [
|
protected readonly statusOptions: IssueEntity['status'][] = [
|
||||||
'draft',
|
'draft',
|
||||||
@@ -39,10 +41,38 @@ export class IssueDetail {
|
|||||||
return this.issue.dependsOnIds;
|
return this.issue.dependsOnIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected set dependencyIds(value: number[]) {
|
protected get availableCandidates(): IssueEntity[] {
|
||||||
this.issue.dependsOnIds = Array.isArray(value)
|
return this.issues().filter(
|
||||||
? value.filter((dependencyId): dependencyId is number => typeof dependencyId === 'number')
|
(issue) => issue.id !== this.issue.id && !this.issue.dependsOnIds.includes(issue.id),
|
||||||
: [];
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected resolveDependency(id: number): IssueEntity | undefined {
|
||||||
|
return this.issues().find((issue) => issue.id === id);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected openAddDependency(): void {
|
||||||
|
this.selectedCandidateId = null;
|
||||||
|
this.showAddDependency = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected cancelAddDependency(): void {
|
||||||
|
this.showAddDependency = false;
|
||||||
|
this.selectedCandidateId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected confirmAddDependency(): void {
|
||||||
|
if (this.selectedCandidateId !== null) {
|
||||||
|
this.issue.dependsOnIds = [...this.issue.dependsOnIds, this.selectedCandidateId];
|
||||||
|
this.saveIssue();
|
||||||
|
}
|
||||||
|
this.showAddDependency = false;
|
||||||
|
this.selectedCandidateId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected removeDependency(id: number): void {
|
||||||
|
this.issue.dependsOnIds = this.issue.dependsOnIds.filter((depId) => depId !== id);
|
||||||
|
this.saveIssue();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected get estimatedTimeValue(): number | null {
|
protected get estimatedTimeValue(): number | null {
|
||||||
@@ -102,23 +132,6 @@ export class IssueDetail {
|
|||||||
this.moreMenuOpen = false;
|
this.moreMenuOpen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected resolveDependencyLabels(issueIds: number[]): string {
|
|
||||||
if (issueIds.length === 0) {
|
|
||||||
return '-';
|
|
||||||
}
|
|
||||||
|
|
||||||
return issueIds
|
|
||||||
.map((issueId) => this.issues().find((issue) => issue.id === issueId))
|
|
||||||
.filter((issue): issue is IssueEntity => Boolean(issue))
|
|
||||||
.map((issue) => `#${issue.id} - ${issue.name || 'Sans nom'}`)
|
|
||||||
.join(', ');
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get dependencyCandidates(): IssueEntity[] {
|
|
||||||
return this.issues().filter((issue) => issue.id !== this.issue.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private buildIssue(): IssueEntity {
|
private buildIssue(): IssueEntity {
|
||||||
const idParam = this.route.snapshot.paramMap.get('id');
|
const idParam = this.route.snapshot.paramMap.get('id');
|
||||||
const draftId = this.route.snapshot.queryParamMap.get('draftId');
|
const draftId = this.route.snapshot.queryParamMap.get('draftId');
|
||||||
|
|||||||
Reference in New Issue
Block a user