Ajout filtre par priorité des issues
Signed-off-by: Gato <cedric@goutailler-olivier.fr>
This commit is contained in:
@@ -32,5 +32,28 @@
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sortable-col {
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sortable-col:hover {
|
||||||
|
background-color: #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sortable-col.sorted {
|
||||||
|
color: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-icon {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
margin-left: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-icon--idle {
|
||||||
|
color: #adb5bd;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -165,7 +165,17 @@
|
|||||||
<th>#</th>
|
<th>#</th>
|
||||||
<th>Titre</th>
|
<th>Titre</th>
|
||||||
<th>Type</th>
|
<th>Type</th>
|
||||||
<th>Priorite</th>
|
<th
|
||||||
|
class="sortable-col"
|
||||||
|
(click)="toggleSortPriority()"
|
||||||
|
[class.sorted]="sortPriority !== null"
|
||||||
|
title="Trier par priorité"
|
||||||
|
>
|
||||||
|
Priorité
|
||||||
|
@if (sortPriority === 'desc') { <span class="sort-icon">↓</span> }
|
||||||
|
@else if (sortPriority === 'asc') { <span class="sort-icon">↑</span> }
|
||||||
|
@else { <span class="sort-icon sort-icon--idle">⇅</span> }
|
||||||
|
</th>
|
||||||
<th>Statut</th>
|
<th>Statut</th>
|
||||||
<th>Milestone</th>
|
<th>Milestone</th>
|
||||||
<th>Assignee</th>
|
<th>Assignee</th>
|
||||||
|
|||||||
@@ -356,6 +356,59 @@ describe('Issues', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('sortPriority — toggleSortPriority', () => {
|
||||||
|
it('starts with no sort active', () => {
|
||||||
|
expect((component as any).sortPriority).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('first toggle sets sort to desc (highest first)', () => {
|
||||||
|
(component as any).toggleSortPriority();
|
||||||
|
expect((component as any).sortPriority).toBe('desc');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('second toggle sets sort to asc (lowest first)', () => {
|
||||||
|
(component as any).sortPriority = 'desc';
|
||||||
|
(component as any).toggleSortPriority();
|
||||||
|
expect((component as any).sortPriority).toBe('asc');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('third toggle resets sort to null', () => {
|
||||||
|
(component as any).sortPriority = 'asc';
|
||||||
|
(component as any).toggleSortPriority();
|
||||||
|
expect((component as any).sortPriority).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('filteredIssues — priority sort', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
store.upsert(makeIssue({ id: 10, name: 'Très haute', priority: 'TRES_HAUTE' }));
|
||||||
|
store.upsert(makeIssue({ id: 11, name: 'Basse', priority: 'BASSE' }));
|
||||||
|
store.upsert(makeIssue({ id: 12, name: 'Haute', priority: 'HAUTE' }));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not reorder issues when sortPriority is null', () => {
|
||||||
|
(component as any).sortPriority = null;
|
||||||
|
const ids = (component as any).filteredIssues.map((i: IssueEntity) => i.id);
|
||||||
|
expect(ids).toEqual(store.issues().map((i) => i.id));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sorts highest priority first when sortPriority is desc', () => {
|
||||||
|
(component as any).sortPriority = 'desc';
|
||||||
|
const priorities = (component as any).filteredIssues.map((i: IssueEntity) => i.priority);
|
||||||
|
const order = ['TRES_HAUTE', 'HAUTE', 'MOYENNE', 'BASSE', 'TRES_FAIBLE'];
|
||||||
|
const indices = priorities.map((p: string) => order.indexOf(p));
|
||||||
|
expect(indices).toEqual([...indices].sort((a, b) => a - b));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sorts lowest priority first when sortPriority is asc', () => {
|
||||||
|
(component as any).sortPriority = 'asc';
|
||||||
|
const priorities = (component as any).filteredIssues.map((i: IssueEntity) => i.priority);
|
||||||
|
const order = ['TRES_HAUTE', 'HAUTE', 'MOYENNE', 'BASSE', 'TRES_FAIBLE'];
|
||||||
|
const indices = priorities.map((p: string) => order.indexOf(p));
|
||||||
|
expect(indices).toEqual([...indices].sort((a, b) => b - a));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('createIssue', () => {
|
describe('createIssue', () => {
|
||||||
it('navigates to /projects/:pid/issues/new', () => {
|
it('navigates to /projects/:pid/issues/new', () => {
|
||||||
const spy = vi.spyOn(router, 'navigate').mockResolvedValue(true);
|
const spy = vi.spyOn(router, 'navigate').mockResolvedValue(true);
|
||||||
|
|||||||
@@ -57,6 +57,12 @@ export class Issues {
|
|||||||
'TRES_HAUTE', 'HAUTE', 'MOYENNE', 'BASSE', 'TRES_FAIBLE',
|
'TRES_HAUTE', 'HAUTE', 'MOYENNE', 'BASSE', 'TRES_FAIBLE',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
private readonly PRIORITY_ORDER: Record<IssuePriority, number> = {
|
||||||
|
TRES_HAUTE: 0, HAUTE: 1, MOYENNE: 2, BASSE: 3, TRES_FAIBLE: 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
protected sortPriority: 'desc' | 'asc' | null = null;
|
||||||
|
|
||||||
protected readonly statusOptions = this.statusesStore.statuses;
|
protected readonly statusOptions = this.statusesStore.statuses;
|
||||||
|
|
||||||
protected getMilestoneForIssue(issueId: number): MilestoneEntity | undefined {
|
protected getMilestoneForIssue(issueId: number): MilestoneEntity | undefined {
|
||||||
@@ -66,7 +72,7 @@ export class Issues {
|
|||||||
protected get filteredIssues(): IssueEntity[] {
|
protected get filteredIssues(): IssueEntity[] {
|
||||||
const q = this.searchQuery.trim().toLowerCase();
|
const q = this.searchQuery.trim().toLowerCase();
|
||||||
const milestoneActive = this.selectedMilestoneIds.size > 0 || this.showNoMilestone;
|
const milestoneActive = this.selectedMilestoneIds.size > 0 || this.showNoMilestone;
|
||||||
return this.issues().filter((i) => {
|
const filtered = this.issues().filter((i) => {
|
||||||
if (this.selectedTypes.size > 0 && !this.selectedTypes.has(i.type)) return false;
|
if (this.selectedTypes.size > 0 && !this.selectedTypes.has(i.type)) return false;
|
||||||
if (this.selectedStatuses.size > 0 && !this.selectedStatuses.has(i.status)) return false;
|
if (this.selectedStatuses.size > 0 && !this.selectedStatuses.has(i.status)) return false;
|
||||||
if (this.selectedPriorities.size > 0 && !this.selectedPriorities.has(i.priority)) return false;
|
if (this.selectedPriorities.size > 0 && !this.selectedPriorities.has(i.priority)) return false;
|
||||||
@@ -79,6 +85,17 @@ export class Issues {
|
|||||||
if (q && !i.name.toLowerCase().includes(q)) return false;
|
if (q && !i.name.toLowerCase().includes(q)) return false;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
if (this.sortPriority === null) return filtered;
|
||||||
|
const dir = this.sortPriority === 'desc' ? 1 : -1;
|
||||||
|
return [...filtered].sort(
|
||||||
|
(a, b) => (this.PRIORITY_ORDER[a.priority] - this.PRIORITY_ORDER[b.priority]) * dir,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected toggleSortPriority(): void {
|
||||||
|
if (this.sortPriority === null) this.sortPriority = 'desc';
|
||||||
|
else if (this.sortPriority === 'desc') this.sortPriority = 'asc';
|
||||||
|
else this.sortPriority = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected toggleDropdown(name: 'type' | 'status' | 'priority' | 'milestone', event: Event): void {
|
protected toggleDropdown(name: 'type' | 'status' | 'priority' | 'milestone', event: Event): void {
|
||||||
|
|||||||
Reference in New Issue
Block a user