Files
CapaKraken/packages/api/src/router/timeline-read-shared.ts
T
Hartmut 1df208dbcc feat(timeline): add pulse animation for in-flight drag mutations
Allocation bars that have active optimistic overrides (post-drag,
awaiting server confirmation) now pulse subtly via animate-pulse.
The pending set is derived from the existing optimisticAllocations
map keys, requiring no additional state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 13:28:46 +02:00

77 lines
2.2 KiB
TypeScript

import { buildSplitAllocationReadModel } from "@capakraken/application";
import type { PrismaClient } from "@capakraken/db";
import { z } from "zod";
import { TimelineWindowFiltersSchema } from "./timeline-read-schema-support.js";
import { loadTimelineEntryRecords } from "./timeline-entry-query-support.js";
export {
buildSelfServiceTimelineInput,
buildTimelineEntriesDetailInput,
createTimelineDateRange,
createTimelineFilters,
} from "./timeline-filter-support.js";
export type ShiftDbClient = Pick<
PrismaClient,
"project" | "demandRequirement" | "assignment"
>;
export type TimelineEntriesDbClient = Pick<
PrismaClient,
"demandRequirement" | "assignment" | "resource" | "project" | "holidayCalendar" | "country" | "metroCity"
>;
export type TimelineEntriesFilters = {
startDate: Date;
endDate: Date;
resourceIds?: string[] | undefined;
projectIds?: string[] | undefined;
clientIds?: string[] | undefined;
chapters?: string[] | undefined;
eids?: string[] | undefined;
countryCodes?: string[] | undefined;
};
type TimelineWindowFiltersInput = z.infer<typeof TimelineWindowFiltersSchema>;
export function getAssignmentResourceIds(
readModel: ReturnType<typeof buildSplitAllocationReadModel>,
): string[] {
return [
...new Set(
readModel.assignments
.map((assignment) => assignment.resourceId)
.filter((resourceId): resourceId is string => resourceId !== null),
),
];
}
export { toIsoDateOrNull as fmtDate } from "@capakraken/shared";
export function createEmptyTimelineEntriesView() {
return buildSplitAllocationReadModel({
demandRequirements: [],
assignments: [],
});
}
export function rangesOverlap(
leftStart: Date,
leftEnd: Date,
rightStart: Date,
rightEnd: Date,
): boolean {
return leftStart <= rightEnd && rightStart <= leftEnd;
}
export function toDate(value: Date | string): Date {
return value instanceof Date ? value : new Date(value);
}
export async function loadTimelineEntriesReadModel(
db: TimelineEntriesDbClient,
input: TimelineEntriesFilters,
) {
const { demandRequirements, assignments } = await loadTimelineEntryRecords(db, input);
return buildSplitAllocationReadModel({ demandRequirements, assignments });
}