- Replaced inline drawing logic for task comments with a new `drawCommentBubble` function to enhance readability and maintainability. - The new function encapsulates the drawing of a speech-bubble icon, including the rounded rectangle body, tail, and inner dots to suggest text.
51 lines
1.6 KiB
TypeScript
51 lines
1.6 KiB
TypeScript
const TASK_SINCE_GRACE_MS = 2 * 60 * 1000;
|
|
|
|
type TaskChangeIntervalLike = {
|
|
startedAt?: string | null;
|
|
};
|
|
|
|
type TaskChangeHistoryEventLike = {
|
|
timestamp?: string | null;
|
|
};
|
|
|
|
export interface TaskChangeSinceLike<
|
|
TInterval extends TaskChangeIntervalLike = TaskChangeIntervalLike,
|
|
THistoryEvent extends TaskChangeHistoryEventLike = TaskChangeHistoryEventLike,
|
|
> {
|
|
createdAt?: string | null;
|
|
workIntervals?: TInterval[] | null;
|
|
historyEvents?: THistoryEvent[] | null;
|
|
}
|
|
|
|
export function deriveTaskSince<
|
|
TInterval extends TaskChangeIntervalLike,
|
|
THistoryEvent extends TaskChangeHistoryEventLike,
|
|
>(task: TaskChangeSinceLike<TInterval, THistoryEvent> | null | undefined): string | undefined {
|
|
if (!task) return undefined;
|
|
|
|
const sources: string[] = [];
|
|
if (typeof task.createdAt === 'string' && task.createdAt.length > 0) {
|
|
sources.push(task.createdAt);
|
|
}
|
|
if (Array.isArray(task.workIntervals)) {
|
|
for (const interval of task.workIntervals) {
|
|
if (typeof interval?.startedAt === 'string' && interval.startedAt.length > 0) {
|
|
sources.push(interval.startedAt);
|
|
}
|
|
}
|
|
}
|
|
if (Array.isArray(task.historyEvents)) {
|
|
for (const event of task.historyEvents) {
|
|
if (typeof event?.timestamp === 'string' && event.timestamp.length > 0) {
|
|
sources.push(event.timestamp);
|
|
}
|
|
}
|
|
}
|
|
if (sources.length === 0) return undefined;
|
|
|
|
const [first, ...rest] = sources;
|
|
const earliest = rest.reduce((a, b) => (a < b ? a : b), first);
|
|
const date = new Date(earliest);
|
|
date.setTime(date.getTime() - TASK_SINCE_GRACE_MS);
|
|
return date.toISOString();
|
|
}
|