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
+1
View File
@@ -6,6 +6,7 @@ export { updateDemandRequirement } from "./use-cases/allocation/update-demand-re
export {
createAssignment,
createAssignmentFragment,
type AssignmentWithRelations,
} from "./use-cases/allocation/create-assignment.js";
export { updateAssignment } from "./use-cases/allocation/update-assignment.js";
@@ -1,7 +1,5 @@
import { VacationStatus } from "@capakraken/db";
import { getPublicHolidays, type WeekdayAvailability } from "@capakraken/shared";
const MILLISECONDS_PER_DAY = 86_400_000;
import { getPublicHolidays, toIsoDate, MILLISECONDS_PER_DAY, DAY_KEYS, normalizeCityName, normalizeStateCode, type WeekdayAvailability } from "@capakraken/shared";
type CalendarScope = "COUNTRY" | "STATE" | "CITY";
@@ -54,16 +52,6 @@ type ResourceCapacityDbClient = {
};
};
const DAY_KEYS: (keyof WeekdayAvailability)[] = [
"sunday",
"monday",
"tuesday",
"wednesday",
"thursday",
"friday",
"saturday",
];
const CITY_HOLIDAY_RULES: Array<{
countryCode: string;
cityName: string;
@@ -76,20 +64,6 @@ const CITY_HOLIDAY_RULES: Array<{
},
];
function toIsoDate(value: Date): string {
return value.toISOString().slice(0, 10);
}
function normalizeCityName(cityName?: string | null): string | null {
const normalized = cityName?.trim().toLowerCase();
return normalized && normalized.length > 0 ? normalized : null;
}
function normalizeStateCode(stateCode?: string | null): string | null {
const normalized = stateCode?.trim().toUpperCase();
return normalized && normalized.length > 0 ? normalized : null;
}
export function getAvailabilityHoursForDate(
availability: WeekdayAvailability,
date: Date,
@@ -1,5 +1,5 @@
import type { PrismaClient } from "@capakraken/db";
import type { WeekdayAvailability } from "@capakraken/shared";
import { toIsoDate, DAY_KEYS, type WeekdayAvailability } from "@capakraken/shared";
import { calculateInclusiveDays, MILLISECONDS_PER_DAY } from "./shared.js";
import {
calculateEffectiveAllocationCostCents,
@@ -46,20 +46,6 @@ export interface BudgetForecastRow {
derivation?: BudgetForecastDerivationSummary;
}
const DAY_KEYS: (keyof WeekdayAvailability)[] = [
"sunday",
"monday",
"tuesday",
"wednesday",
"thursday",
"friday",
"saturday",
];
function toIsoDate(value: Date): string {
return value.toISOString().slice(0, 10);
}
function getDailyAvailabilityHours(
availability: WeekdayAvailability,
date: Date,
@@ -1,5 +1,5 @@
import type { PrismaClient } from "@capakraken/db";
import type { WeekdayAvailability } from "@capakraken/shared";
import { toIsoDate, DAY_KEYS, type WeekdayAvailability } from "@capakraken/shared";
import {
isChargeabilityActualBooking,
isChargeabilityRelevantProject,
@@ -12,16 +12,6 @@ import {
type DailyAvailabilityContext,
} from "./holiday-capacity.js";
const DAY_KEYS: (keyof WeekdayAvailability)[] = [
"sunday",
"monday",
"tuesday",
"wednesday",
"thursday",
"friday",
"saturday",
];
export interface GetDashboardChargeabilityOverviewInput {
includeProposed?: boolean;
topN: number;
@@ -72,10 +62,6 @@ export interface DashboardChargeabilityOverview {
month: string;
}
function toIsoDate(value: Date): string {
return value.toISOString().slice(0, 10);
}
function getDailyAvailabilityHours(
availability: WeekdayAvailability,
date: Date,
@@ -1,5 +1,5 @@
import type { PrismaClient } from "@capakraken/db";
import type { WeekdayAvailability } from "@capakraken/shared";
import { toIsoDate, type WeekdayAvailability } from "@capakraken/shared";
import { loadDashboardPlanningReadModel } from "./load-dashboard-planning-read-model.js";
import { calculateAllocationHours } from "./shared.js";
import {
@@ -66,10 +66,6 @@ function toDate(value: Date | string): Date {
return value instanceof Date ? value : new Date(value);
}
function toIsoDate(value: Date): string {
return value.toISOString().slice(0, 10);
}
function buildLocationKey(input: {
countryCode: string | null | undefined;
countryName: string | null | undefined;
@@ -1,5 +1,5 @@
import type { PrismaClient } from "@capakraken/db";
import type { WeekdayAvailability } from "@capakraken/shared";
import { toIsoDate, DAY_KEYS, type WeekdayAvailability } from "@capakraken/shared";
import { listAssignmentBookings } from "../allocation/list-assignment-bookings.js";
import { getMonthBucketKey, getWeekBucketKey } from "./shared.js";
import {
@@ -81,20 +81,6 @@ type PeakTimesCapacityDerivationSummary = Pick<
| "calendarLocations"
>;
const DAY_KEYS: (keyof WeekdayAvailability)[] = [
"sunday",
"monday",
"tuesday",
"wednesday",
"thursday",
"friday",
"saturday",
];
function toIsoDate(value: Date): string {
return value.toISOString().slice(0, 10);
}
function round1(value: number): number {
return Math.round(value * 10) / 10;
}
@@ -1,5 +1,5 @@
import type { PrismaClient } from "@capakraken/db";
import type { WeekdayAvailability } from "@capakraken/shared";
import { toIsoDate, MILLISECONDS_PER_DAY, DAY_KEYS, type WeekdayAvailability } from "@capakraken/shared";
import { calculateInclusiveDays } from "./shared.js";
import {
calculateEffectiveAllocationCostCents,
@@ -51,26 +51,12 @@ export interface ProjectHealthRow {
};
}
const DAY_KEYS: (keyof WeekdayAvailability)[] = [
"sunday",
"monday",
"tuesday",
"wednesday",
"thursday",
"friday",
"saturday",
];
function hasAvailability<T extends { availability?: unknown }>(
resource: T | null | undefined,
): resource is T & { availability: WeekdayAvailability } {
return resource !== null && resource !== undefined && resource.availability !== null && resource.availability !== undefined;
}
function toIsoDate(value: Date): string {
return value.toISOString().slice(0, 10);
}
function getDailyAvailabilityHours(
availability: WeekdayAvailability,
date: Date,
@@ -360,7 +346,7 @@ export async function getDashboardProjectHealth(
const endDate = p.endDate ? toUtcDayStart(p.endDate) : null;
const today = toUtcDayStart(now);
const daysUntilEndDate = endDate
? Math.round((endDate.getTime() - today.getTime()) / 86_400_000)
? Math.round((endDate.getTime() - today.getTime()) / MILLISECONDS_PER_DAY)
: null;
const timelineStatus = endDate === null
? "UNSCHEDULED"
@@ -1,4 +1,5 @@
export const MILLISECONDS_PER_DAY = 86_400_000;
export { MILLISECONDS_PER_DAY } from "@capakraken/shared";
import { MILLISECONDS_PER_DAY } from "@capakraken/shared";
export function calculateInclusiveDays(startDate: Date, endDate: Date): number {
return (endDate.getTime() - startDate.getTime()) / MILLISECONDS_PER_DAY + 1;