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:
@@ -1,7 +1,7 @@
|
||||
import { BlueprintTarget, CreateBlueprintSchema, UpdateBlueprintSchema } from "@capakraken/shared";
|
||||
import { z } from "zod";
|
||||
import { findUniqueOrThrow } from "../db/helpers.js";
|
||||
import { createAuditEntry } from "../lib/audit.js";
|
||||
import { makeAuditLogger } from "../lib/audit-helpers.js";
|
||||
import type { TRPCContext } from "../trpc.js";
|
||||
import {
|
||||
buildBlueprintCreateData,
|
||||
@@ -32,10 +32,6 @@ type BlueprintDetailReadModel = {
|
||||
isActive: boolean;
|
||||
};
|
||||
|
||||
function withAuditUser(userId: string | undefined) {
|
||||
return userId ? { userId } : {};
|
||||
}
|
||||
|
||||
async function getBlueprintOrThrow(ctx: BlueprintProcedureContext, id: string) {
|
||||
return findUniqueOrThrow(ctx.db.blueprint.findUnique({ where: { id } }), "Blueprint");
|
||||
}
|
||||
@@ -131,25 +127,24 @@ export async function getBlueprintDetailByIdentifier(
|
||||
}
|
||||
|
||||
export async function createBlueprint(ctx: BlueprintProcedureContext, input: BlueprintCreateInput) {
|
||||
const audit = makeAuditLogger(ctx.db, ctx.dbUser?.id);
|
||||
const blueprint = await ctx.db.blueprint.create({
|
||||
data: buildBlueprintCreateData(input),
|
||||
});
|
||||
|
||||
void createAuditEntry({
|
||||
db: ctx.db,
|
||||
audit({
|
||||
entityType: "Blueprint",
|
||||
entityId: blueprint.id,
|
||||
entityName: blueprint.name,
|
||||
action: "CREATE",
|
||||
...withAuditUser(ctx.dbUser?.id),
|
||||
after: { name: input.name, target: input.target, description: input.description },
|
||||
source: "ui",
|
||||
});
|
||||
|
||||
return blueprint;
|
||||
}
|
||||
|
||||
export async function updateBlueprint(ctx: BlueprintProcedureContext, input: BlueprintUpdateInput) {
|
||||
const audit = makeAuditLogger(ctx.db, ctx.dbUser?.id);
|
||||
const before = await getBlueprintOrThrow(ctx, input.id);
|
||||
|
||||
const updated = await ctx.db.blueprint.update({
|
||||
@@ -157,16 +152,13 @@ export async function updateBlueprint(ctx: BlueprintProcedureContext, input: Blu
|
||||
data: buildBlueprintUpdateData(input.data),
|
||||
});
|
||||
|
||||
void createAuditEntry({
|
||||
db: ctx.db,
|
||||
audit({
|
||||
entityType: "Blueprint",
|
||||
entityId: input.id,
|
||||
entityName: updated.name,
|
||||
action: "UPDATE",
|
||||
...withAuditUser(ctx.dbUser?.id),
|
||||
before: before as unknown as Record<string, unknown>,
|
||||
after: updated as unknown as Record<string, unknown>,
|
||||
source: "ui",
|
||||
});
|
||||
|
||||
return updated;
|
||||
@@ -176,22 +168,20 @@ export async function updateBlueprintRolePresets(
|
||||
ctx: BlueprintProcedureContext,
|
||||
input: BlueprintRolePresetsInput,
|
||||
) {
|
||||
const audit = makeAuditLogger(ctx.db, ctx.dbUser?.id);
|
||||
const before = await getBlueprintOrThrow(ctx, input.id);
|
||||
const updated = await ctx.db.blueprint.update({
|
||||
where: { id: input.id },
|
||||
data: buildBlueprintRolePresetsUpdateData(input.rolePresets),
|
||||
});
|
||||
|
||||
void createAuditEntry({
|
||||
db: ctx.db,
|
||||
audit({
|
||||
entityType: "Blueprint",
|
||||
entityId: input.id,
|
||||
entityName: updated.name,
|
||||
action: "UPDATE",
|
||||
...withAuditUser(ctx.dbUser?.id),
|
||||
before: { rolePresets: before.rolePresets },
|
||||
after: { rolePresets: input.rolePresets },
|
||||
source: "ui",
|
||||
summary: "Updated role presets",
|
||||
});
|
||||
|
||||
@@ -199,19 +189,17 @@ export async function updateBlueprintRolePresets(
|
||||
}
|
||||
|
||||
export async function deleteBlueprint(ctx: BlueprintProcedureContext, input: BlueprintIdInput) {
|
||||
const audit = makeAuditLogger(ctx.db, ctx.dbUser?.id);
|
||||
const deleted = await ctx.db.blueprint.update({
|
||||
where: { id: input.id },
|
||||
data: { isActive: false },
|
||||
});
|
||||
|
||||
void createAuditEntry({
|
||||
db: ctx.db,
|
||||
audit({
|
||||
entityType: "Blueprint",
|
||||
entityId: input.id,
|
||||
entityName: deleted.name,
|
||||
action: "DELETE",
|
||||
...withAuditUser(ctx.dbUser?.id),
|
||||
source: "ui",
|
||||
});
|
||||
|
||||
return deleted;
|
||||
@@ -221,6 +209,7 @@ export async function batchDeleteBlueprints(
|
||||
ctx: BlueprintProcedureContext,
|
||||
input: BlueprintBatchDeleteInput,
|
||||
) {
|
||||
const audit = makeAuditLogger(ctx.db, ctx.dbUser?.id);
|
||||
const updated = await ctx.db.$transaction(
|
||||
input.ids.map((id) =>
|
||||
ctx.db.blueprint.update({
|
||||
@@ -231,14 +220,11 @@ export async function batchDeleteBlueprints(
|
||||
);
|
||||
|
||||
for (const blueprint of updated) {
|
||||
void createAuditEntry({
|
||||
db: ctx.db,
|
||||
audit({
|
||||
entityType: "Blueprint",
|
||||
entityId: blueprint.id,
|
||||
entityName: blueprint.name,
|
||||
action: "DELETE",
|
||||
...withAuditUser(ctx.dbUser?.id),
|
||||
source: "ui",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -261,20 +247,18 @@ export async function setBlueprintGlobal(
|
||||
ctx: BlueprintProcedureContext,
|
||||
input: BlueprintSetGlobalInput,
|
||||
) {
|
||||
const audit = makeAuditLogger(ctx.db, ctx.dbUser?.id);
|
||||
const updated = await ctx.db.blueprint.update({
|
||||
where: { id: input.id },
|
||||
data: { isGlobal: input.isGlobal },
|
||||
});
|
||||
|
||||
void createAuditEntry({
|
||||
db: ctx.db,
|
||||
audit({
|
||||
entityType: "Blueprint",
|
||||
entityId: input.id,
|
||||
entityName: updated.name,
|
||||
action: "UPDATE",
|
||||
...withAuditUser(ctx.dbUser?.id),
|
||||
after: { isGlobal: input.isGlobal },
|
||||
source: "ui",
|
||||
summary: input.isGlobal ? "Set blueprint as global" : "Removed global flag from blueprint",
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user