fix(frontend): sprint change/removal on tickets, web workspace (#86 QA fix)

QA-flagged gap: the web surface could only ADD a ticket to a sprint, never
change or clear it, despite the backend already exposing both
ticket_assign_sprint and ticket_unassign_sprint.

- useTicketDetail: new setSprint(sprintId | null) method (additive, also
  available to the desktop TicketDetail — unused there today, no behaviour
  change), routing through the existing TicketGateway.setTicketSprint.
- WebTicketDetail: Sprint selector in "Statut et priorité", immediate save
  like status/priority; empty value clears back to "Sans sprint".
- WebSprintsView: each sprint card now shows its tickets (compact list,
  resolved client-side like the desktop SprintManager) with a "Retirer du
  sprint" action per ticket, calling assignSprint(ref, null) — symmetric
  with "Ajouter tickets". Extracted the card into a SprintCard subcomponent
  to keep the growing card readable.
- Tests: WebTicketsSprints.test.tsx now has 16 tests (was 10) — added
  sprint change/clear, sprint removal from the Sprints tab, agent
  assign/unassign, ticket link/unlink, carnet save, and free-text search
  filtering.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-21 07:43:09 +02:00
parent e69361feb7
commit e7f67bada9
4 changed files with 396 additions and 103 deletions

View File

@ -48,6 +48,12 @@ export interface TicketDetailViewModel {
link: (targetRef: string, kind: TicketLinkKind) => Promise<boolean>;
unlink: (targetRef: string, kind?: TicketLinkKind) => Promise<boolean>;
assign: (agentId: string, assigned: boolean) => Promise<boolean>;
/**
* Changes this ticket's sprint membership, or clears it with
* `sprintId === null` (ticket #86 — web sprint control in the detail view).
* Routes to `ticket_assign_sprint`/`ticket_unassign_sprint` via the gateway.
*/
setSprint: (sprintId: string | null) => Promise<boolean>;
/**
* Deletes this ticket (ticket #6). Returns `true` on success. The removal from
* lists and the closing of this surface flow from the resulting `issueDeleted`
@ -205,6 +211,12 @@ export function useTicketDetail(
[run, gateway, projectId, ref],
);
const setSprint: TicketDetailViewModel["setSprint"] = useCallback(
(sprintId) =>
run((version) => gateway.setTicketSprint(projectId, ref, sprintId, version)),
[run, gateway, projectId, ref],
);
const remove: TicketDetailViewModel["remove"] = useCallback(async () => {
setBusy(true);
setError(null);
@ -234,6 +246,7 @@ export function useTicketDetail(
link,
unlink,
assign,
setSprint,
remove,
};
}