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,5 +1,5 @@
import {
createAssignment,
createAssignmentFragment,
deleteAllocationEntry,
loadAllocationEntry,
updateAssignment,
@@ -162,8 +162,8 @@ export async function carveTimelineAllocationRange(input: {
updatedAllocationIds.push(updated.id);
if (hasLeftFragment && hasRightFragment) {
const created = await createAssignment(
tx as unknown as Parameters<typeof createAssignment>[0],
const created = await createAssignmentFragment(
tx as unknown as Parameters<typeof createAssignmentFragment>[0],
{
demandRequirementId: assignment.demandRequirementId ?? undefined,
resourceId: assignment.resourceId,
@@ -256,46 +256,30 @@ export async function extractTimelineAllocationFragment(input: {
},
);
if (hasLeftFragment) {
const createdLeft = await createAssignment(
tx as unknown as Parameters<typeof createAssignment>[0],
{
demandRequirementId: assignment.demandRequirementId ?? undefined,
resourceId: assignment.resourceId,
projectId: assignment.projectId,
startDate: assignmentStart,
endDate: addDays(extractStart, -1),
hoursPerDay: assignment.hoursPerDay,
percentage: assignment.percentage,
role: assignment.role ?? undefined,
roleId: assignment.roleId ?? undefined,
dailyCostCents: assignment.dailyCostCents,
status: toSharedAllocationStatus(assignment.status),
metadata,
},
);
createdAllocationIds.push(createdLeft.id);
}
const fragmentBase = {
demandRequirementId: assignment.demandRequirementId ?? undefined,
resourceId: assignment.resourceId,
projectId: assignment.projectId,
hoursPerDay: assignment.hoursPerDay,
percentage: assignment.percentage,
role: assignment.role ?? undefined,
roleId: assignment.roleId ?? undefined,
dailyCostCents: assignment.dailyCostCents,
status: toSharedAllocationStatus(assignment.status),
metadata,
};
const txClient = tx as unknown as Parameters<typeof createAssignmentFragment>[0];
if (hasRightFragment) {
const createdRight = await createAssignment(
tx as unknown as Parameters<typeof createAssignment>[0],
{
demandRequirementId: assignment.demandRequirementId ?? undefined,
resourceId: assignment.resourceId,
projectId: assignment.projectId,
startDate: addDays(extractEnd, 1),
endDate: assignmentEnd,
hoursPerDay: assignment.hoursPerDay,
percentage: assignment.percentage,
role: assignment.role ?? undefined,
roleId: assignment.roleId ?? undefined,
dailyCostCents: assignment.dailyCostCents,
status: toSharedAllocationStatus(assignment.status),
metadata,
},
);
createdAllocationIds.push(createdRight.id);
const fragments = await Promise.all([
hasLeftFragment
? createAssignmentFragment(txClient, { ...fragmentBase, startDate: assignmentStart, endDate: addDays(extractStart, -1) })
: null,
hasRightFragment
? createAssignmentFragment(txClient, { ...fragmentBase, startDate: addDays(extractEnd, 1), endDate: assignmentEnd })
: null,
]);
for (const frag of fragments) {
if (frag) createdAllocationIds.push(frag.id);
}
return {