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>
This commit is contained in:
2026-04-09 13:28:46 +02:00
parent 7a5e98e2e9
commit 1df208dbcc
386 changed files with 657 additions and 81650 deletions
+5 -35
View File
@@ -1,22 +1,9 @@
import { type WeekdayAvailability } from "@capakraken/shared";
import { type ResourceDailyAvailabilityContext } from "../lib/resource-capacity.js";
const DAY_KEYS: (keyof WeekdayAvailability)[] = [
"sunday",
"monday",
"tuesday",
"wednesday",
"thursday",
"friday",
"saturday",
];
import { toIsoDate, type WeekdayAvailability } from "@capakraken/shared";
export { toIsoDate, round1, averagePerWorkingDay } from "@capakraken/shared";
import { getAvailabilityHoursForDate, calculateEffectiveDayAvailability as _calcEffective, type ResourceDailyAvailabilityContext } from "../lib/resource-capacity.js";
export const ACTIVE_STATUSES = new Set(["PROPOSED", "CONFIRMED", "ACTIVE"]);
export function toIsoDate(value: Date): string {
return value.toISOString().slice(0, 10);
}
function createUtcDate(year: number, monthIndex: number, day: number): Date {
return new Date(Date.UTC(year, monthIndex, day));
}
@@ -48,16 +35,11 @@ export function createDateRange(input: {
return { startDate, endDate };
}
export function round1(value: number): number {
return Math.round(value * 10) / 10;
}
export function getBaseDayAvailability(
availability: WeekdayAvailability,
date: Date,
): number {
const key = DAY_KEYS[date.getUTCDay()];
return key ? (availability[key] ?? 0) : 0;
return getAvailabilityHoursForDate(availability, date);
}
export function getEffectiveDayAvailability(
@@ -65,25 +47,13 @@ export function getEffectiveDayAvailability(
date: Date,
context: ResourceDailyAvailabilityContext | undefined,
): number {
const key = DAY_KEYS[date.getUTCDay()];
const baseHours = key ? (availability[key] ?? 0) : 0;
if (baseHours <= 0) {
return 0;
}
const fraction = context?.absenceFractionsByDate.get(toIsoDate(date)) ?? 0;
return Math.max(0, baseHours * (1 - fraction));
return _calcEffective({ availability, date, context });
}
function overlapsDateRange(startDate: Date, endDate: Date, date: Date): boolean {
return date >= startDate && date <= endDate;
}
export function averagePerWorkingDay(totalHours: number, workingDays: number): number {
if (workingDays <= 0) {
return 0;
}
return round1(totalHours / workingDays);
}
export function createLocationLabel(input: {
countryCode?: string | null;