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:
@ -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);
|
||||
|
||||
Reference in New Issue
Block a user