feat(ivr): persiste posição dos nós do editor visual entre recargas
Pedido do usuário: "faz a posição dos nós persistir entre recargas" — o editor visual da PHASE 60 recalculava o layout do zero a cada carga da página, perdendo qualquer arrasto manual assim que a tela recarregava. `IvrMenu.entryPositionX/Y` + `IvrMenuOption.positionX/Y` (nullable, puramente de apresentação — nunca entram no dialplan compilado, só no layout do canvas). Salvos no banco, nunca localStorage: mesma convenção do resto do app, estado compartilhado entre quem quer que edite o tenant, não por navegador/dispositivo. "Salvar alterações" agora lê a posição de verdade do estado de nós do @xyflow/react (reflete arrastos feitos na sessão), não do estado de conteúdo das opções — os dois tinham ficado dessincronizados desde a PHASE 60. Sem posição salva ainda (menu novo, opção recém-adicionada), continua caindo num layout automático em coluna. Testado ponta a ponta: PATCH com coordenadas específicas (incluindo o nó "Entrada"), GET de volta confirma os mesmos valores, e a página carregada de novo com uma sessão real (cookie de login, não só a API crua) já embute essas coordenadas nos props iniciais do componente. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { Type } from "class-transformer";
|
||||
import { ArrayMaxSize, ArrayMinSize, IsArray, IsIn, IsOptional, IsString, Matches, MaxLength, ValidateNested } from "class-validator";
|
||||
import { ArrayMaxSize, ArrayMinSize, IsArray, IsIn, IsNumber, IsOptional, IsString, Matches, MaxLength, ValidateNested } from "class-validator";
|
||||
import { ALLOWED_IVR_DIGITS } from "@b2bcall/telephony";
|
||||
import { IsSafeDialplanData } from "../../dialplan/dto/safe-dialplan-data.validator";
|
||||
|
||||
@@ -20,6 +20,16 @@ export class IvrMenuOptionDto {
|
||||
@IsString()
|
||||
@MaxLength(80)
|
||||
label?: string;
|
||||
|
||||
// Posição do nó no editor visual (PHASE 61) — puramente de
|
||||
// apresentação, nunca afeta o dialplan compilado.
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
positionX?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
positionY?: number;
|
||||
}
|
||||
|
||||
export class CreateIvrMenuDto {
|
||||
@@ -66,4 +76,12 @@ export class UpdateIvrMenuDto {
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => IvrMenuOptionDto)
|
||||
options?: IvrMenuOptionDto[];
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
entryPositionX?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
entryPositionY?: number;
|
||||
}
|
||||
|
||||
@@ -147,6 +147,8 @@ export class IvrMenusController {
|
||||
destinationNumber: opt.destinationNumber,
|
||||
destinationContext: opt.destinationContext ?? "default",
|
||||
label: opt.label,
|
||||
positionX: opt.positionX,
|
||||
positionY: opt.positionY,
|
||||
})),
|
||||
});
|
||||
return created;
|
||||
@@ -220,7 +222,12 @@ export class IvrMenusController {
|
||||
const menu = await withTenantContext(prisma, tenantId, async (tx) => {
|
||||
const updated = await tx.ivrMenu.update({
|
||||
where: { id },
|
||||
data: { ...(dto.name !== undefined ? { name: dto.name } : {}), ...(dto.greeting !== undefined ? { greeting: dto.greeting } : {}) },
|
||||
data: {
|
||||
...(dto.name !== undefined ? { name: dto.name } : {}),
|
||||
...(dto.greeting !== undefined ? { greeting: dto.greeting } : {}),
|
||||
...(dto.entryPositionX !== undefined ? { entryPositionX: dto.entryPositionX } : {}),
|
||||
...(dto.entryPositionY !== undefined ? { entryPositionY: dto.entryPositionY } : {}),
|
||||
},
|
||||
});
|
||||
if (dto.options) {
|
||||
await tx.ivrMenuOption.deleteMany({ where: { ivrMenuId: id } });
|
||||
@@ -232,6 +239,8 @@ export class IvrMenusController {
|
||||
destinationNumber: opt.destinationNumber,
|
||||
destinationContext: opt.destinationContext ?? "default",
|
||||
label: opt.label,
|
||||
positionX: opt.positionX,
|
||||
positionY: opt.positionY,
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user