feat(automations): redesign da acao de enviar e-mail com UX melhorada
- Cria componente EmailActionConfig dedicado para configuracao de e-mail
- Layout expandido (full-width) para melhor aproveitamento do espaco
- Variaveis como badges clicaveis que inserem no campo ativo
- Editor TipTap para mensagem com suporte a variaveis inline
- Autocomplete de variaveis ao digitar {{
- Organizacao visual melhorada com secoes claras
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
e401053667
commit
a4144dd39e
2 changed files with 630 additions and 140 deletions
614
src/components/automations/email-action-config.tsx
Normal file
614
src/components/automations/email-action-config.tsx
Normal file
|
|
@ -0,0 +1,614 @@
|
|||
"use client"
|
||||
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||
import { useEditor, EditorContent } from "@tiptap/react"
|
||||
import type { Editor } from "@tiptap/react"
|
||||
import StarterKit from "@tiptap/starter-kit"
|
||||
import Placeholder from "@tiptap/extension-placeholder"
|
||||
import Mention from "@tiptap/extension-mention"
|
||||
import { ReactRenderer } from "@tiptap/react"
|
||||
import tippy, { type Instance, type Props as TippyProps } from "tippy.js"
|
||||
import { Braces, Trash2 } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Select, SelectContent, SelectEmptyState, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
|
||||
|
||||
// Tipos
|
||||
type EmailCtaTarget = "AUTO" | "PORTAL" | "STAFF"
|
||||
|
||||
type SendEmailAction = {
|
||||
id: string
|
||||
type: "SEND_EMAIL"
|
||||
subject: string
|
||||
message: string
|
||||
toRequester: boolean
|
||||
toAssignee: boolean
|
||||
toUserId: string
|
||||
toEmails: string
|
||||
ctaTarget: EmailCtaTarget
|
||||
ctaLabel: string
|
||||
}
|
||||
|
||||
type Agent = {
|
||||
_id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
type EmailActionConfigProps = {
|
||||
action: SendEmailAction
|
||||
onChange: (action: SendEmailAction) => void
|
||||
onRemove: () => void
|
||||
agents: Agent[]
|
||||
}
|
||||
|
||||
// Variaveis disponiveis para interpolacao
|
||||
const EMAIL_VARIABLES = [
|
||||
{ key: "ticket.reference", label: "Referencia", description: "Numero do chamado (ex: #1234)" },
|
||||
{ key: "ticket.subject", label: "Assunto", description: "Titulo do chamado" },
|
||||
{ key: "ticket.status", label: "Status", description: "Status atual do chamado" },
|
||||
{ key: "ticket.priority", label: "Prioridade", description: "Nivel de prioridade" },
|
||||
{ key: "company.name", label: "Empresa", description: "Nome da empresa do solicitante" },
|
||||
{ key: "requester.name", label: "Solicitante", description: "Nome de quem abriu o chamado" },
|
||||
{ key: "assignee.name", label: "Responsavel", description: "Nome do agente responsavel" },
|
||||
] as const
|
||||
|
||||
type EmailVariable = (typeof EMAIL_VARIABLES)[number]
|
||||
|
||||
const CLEAR_SELECT_VALUE = "__clear__"
|
||||
|
||||
// Estilos do badge de variavel
|
||||
const VARIABLE_BADGE_CLASSES =
|
||||
"inline-flex items-center gap-1 rounded bg-sky-50 border border-sky-200 px-1.5 py-0.5 text-xs font-mono text-sky-700 whitespace-nowrap"
|
||||
|
||||
// Extensao TipTap para variaveis de e-mail
|
||||
const EmailVariableMentionExtension = Mention.extend({
|
||||
name: "emailVariable",
|
||||
priority: 1000,
|
||||
group: "inline",
|
||||
inline: true,
|
||||
selectable: true,
|
||||
atom: true,
|
||||
|
||||
addAttributes() {
|
||||
return {
|
||||
id: { default: null },
|
||||
label: { default: null },
|
||||
}
|
||||
},
|
||||
|
||||
addKeyboardShortcuts() {
|
||||
return {
|
||||
Backspace: ({ editor }: { editor: Editor }) => {
|
||||
const { state } = editor
|
||||
const { selection } = state
|
||||
if (selection.empty) {
|
||||
const { $from } = selection
|
||||
const nodeBefore = $from.nodeBefore
|
||||
if (nodeBefore?.type?.name === this.name) {
|
||||
const from = $from.pos - nodeBefore.nodeSize
|
||||
const to = $from.pos
|
||||
editor.chain().focus().deleteRange({ from, to }).run()
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
return [
|
||||
{
|
||||
tag: `span[data-email-variable]`,
|
||||
getAttrs: (dom: HTMLElement | string) => {
|
||||
if (dom instanceof HTMLElement) {
|
||||
return {
|
||||
id: dom.dataset.variableId ?? null,
|
||||
label: dom.dataset.variableLabel ?? dom.textContent ?? null,
|
||||
}
|
||||
}
|
||||
return {}
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
renderHTML({ HTMLAttributes }: { HTMLAttributes: Record<string, unknown> }) {
|
||||
const id = String(HTMLAttributes.id ?? "")
|
||||
const label = String(HTMLAttributes.label ?? id)
|
||||
return [
|
||||
"span",
|
||||
{
|
||||
"data-email-variable": "true",
|
||||
"data-variable-id": id,
|
||||
"data-variable-label": label,
|
||||
class: VARIABLE_BADGE_CLASSES,
|
||||
contenteditable: "false",
|
||||
},
|
||||
`{{${id}}}`,
|
||||
]
|
||||
},
|
||||
|
||||
addNodeView() {
|
||||
return ({ node }) => {
|
||||
const dom = document.createElement("span")
|
||||
dom.className = VARIABLE_BADGE_CLASSES
|
||||
dom.contentEditable = "false"
|
||||
dom.dataset.emailVariable = "true"
|
||||
dom.dataset.variableId = String(node.attrs.id ?? "")
|
||||
dom.dataset.variableLabel = String(node.attrs.label ?? "")
|
||||
dom.textContent = `{{${node.attrs.id}}}`
|
||||
return { dom }
|
||||
}
|
||||
},
|
||||
}).configure({
|
||||
suggestion: {
|
||||
char: "{{",
|
||||
allowSpaces: false,
|
||||
startOfLine: false,
|
||||
render: () => {
|
||||
let component: ReactRenderer | null = null
|
||||
let popup: Instance<TippyProps> | null = null
|
||||
let keydownHandler: ((event: KeyboardEvent) => boolean) | null = null
|
||||
|
||||
const registerHandler = (handler: ((event: KeyboardEvent) => boolean) | null) => {
|
||||
keydownHandler = handler
|
||||
}
|
||||
|
||||
return {
|
||||
onStart: (props) => {
|
||||
component = new ReactRenderer(EmailVariableList, {
|
||||
props: { ...props, onRegister: registerHandler },
|
||||
editor: props.editor,
|
||||
})
|
||||
|
||||
if (!props.clientRect) return
|
||||
|
||||
popup = tippy(document.body, {
|
||||
getReferenceClientRect: () => props.clientRect?.() ?? new DOMRect(),
|
||||
appendTo: () => document.body,
|
||||
content: component.element,
|
||||
showOnCreate: true,
|
||||
interactive: true,
|
||||
trigger: "manual",
|
||||
placement: "bottom-start",
|
||||
zIndex: 99999,
|
||||
})
|
||||
},
|
||||
onUpdate(props) {
|
||||
component?.updateProps({ ...props, onRegister: registerHandler })
|
||||
if (!props.clientRect) return
|
||||
popup?.setProps({
|
||||
getReferenceClientRect: () => props.clientRect?.() ?? new DOMRect(),
|
||||
})
|
||||
},
|
||||
onKeyDown(props) {
|
||||
if (props.event.key === "Escape") {
|
||||
popup?.hide()
|
||||
return true
|
||||
}
|
||||
if (keydownHandler?.(props.event)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
onExit() {
|
||||
popup?.destroy()
|
||||
component?.destroy()
|
||||
keydownHandler = null
|
||||
},
|
||||
}
|
||||
},
|
||||
items: ({ query }) => {
|
||||
const q = query.toLowerCase().replace(/^\{*/, "")
|
||||
return EMAIL_VARIABLES.filter(
|
||||
(v) =>
|
||||
v.key.toLowerCase().includes(q) ||
|
||||
v.label.toLowerCase().includes(q) ||
|
||||
v.description.toLowerCase().includes(q)
|
||||
)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// Lista de sugestoes de variaveis
|
||||
type EmailVariableListProps = {
|
||||
items: EmailVariable[]
|
||||
command: (item: { id: string; label: string }) => void
|
||||
onRegister?: (handler: ((event: KeyboardEvent) => boolean) | null) => void
|
||||
}
|
||||
|
||||
function EmailVariableList({ items, command, onRegister }: EmailVariableListProps) {
|
||||
const [selectedIndex, setSelectedIndex] = useState(0)
|
||||
|
||||
const selectItem = useCallback(
|
||||
(index: number) => {
|
||||
const item = items[index]
|
||||
if (item) {
|
||||
command({ id: item.key, label: item.label })
|
||||
}
|
||||
},
|
||||
[command, items]
|
||||
)
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(event: KeyboardEvent) => {
|
||||
if (event.key === "ArrowUp") {
|
||||
setSelectedIndex((prev) => (prev + items.length - 1) % items.length)
|
||||
return true
|
||||
}
|
||||
if (event.key === "ArrowDown") {
|
||||
setSelectedIndex((prev) => (prev + 1) % items.length)
|
||||
return true
|
||||
}
|
||||
if (event.key === "Enter") {
|
||||
selectItem(selectedIndex)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
[items.length, selectItem, selectedIndex]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
onRegister?.(handleKeyDown)
|
||||
return () => onRegister?.(null)
|
||||
}, [handleKeyDown, onRegister])
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedIndex(0)
|
||||
}, [items])
|
||||
|
||||
if (!items.length) {
|
||||
return (
|
||||
<div className="rounded-lg border bg-white p-3 text-sm text-muted-foreground shadow-lg">
|
||||
Nenhuma variavel encontrada
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-h-64 min-w-[280px] overflow-y-auto rounded-lg border bg-white p-1 shadow-lg">
|
||||
{items.map((item, index) => (
|
||||
<button
|
||||
key={item.key}
|
||||
type="button"
|
||||
className={cn(
|
||||
"flex w-full flex-col gap-0.5 rounded-md px-3 py-2 text-left transition",
|
||||
index === selectedIndex ? "bg-sky-50" : "hover:bg-slate-50"
|
||||
)}
|
||||
onMouseEnter={() => setSelectedIndex(index)}
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault()
|
||||
selectItem(index)
|
||||
}}
|
||||
>
|
||||
<span className="font-mono text-sm text-sky-700">{`{{${item.key}}}`}</span>
|
||||
<span className="text-xs text-muted-foreground">{item.description}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Converte HTML com badges para texto com {{variaveis}}
|
||||
function htmlToPlainVariables(html: string): string {
|
||||
if (!html) return ""
|
||||
// Remove tags HTML mantendo o texto
|
||||
const div = document.createElement("div")
|
||||
div.innerHTML = html
|
||||
// Badges de variavel ja tem o texto {{variavel}} dentro
|
||||
return div.textContent ?? ""
|
||||
}
|
||||
|
||||
// Converte texto com {{variaveis}} para HTML com badges
|
||||
function plainVariablesToHtml(text: string): string {
|
||||
if (!text) return ""
|
||||
// Escapa HTML primeiro
|
||||
const escaped = text
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
// Converte {{var}} para badges
|
||||
return escaped.replace(/\{\{([^}]+)\}\}/g, (_, varKey) => {
|
||||
const variable = EMAIL_VARIABLES.find((v) => v.key === varKey)
|
||||
const label = variable?.label ?? varKey
|
||||
return `<span data-email-variable="true" data-variable-id="${varKey}" data-variable-label="${label}" class="${VARIABLE_BADGE_CLASSES}">{{${varKey}}}</span>`
|
||||
})
|
||||
}
|
||||
|
||||
// Editor de mensagem com suporte a variaveis
|
||||
type MessageEditorProps = {
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
onInsertVariable: (ref: { insertVariable: (key: string) => void } | null) => void
|
||||
}
|
||||
|
||||
function MessageEditor({ value, onChange, onInsertVariable }: MessageEditorProps) {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- initialContent deve ser calculado apenas uma vez na montagem
|
||||
const initialContent = useMemo(() => plainVariablesToHtml(value), [])
|
||||
|
||||
const editor = useEditor({
|
||||
extensions: [
|
||||
StarterKit.configure({
|
||||
bulletList: false,
|
||||
orderedList: false,
|
||||
blockquote: false,
|
||||
codeBlock: false,
|
||||
heading: false,
|
||||
horizontalRule: false,
|
||||
}),
|
||||
Placeholder.configure({ placeholder: "Escreva a mensagem do e-mail..." }),
|
||||
EmailVariableMentionExtension,
|
||||
],
|
||||
content: initialContent,
|
||||
editorProps: {
|
||||
attributes: {
|
||||
class: "prose prose-sm max-w-none focus:outline-none min-h-[100px] p-3 text-sm",
|
||||
},
|
||||
},
|
||||
onUpdate({ editor }) {
|
||||
const html = editor.getHTML()
|
||||
const plain = htmlToPlainVariables(html)
|
||||
onChange(plain)
|
||||
},
|
||||
immediatelyRender: false,
|
||||
})
|
||||
|
||||
// Expoe metodo para inserir variavel externamente
|
||||
useEffect(() => {
|
||||
if (!editor) {
|
||||
onInsertVariable(null)
|
||||
return
|
||||
}
|
||||
|
||||
onInsertVariable({
|
||||
insertVariable: (key: string) => {
|
||||
const variable = EMAIL_VARIABLES.find((v) => v.key === key)
|
||||
if (!variable) return
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.insertContent({
|
||||
type: "emailVariable",
|
||||
attrs: { id: key, label: variable.label },
|
||||
})
|
||||
.run()
|
||||
},
|
||||
})
|
||||
|
||||
return () => onInsertVariable(null)
|
||||
}, [editor, onInsertVariable])
|
||||
|
||||
// Sincroniza valor externo
|
||||
useEffect(() => {
|
||||
if (!editor) return
|
||||
const currentPlain = htmlToPlainVariables(editor.getHTML())
|
||||
if (currentPlain !== value) {
|
||||
editor.commands.setContent(plainVariablesToHtml(value), { emitUpdate: false })
|
||||
}
|
||||
}, [value, editor])
|
||||
|
||||
if (!editor) return null
|
||||
|
||||
return (
|
||||
<div className="rounded-lg border bg-white focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2">
|
||||
<EditorContent editor={editor} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Componente principal
|
||||
export function EmailActionConfig({ action, onChange, onRemove, agents }: EmailActionConfigProps) {
|
||||
const subjectInputRef = useRef<HTMLInputElement>(null)
|
||||
const messageEditorRef = useRef<{ insertVariable: (key: string) => void } | null>(null)
|
||||
const [activeField, setActiveField] = useState<"subject" | "message">("message")
|
||||
|
||||
const handleChange = useCallback(
|
||||
<K extends keyof SendEmailAction>(key: K, value: SendEmailAction[K]) => {
|
||||
onChange({ ...action, [key]: value })
|
||||
},
|
||||
[action, onChange]
|
||||
)
|
||||
|
||||
const handleInsertVariable = useCallback(
|
||||
(variable: EmailVariable) => {
|
||||
if (activeField === "subject" && subjectInputRef.current) {
|
||||
const input = subjectInputRef.current
|
||||
const start = input.selectionStart ?? input.value.length
|
||||
const end = input.selectionEnd ?? input.value.length
|
||||
const varText = `{{${variable.key}}}`
|
||||
const newValue = input.value.slice(0, start) + varText + input.value.slice(end)
|
||||
handleChange("subject", newValue)
|
||||
// Reposiciona cursor apos a variavel
|
||||
requestAnimationFrame(() => {
|
||||
input.focus()
|
||||
const newPos = start + varText.length
|
||||
input.setSelectionRange(newPos, newPos)
|
||||
})
|
||||
} else if (activeField === "message" && messageEditorRef.current) {
|
||||
messageEditorRef.current.insertVariable(variable.key)
|
||||
}
|
||||
},
|
||||
[activeField, handleChange]
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Header com tipo e botao de remover */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex size-8 items-center justify-center rounded-lg bg-sky-100">
|
||||
<Braces className="size-4 text-sky-600" />
|
||||
</div>
|
||||
<span className="font-medium text-neutral-900">Enviar e-mail</span>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={onRemove}
|
||||
className="h-8 w-8 text-slate-500 hover:bg-red-50 hover:text-red-700"
|
||||
title="Remover acao"
|
||||
>
|
||||
<Trash2 className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Assunto */}
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-sm font-medium">Assunto do e-mail</Label>
|
||||
<Input
|
||||
ref={subjectInputRef}
|
||||
value={action.subject}
|
||||
onChange={(e) => handleChange("subject", e.target.value)}
|
||||
onFocus={() => setActiveField("subject")}
|
||||
placeholder="Ex.: Atualizacao do chamado #{{ticket.reference}}"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Mensagem */}
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-sm font-medium">Mensagem</Label>
|
||||
<div onFocus={() => setActiveField("message")}>
|
||||
<MessageEditor
|
||||
value={action.message}
|
||||
onChange={(v) => handleChange("message", v)}
|
||||
onInsertVariable={(ref) => {
|
||||
messageEditorRef.current = ref
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Paleta de variaveis */}
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xs text-muted-foreground">
|
||||
Variaveis disponiveis (clique para inserir no {activeField === "subject" ? "assunto" : "corpo"})
|
||||
</Label>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{EMAIL_VARIABLES.map((variable) => (
|
||||
<Tooltip key={variable.key}>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleInsertVariable(variable)}
|
||||
className={cn(
|
||||
VARIABLE_BADGE_CLASSES,
|
||||
"cursor-pointer transition hover:bg-sky-100 hover:border-sky-300"
|
||||
)}
|
||||
>
|
||||
{`{{${variable.key}}}`}
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" className="text-xs">
|
||||
{variable.description}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Destinatarios */}
|
||||
<div className="space-y-3 rounded-lg border border-slate-200 bg-slate-50/50 p-4">
|
||||
<Label className="text-sm font-medium">Destinatarios</Label>
|
||||
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<label className="flex items-center gap-2 text-sm text-neutral-700">
|
||||
<Checkbox
|
||||
checked={action.toRequester}
|
||||
onCheckedChange={(checked) => handleChange("toRequester", Boolean(checked))}
|
||||
/>
|
||||
Solicitante do ticket
|
||||
</label>
|
||||
<label className="flex items-center gap-2 text-sm text-neutral-700">
|
||||
<Checkbox
|
||||
checked={action.toAssignee}
|
||||
onCheckedChange={(checked) => handleChange("toAssignee", Boolean(checked))}
|
||||
/>
|
||||
Responsavel do ticket
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-xs text-muted-foreground">Agente especifico (opcional)</Label>
|
||||
<Select
|
||||
value={action.toUserId}
|
||||
onValueChange={(value) => {
|
||||
const nextValue = value === CLEAR_SELECT_VALUE ? "" : value
|
||||
handleChange("toUserId", nextValue)
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="bg-white">
|
||||
<SelectValue placeholder="Selecione" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="rounded-xl">
|
||||
{agents.length === 0 ? (
|
||||
<SelectEmptyState
|
||||
message="Nenhum agente disponivel"
|
||||
createLabel="Gerenciar usuarios"
|
||||
createHref="/admin/users"
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<SelectItem value={CLEAR_SELECT_VALUE}>Nenhum</SelectItem>
|
||||
{agents.map((u) => (
|
||||
<SelectItem key={u._id} value={String(u._id)}>
|
||||
{u.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-xs text-muted-foreground">E-mails adicionais</Label>
|
||||
<Input
|
||||
value={action.toEmails}
|
||||
onChange={(e) => handleChange("toEmails", e.target.value)}
|
||||
placeholder="cliente@empresa.com, outro@dominio.com"
|
||||
className="bg-white"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Botao do e-mail */}
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-xs text-muted-foreground">Link do botao</Label>
|
||||
<Select
|
||||
value={action.ctaTarget}
|
||||
onValueChange={(value) => handleChange("ctaTarget", value as EmailCtaTarget)}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="rounded-xl">
|
||||
<SelectItem value="AUTO">Auto (detecta pelo destinatario)</SelectItem>
|
||||
<SelectItem value="PORTAL">Portal (cliente)</SelectItem>
|
||||
<SelectItem value="STAFF">Painel (agente)</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-xs text-muted-foreground">Texto do botao</Label>
|
||||
<Input
|
||||
value={action.ctaLabel}
|
||||
onChange={(e) => handleChange("ctaLabel", e.target.value)}
|
||||
placeholder="Abrir chamado"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue