Files
task-stream/libs/web/tracker/application/src/lib/events/issue.events.ts
Josep Piguillem dfa42a8a1a feat: pinned tickets, at hand on the home page and in search
Each person pins up to 20 tickets from the ticket's page («Fijar»). They
show as a strip under the projects on the home page and in the search
palette before typing. Private and without history, like the views of
«my work» (Mongo `pinned_issues`); tickets in the trash or out of reach
are left out, and when the list is full the unreadable ones are dropped
first.

API: GET /api/me/pins, PUT/DELETE /api/issues/{issue}/pin; GraphQL
myPins, pinIssue, unpinIssue and Issue.pinned.

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

106 lines
3.5 KiB
TypeScript

import { type } from '@ngrx/signals';
import { eventGroup } from '@ngrx/signals/events';
import {
Activity,
ChangeStatusInput,
Comment,
CommentChanged,
Issue,
IssueSummary,
LinkIssuesInput,
UpdateIssueInput,
} from '@task-stream/web/tracker/domain';
export const issuePageEvents = eventGroup({
source: 'Issue Page',
events: {
opened: type<{ idOrKey: string }>(),
statusChangeRequested: type<ChangeStatusInput>(),
assignRequested: type<{ issueId: string; assigneeId: string | null }>(),
updateRequested: type<UpdateIssueInput>(),
linkRequested: type<LinkIssuesInput>(),
/** Relate by the other ticket's key (e.g. BE-3), resolved to its id first. */
linkByKeyRequested: type<{
issueId: string;
type: LinkIssuesInput['type'];
targetKey: string;
}>(),
unlinkRequested: type<LinkIssuesInput>(),
sprintChangeRequested: type<{
issueId: string;
sprintId: string | null;
}>(),
releaseChangeRequested: type<{
issueId: string;
releaseId: string | null;
}>(),
commentAddRequested: type<{
issueId: string;
body: string;
/** Uploaded under the new comment once it exists. */
files?: File[];
/** Comment it answers (one level of replies). */
replyTo?: string | null;
}>(),
watchRequested: type<{ issueId: string; watching: boolean }>(),
pinRequested: type<{ issueId: string; pinned: boolean }>(),
trashRequested: type<{ issueId: string; trashed: boolean }>(),
/** Place the ticket under another one by its key (e.g. WEB-3); null: out. */
parentRequested: type<{ issueId: string; parentKey: string | null }>(),
subtaskCreateRequested: type<{
projectId: string;
parentId: string;
type: string;
title: string;
}>(),
checklistAddRequested: type<{ issueId: string; text: string }>(),
checklistUpdateRequested: type<{
issueId: string;
itemId: string;
changes: { text?: string; done?: boolean };
}>(),
checklistRemoveRequested: type<{ issueId: string; itemId: string }>(),
attachRequested: type<{
issueId: string;
files: File[];
commentId?: string | null;
}>(),
attachmentRemoveRequested: type<{
issueId: string;
attachmentId: string;
}>(),
commentEditRequested: type<{ commentId: string; body: string }>(),
reactRequested: type<{ commentId: string; emoji: string; on: boolean }>(),
voteRequested: type<{ issueId: string; on: boolean }>(),
/** An image pasted into a text was uploaded (it joins the ticket's files). */
imagePasted: type<{ issueId: string }>(),
commentDeleteRequested: type<{ commentId: string }>(),
},
});
export const issueApiEvents = eventGroup({
source: 'Issue API',
events: {
loaded: type<Issue>(),
loadFailed: type<string>(),
commentsLoaded: type<Comment[]>(),
historyLoaded: type<Activity[]>(),
childrenLoaded: type<IssueSummary[]>(),
commandSucceeded: type<string>(),
/** A checklist step was saved (no toast for something so small). */
checklistSaved: type<void>(),
commandFailed: type<string>(),
},
});
export const issueRealtimeEvents = eventGroup({
source: 'Issue Realtime',
events: {
/** Something about this ticket changed: refetch it. */
issueTouched: type<{ issueId: string; version: number }>(),
/** A ticket under this one changed, arrived or left: reload them. */
childrenTouched: type<{ issueId: string }>(),
commentChanged: type<CommentChanged>(),
},
});