From 26cc79cec2cef567ffa2ce9c14769dfc88ad2fc2 Mon Sep 17 00:00:00 2001
From: Matheus
Date: Sun, 30 Aug 2026 20:13:03 -0300
Subject: [PATCH] =?UTF-8?q?fix(filas):=20adiciona=20edi=C3=A7=C3=A3o=20de?=
=?UTF-8?q?=20fila=20ap=C3=B3s=20cria=C3=A7=C3=A3o?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Achado real reportado pelo usuário: "na fila nao tem opcao de editar a
fila apos a criacao". queues.controller.ts só tinha create/list/delete
— nunca existiu PATCH.
UpdateQueueDto (mesmos campos do create, todos opcionais) + PATCH
/queues/:id. Tela ganhou um botão "Editar" por linha, abrindo um
formulário inline pré-preenchido (reaproveita o mesmo componente do
"Nova fila", só muda entre createQueue/updateQueue).
Testado ponta a ponta com Playwright de verdade (não só a API): abrir o
form de edição confirmando que veio pré-preenchido com os valores reais
da fila, editar nome e espera máxima, salvar, e confirmar que a tabela
atualizou com os novos valores.
Co-Authored-By: Claude Sonnet 5
Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
---
apps/api/src/queues/dto/create-queue.dto.ts | 77 ++++++++++++
apps/api/src/queues/queues.controller.ts | 55 +++++++-
.../src/app/app/callcenter/filas/actions.ts | 14 +++
.../app/app/callcenter/filas/filas-view.tsx | 119 ++++++++++++------
4 files changed, 223 insertions(+), 42 deletions(-)
diff --git a/apps/api/src/queues/dto/create-queue.dto.ts b/apps/api/src/queues/dto/create-queue.dto.ts
index d078ba0..4df2bc1 100644
--- a/apps/api/src/queues/dto/create-queue.dto.ts
+++ b/apps/api/src/queues/dto/create-queue.dto.ts
@@ -86,3 +86,80 @@ export class CreateQueueDto {
@IsBoolean()
recordingEnabled?: boolean;
}
+
+export class UpdateQueueDto {
+ @IsOptional()
+ @IsString()
+ @MaxLength(80)
+ name?: string;
+
+ @IsOptional()
+ @IsString()
+ @MaxLength(255)
+ description?: string;
+
+ @IsOptional()
+ @IsIn(STRATEGIES)
+ strategy?: (typeof STRATEGIES)[number];
+
+ @IsOptional()
+ @IsString()
+ @MaxLength(255)
+ mohSound?: string;
+
+ @IsOptional()
+ @IsString()
+ @MaxLength(255)
+ announceSound?: string;
+
+ @IsOptional()
+ @IsInt()
+ @Min(0)
+ @Max(3600)
+ announceFrequency?: number;
+
+ @IsOptional()
+ @IsInt()
+ @Min(0)
+ @Max(3600)
+ maxWaitTime?: number;
+
+ @IsOptional()
+ @IsInt()
+ @Min(0)
+ @Max(3600)
+ maxWaitTimeWithNoAgent?: number;
+
+ @IsOptional()
+ @IsString()
+ @MaxLength(80)
+ agentNoAnswerStatus?: string;
+
+ @IsOptional()
+ @IsBoolean()
+ tierRulesApply?: boolean;
+
+ @IsOptional()
+ @IsInt()
+ @Min(1)
+ @Max(3600)
+ tierRuleWaitSecond?: number;
+
+ @IsOptional()
+ @IsInt()
+ @Min(0)
+ @Max(3600)
+ discardAbandonedAfter?: number;
+
+ @IsOptional()
+ @IsBoolean()
+ abandonedResumeAllowed?: boolean;
+
+ @IsOptional()
+ @IsBoolean()
+ skipAgentsWithExternalCalls?: boolean;
+
+ @IsOptional()
+ @IsBoolean()
+ recordingEnabled?: boolean;
+}
diff --git a/apps/api/src/queues/queues.controller.ts b/apps/api/src/queues/queues.controller.ts
index 62551f9..57adc16 100644
--- a/apps/api/src/queues/queues.controller.ts
+++ b/apps/api/src/queues/queues.controller.ts
@@ -7,6 +7,7 @@ import {
HttpStatus,
NotFoundException,
Param,
+ Patch,
Post,
UseGuards,
} from "@nestjs/common";
@@ -18,7 +19,7 @@ import { PermissionGuard } from "../common/guards/permission.guard";
import { RequirePermission } from "../common/decorators/require-permission.decorator";
import { CurrentUser } from "../common/decorators/current-user.decorator";
import { getRedisClient } from "../common/redis";
-import { CreateQueueDto } from "./dto/create-queue.dto";
+import { CreateQueueDto, UpdateQueueDto } from "./dto/create-queue.dto";
const QUEUES_SYNC_CHANNEL = "b2bcall:queues:sync";
@@ -101,6 +102,58 @@ export class QueuesController {
return queue;
}
+ /**
+ * Achado real reportado pelo usuário: "na fila nao tem opcao de editar
+ * a fila apos a criacao" — só existia create/delete até aqui.
+ */
+ @RequirePermission("queues.manage")
+ @Patch(":id")
+ async update(@CurrentUser() user: AccessTokenClaims, @Param("id") id: string, @Body() dto: UpdateQueueDto) {
+ const prisma = getPrismaClient();
+ const tenantId = user.tenantId!;
+
+ const result = await withTenantContext(prisma, tenantId, (tx) =>
+ tx.queue.updateMany({
+ where: { id, tenantId, deletedAt: null },
+ data: {
+ ...(dto.name !== undefined ? { name: dto.name } : {}),
+ ...(dto.description !== undefined ? { description: dto.description } : {}),
+ ...(dto.strategy !== undefined ? { strategy: dto.strategy } : {}),
+ ...(dto.mohSound !== undefined ? { mohSound: dto.mohSound } : {}),
+ ...(dto.announceSound !== undefined ? { announceSound: dto.announceSound } : {}),
+ ...(dto.announceFrequency !== undefined ? { announceFrequency: dto.announceFrequency } : {}),
+ ...(dto.maxWaitTime !== undefined ? { maxWaitTime: dto.maxWaitTime } : {}),
+ ...(dto.maxWaitTimeWithNoAgent !== undefined ? { maxWaitTimeWithNoAgent: dto.maxWaitTimeWithNoAgent } : {}),
+ ...(dto.agentNoAnswerStatus !== undefined ? { agentNoAnswerStatus: dto.agentNoAnswerStatus } : {}),
+ ...(dto.tierRulesApply !== undefined ? { tierRulesApply: dto.tierRulesApply } : {}),
+ ...(dto.tierRuleWaitSecond !== undefined ? { tierRuleWaitSecond: dto.tierRuleWaitSecond } : {}),
+ ...(dto.discardAbandonedAfter !== undefined ? { discardAbandonedAfter: dto.discardAbandonedAfter } : {}),
+ ...(dto.abandonedResumeAllowed !== undefined ? { abandonedResumeAllowed: dto.abandonedResumeAllowed } : {}),
+ ...(dto.skipAgentsWithExternalCalls !== undefined ? { skipAgentsWithExternalCalls: dto.skipAgentsWithExternalCalls } : {}),
+ ...(dto.recordingEnabled !== undefined ? { recordingEnabled: dto.recordingEnabled } : {}),
+ },
+ }),
+ );
+ if (result.count === 0) {
+ throw new NotFoundException();
+ }
+
+ const updated = await withTenantContext(prisma, tenantId, (tx) => tx.queue.findFirstOrThrow({ where: { id } }));
+
+ await recordAuditEvent(prisma, {
+ action: "QUEUE_UPDATE",
+ tenantId,
+ userId: user.sub,
+ entityType: "queue",
+ entityId: id,
+ after: { ...dto },
+ });
+
+ await notifyQueuesChanged();
+
+ return updated;
+ }
+
@RequirePermission("queues.manage")
@Delete(":id")
@HttpCode(HttpStatus.NO_CONTENT)
diff --git a/apps/frontend/src/app/app/callcenter/filas/actions.ts b/apps/frontend/src/app/app/callcenter/filas/actions.ts
index a5698b5..71811f4 100644
--- a/apps/frontend/src/app/app/callcenter/filas/actions.ts
+++ b/apps/frontend/src/app/app/callcenter/filas/actions.ts
@@ -40,6 +40,20 @@ export async function createQueue(input: CreateQueueInput): Promise<{ ok: true;
}
}
+export async function updateQueue(
+ id: string,
+ input: CreateQueueInput,
+): Promise<{ ok: true; queue: Queue } | { ok: false; error: string }> {
+ const session = await requireSession();
+ try {
+ const queue = await apiFetch(`/queues/${id}`, session.accessToken, { method: "PATCH", body: JSON.stringify(input) });
+ revalidatePath("/app/callcenter/filas");
+ return { ok: true, queue };
+ } catch (err) {
+ return { ok: false, error: extractErrorMessage(err) };
+ }
+}
+
export async function deleteQueue(id: string): Promise<{ ok: true } | { ok: false; error: string }> {
const session = await requireSession();
try {
diff --git a/apps/frontend/src/app/app/callcenter/filas/filas-view.tsx b/apps/frontend/src/app/app/callcenter/filas/filas-view.tsx
index c6a94ca..cc20083 100644
--- a/apps/frontend/src/app/app/callcenter/filas/filas-view.tsx
+++ b/apps/frontend/src/app/app/callcenter/filas/filas-view.tsx
@@ -1,8 +1,8 @@
"use client";
-import { useState, useTransition } from "react";
+import { Fragment, useState, useTransition } from "react";
import { useRouter } from "next/navigation";
-import { ListTree, Plus, Trash2, X } from "lucide-react";
+import { ListTree, Pencil, Plus, Trash2, X } from "lucide-react";
import { Panel, PanelHeader } from "@/components/ui/panel";
import { Button } from "@/components/ui/button";
import { Input, Select, FieldLabel } from "@/components/ui/input";
@@ -10,10 +10,11 @@ import { Pill } from "@/components/ui/pill";
import { EmptyState, TBody, TD, TH, THead, TR, Table } from "@/components/ui/table";
import { formatDate } from "@/lib/format";
import { QUEUE_STRATEGIES, QUEUE_STRATEGY_LABELS, type Queue } from "@/lib/callcenter-types";
-import { createQueue, deleteQueue } from "./actions";
+import { createQueue, deleteQueue, updateQueue, type CreateQueueInput } from "./actions";
export function FilasView({ queues }: { queues: Queue[] }) {
const [showForm, setShowForm] = useState(false);
+ const [editingId, setEditingId] = useState(null);
return (
@@ -25,13 +26,19 @@ export function FilasView({ queues }: { queues: Queue[] }) {
fila, que distribui pros agentes segundo a estratégia escolhida.
-
)}
-
+
+ {isEditing && (
+
+ Cancelar
+
+ )}
- {pending ? "Criando…" : "Criar fila"}
+ {pending ? "Salvando…" : isEditing ? "Salvar alterações" : "Criar fila"}