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:
2026-08-30 18:33:19 -03:00
parent 0148b926a0
commit 5bbe9e6800
9 changed files with 130 additions and 20 deletions

View File

@@ -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;
}