feat(tickets): tri de la liste de tickets côté backend (#21)

Ajoute le paramètre de tri dans TicketListQuery et l'expose via
ticket_list (surface app-tauri + MCP). Ports et adaptateur mock
frontend alignés sur le contrat de tri. La partie UI (contrôle de tri
dans TicketFacetsBar) suivra dans un commit frontend dédié.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 11:45:59 +02:00
parent 2f8467e2dd
commit e523f44425
4 changed files with 184 additions and 5 deletions

View File

@ -46,6 +46,8 @@ import type {
TicketLink,
TicketLinkKind,
TicketList,
TicketPriority,
TicketStatus,
TurnPage,
TurnView,
Unsubscribe,
@ -87,6 +89,44 @@ import type {
import { normalizeProjectWorkState } from "../workStateNormalization";
import { applyOperation, singleLeafTree } from "@/features/layout/layout";
const TICKET_PRIORITY_RANK: Record<TicketPriority, number> = {
low: 0,
medium: 1,
high: 2,
critical: 3,
};
const TICKET_STATUS_RANK: Record<TicketStatus, number> = {
open: 0,
inProgress: 1,
QA: 2,
closed: 3,
};
function sortTickets(
rows: Ticket[],
sort: NonNullable<TicketListQuery["sort"]>,
): Ticket[] {
return rows.sort((a, b) => {
let fieldOrder = 0;
if (sort.field === "number") {
fieldOrder = a.number - b.number;
} else if (sort.field === "priority") {
fieldOrder =
TICKET_PRIORITY_RANK[a.priority] - TICKET_PRIORITY_RANK[b.priority];
} else if (sort.field === "status") {
fieldOrder = TICKET_STATUS_RANK[a.status] - TICKET_STATUS_RANK[b.status];
} else {
fieldOrder =
a.title.localeCompare(b.title, undefined, { sensitivity: "base" }) ||
a.title.localeCompare(b.title);
}
const directed = sort.direction === "desc" ? -fieldOrder : fieldOrder;
return directed || a.number - b.number;
});
}
export class MockSystemGateway implements SystemGateway {
private listeners = new Set<(e: DomainEvent) => void>();
@ -1955,7 +1995,7 @@ export class MockTicketGateway implements TicketGateway {
// empty/absent set means "no constraint" (all values pass).
const statuses = q.statuses ?? [];
const priorities = q.priorities ?? [];
const rows = [...this.projectTickets(projectId).values()]
let rows = [...this.projectTickets(projectId).values()]
.filter((t) => statuses.length === 0 || statuses.includes(t.status))
.filter((t) => priorities.length === 0 || priorities.includes(t.priority))
.filter(
@ -1967,8 +2007,10 @@ export class MockTicketGateway implements TicketGateway {
!text ||
t.title.toLowerCase().includes(text) ||
t.description.toLowerCase().includes(text),
)
.sort((a, b) => b.number - a.number);
);
rows = q.sort
? sortTickets(rows, q.sort)
: rows.sort((a, b) => b.number - a.number);
const start = Number.parseInt(q.cursor ?? "0", 10) || 0;
const limit = Math.min(Math.max(q.limit ?? 100, 1), 500);
const end = Math.min(rows.length, start + limit);