Avancement + filtre sur liste des issues
This commit is contained in:
@@ -105,14 +105,16 @@
|
|||||||
<input aria-label="Date d'échéance" 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()" />
|
||||||
</div>
|
</div>
|
||||||
<div class="row g-2">
|
<div class="row g-2">
|
||||||
<div class="col-6">
|
<div [class]="isEpicIssue ? 'col-12' : 'col-6'">
|
||||||
<label class="field-label">Temps estimé (h)</label>
|
<label class="field-label">Temps estimé (h)</label>
|
||||||
<input aria-label="Temps estimé" 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()" />
|
||||||
</div>
|
</div>
|
||||||
|
@if (!isEpicIssue) {
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<label class="field-label">Progression (%)</label>
|
<label class="field-label">Progression (%)</label>
|
||||||
<input aria-label="Progression" 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()" />
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,4 +15,13 @@
|
|||||||
outline-offset: -2px;
|
outline-offset: -2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.progress-cell {
|
||||||
|
min-width: 130px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-label {
|
||||||
|
min-width: 2.5rem;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<!-- suppress HtmlUnknownAttribute -->
|
||||||
<div class="d-flex flex-column flex-md-row align-items-md-center justify-content-between gap-3 mb-4">
|
<div class="d-flex flex-column flex-md-row align-items-md-center justify-content-between gap-3 mb-4">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="h2 mb-2">Issues</h1>
|
<h1 class="h2 mb-2">Issues</h1>
|
||||||
@@ -6,6 +7,25 @@
|
|||||||
<button type="button" class="btn btn-primary" (click)="createIssue()">Creer</button>
|
<button type="button" class="btn btn-primary" (click)="createIssue()">Creer</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex flex-wrap gap-2 mb-3">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-sm"
|
||||||
|
[class.btn-outline-secondary]="selectedType !== null"
|
||||||
|
[class.btn-secondary]="selectedType === null"
|
||||||
|
(click)="selectType(null)"
|
||||||
|
>Tous</button>
|
||||||
|
@for (type of typeOptions; track type) {
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="filter-btn btn btn-sm"
|
||||||
|
[class.active]="selectedType === type"
|
||||||
|
[class]="'filter-btn btn btn-sm ' + (selectedType === type ? typeBadgeClass(type).replace('text-bg-', 'btn-') : 'btn-outline-secondary')"
|
||||||
|
(click)="selectType(type)"
|
||||||
|
>{{ type }}</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card shadow-sm">
|
<div class="card shadow-sm">
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-hover align-middle mb-0">
|
<table class="table table-hover align-middle mb-0">
|
||||||
@@ -16,10 +36,11 @@
|
|||||||
<th>Priorite</th>
|
<th>Priorite</th>
|
||||||
<th>Statut</th>
|
<th>Statut</th>
|
||||||
<th>Assignee</th>
|
<th>Assignee</th>
|
||||||
|
<th>Progression</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@for (issue of issues(); track issue.id) {
|
@for (issue of filteredIssues; track issue.id) {
|
||||||
<tr
|
<tr
|
||||||
class="clickable-row"
|
class="clickable-row"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@@ -31,6 +52,21 @@
|
|||||||
<td>{{ issue.priority }}</td>
|
<td>{{ issue.priority }}</td>
|
||||||
<td>{{ issue.status }}</td>
|
<td>{{ issue.status }}</td>
|
||||||
<td>{{ issue.assignee }}</td>
|
<td>{{ issue.assignee }}</td>
|
||||||
|
<td class="progress-cell">
|
||||||
|
<div class="d-flex align-items-center gap-2">
|
||||||
|
<div class="progress flex-grow-1" style="height: 6px;">
|
||||||
|
<div
|
||||||
|
class="progress-bar"
|
||||||
|
role="progressbar"
|
||||||
|
[style.width.%]="getProgress(issue)"
|
||||||
|
[attr.aria-valuenow]="getProgress(issue)"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="100"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<span class="progress-label text-secondary small">{{ getProgress(issue) }}%</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -13,6 +13,20 @@ export class Issues {
|
|||||||
private readonly issuesStore = inject(IssuesStore);
|
private readonly issuesStore = inject(IssuesStore);
|
||||||
|
|
||||||
protected readonly issues = this.issuesStore.issues;
|
protected readonly issues = this.issuesStore.issues;
|
||||||
|
protected selectedType: IssueEntity['type'] | null = null;
|
||||||
|
|
||||||
|
protected readonly typeOptions: IssueEntity['type'][] = [
|
||||||
|
'Epic', 'Bug', 'Study', 'Story', 'Task', 'Technical Story',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected get filteredIssues(): IssueEntity[] {
|
||||||
|
if (this.selectedType === null) return this.issues();
|
||||||
|
return this.issues().filter((i) => i.type === this.selectedType);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected selectType(type: IssueEntity['type'] | null): void {
|
||||||
|
this.selectedType = this.selectedType === type ? null : type;
|
||||||
|
}
|
||||||
|
|
||||||
protected createIssue(): void {
|
protected createIssue(): void {
|
||||||
const nextId = this.issuesStore.getNextId();
|
const nextId = this.issuesStore.getNextId();
|
||||||
@@ -25,6 +39,18 @@ export class Issues {
|
|||||||
this.router.navigate(['/issues', issueId]);
|
this.router.navigate(['/issues', issueId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected getProgress(issue: IssueEntity): number {
|
||||||
|
if (issue.type !== 'Epic') {
|
||||||
|
return issue.progress;
|
||||||
|
}
|
||||||
|
const children = this.issues().filter(
|
||||||
|
(i) => i.id !== issue.id && (i.epic === issue.name || i.dependsOnIds.includes(issue.id)),
|
||||||
|
);
|
||||||
|
if (children.length === 0) return 0;
|
||||||
|
const done = children.filter((i) => i.status === 'done').length;
|
||||||
|
return Math.round((done / children.length) * 100);
|
||||||
|
}
|
||||||
|
|
||||||
protected typeBadgeClass(type: IssueEntity['type']): string {
|
protected typeBadgeClass(type: IssueEntity['type']): string {
|
||||||
const map: Record<IssueEntity['type'], string> = {
|
const map: Record<IssueEntity['type'], string> = {
|
||||||
Bug: 'text-bg-danger',
|
Bug: 'text-bg-danger',
|
||||||
|
|||||||
Reference in New Issue
Block a user