diff --git a/src/app/issues/issues.css b/src/app/issues/issues.css
index 6a9f3bf..e628db9 100644
--- a/src/app/issues/issues.css
+++ b/src/app/issues/issues.css
@@ -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;
+}
+
diff --git a/src/app/issues/issues.html b/src/app/issues/issues.html
index 11adf5c..887e456 100644
--- a/src/app/issues/issues.html
+++ b/src/app/issues/issues.html
@@ -165,7 +165,17 @@
# |
Titre |
Type |
- Priorite |
+
+ Priorité
+ @if (sortPriority === 'desc') { ↓ }
+ @else if (sortPriority === 'asc') { ↑ }
+ @else { ⇅ }
+ |
Statut |
Milestone |
Assignee |
diff --git a/src/app/issues/issues.spec.ts b/src/app/issues/issues.spec.ts
index 2de6c9f..79358be 100644
--- a/src/app/issues/issues.spec.ts
+++ b/src/app/issues/issues.spec.ts
@@ -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);
diff --git a/src/app/issues/issues.ts b/src/app/issues/issues.ts
index 5e59570..6f55ecf 100644
--- a/src/app/issues/issues.ts
+++ b/src/app/issues/issues.ts
@@ -57,6 +57,12 @@ export class Issues {
'TRES_HAUTE', 'HAUTE', 'MOYENNE', 'BASSE', 'TRES_FAIBLE',
];
+ private readonly PRIORITY_ORDER: Record = {
+ 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 {