Files
task-stream/libs/web/tracker/application/src/lib/events/board.events.ts
Josep Piguillem 42a21412d9 feat(web): «Deshacer» after moving, reassigning or trashing a ticket
A toast offers to undo for seven seconds (or Ctrl/⌘ Z outside a field)
by running the opposite command: the card back to its column or the
ticket to its status (the workflow may refuse it), the previous
assignee, or the ticket back from the trash. The undo itself only says
«Deshecho». On the board, from the card's assignee; on the ticket page,
status, assignee and trash, which no longer asks for confirmation.

The toast is the app's own (UndoToastComponent): pi-snack-bar cannot
tell its button from the timeout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 11:43:54 +02:00

70 lines
2.0 KiB
TypeScript

import { type } from '@ngrx/signals';
import { eventGroup } from '@ngrx/signals/events';
import {
BoardColumn,
BoardFilter,
CommandResult,
CreateIssueInput,
IssueChanged,
IssuePriority,
SavedFilter,
} from '@task-stream/web/tracker/domain';
/** What a card can change without opening the ticket. */
export type CardChange =
| { priority: IssuePriority }
| { storyPoints: number | null }
| { assigneeId: string | null };
export const boardPageEvents = eventGroup({
source: 'Board Page',
events: {
opened: type<BoardFilter>(),
filterChanged: type<BoardFilter>(),
/** Optimistic: the card moves immediately and comes back if the API refuses. */
issueMoved: type<{
issueId: string;
to: string;
reason?: string | null;
/** Where it was: «Deshacer» takes it back. */
from?: string;
/** It is the «Deshacer» of a move: no undo for it. */
undoing?: boolean;
}>(),
issueCreateRequested: type<CreateIssueInput>(),
/** From the card: priority, estimate or assignee (the live update repaints it). */
issueEditRequested: type<{
issueId: string;
change: CardChange;
/** It is the «Deshacer» of a change: no undo for it. */
undoing?: boolean;
}>(),
/** Save the current filter under a name. */
viewSaveRequested: type<{ name: string; shared: boolean }>(),
viewDeleteRequested: type<{ id: string }>(),
},
});
export const boardApiEvents = eventGroup({
source: 'Board API',
events: {
loaded: type<BoardColumn[]>(),
loadFailed: type<string>(),
issueMoveFailed: type<string>(),
issueEditFailed: type<string>(),
issueCreated: type<CommandResult>(),
issueCreateFailed: type<string>(),
viewsLoaded: type<SavedFilter[]>(),
viewSaved: type<string>(),
viewDeleted: type<string>(),
viewFailed: type<string>(),
},
});
export const boardRealtimeEvents = eventGroup({
source: 'Board Realtime',
events: {
issueChanged: type<IssueChanged>(),
},
});