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;
|
||||
}
|
||||
|
||||
.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>Titre</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>Milestone</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', () => {
|
||||
it('navigates to /projects/:pid/issues/new', () => {
|
||||
const spy = vi.spyOn(router, 'navigate').mockResolvedValue(true);
|
||||
|
||||
@@ -57,6 +57,12 @@ export class Issues {
|
||||
'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 getMilestoneForIssue(issueId: number): MilestoneEntity | undefined {
|
||||
@@ -66,7 +72,7 @@ export class Issues {
|
||||
protected get filteredIssues(): IssueEntity[] {
|
||||
const q = this.searchQuery.trim().toLowerCase();
|
||||
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.selectedStatuses.size > 0 && !this.selectedStatuses.has(i.status)) 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;
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user