first commit

This commit is contained in:
2026-10-03 13:40:26 +02:00
parent 732a5b5a16
commit 566f415fa7
555 changed files with 62704 additions and 0 deletions

View File

@@ -0,0 +1,80 @@
import {
BoardColumn,
BoardFilter,
IssueStatus,
IssueSummary,
} from '@task-stream/web/tracker/domain';
/** Pure board transformations (unit tested). */
export function findIssue(
columns: BoardColumn[],
issueId: string,
): IssueSummary | undefined {
for (const column of columns) {
const issue = column.issues.find((i) => i.id === issueId);
if (issue) return issue;
}
return undefined;
}
export function matchesFilter(
issue: IssueSummary,
filter: BoardFilter | null,
): boolean {
if (!filter) return true;
if (filter.assigneeId && issue.assignee?.id !== filter.assigneeId)
return false;
if (filter.releaseId && issue.release?.id !== filter.releaseId) return false;
if (filter.area && issue.area !== filter.area) return false;
return true;
}
/** Removes the issue wherever it is and puts `next` (if any) on top of its status column. */
export function placeIssue(
columns: BoardColumn[],
issueId: string,
next: IssueSummary | null,
): BoardColumn[] {
return columns.map((column) => {
const had = column.issues.some((i) => i.id === issueId);
let issues = had
? column.issues.filter((i) => i.id !== issueId)
: column.issues;
let total = column.total - (had ? 1 : 0);
if (next && next.status === column.status) {
issues = [next, ...issues];
total += 1;
}
return had || issues !== column.issues
? { ...column, issues, total }
: column;
});
}
/** Optimistic move. */
export function moveIssue(
columns: BoardColumn[],
issueId: string,
to: IssueStatus,
): BoardColumn[] {
const issue = findIssue(columns, issueId);
return issue
? placeIssue(columns, issueId, { ...issue, status: to })
: columns;
}
/** Merges a realtime change, ignoring stale versions and issues outside the filter. */
export function applyIssueChange(
columns: BoardColumn[],
issue: IssueSummary,
filter: BoardFilter | null,
): BoardColumn[] {
const current = findIssue(columns, issue.id);
if (current && current.version > issue.version) return columns;
return placeIssue(
columns,
issue.id,
matchesFilter(issue, filter) ? issue : null,
);
}