@fayz-ai/plugin-automations 0.9.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/data/templates.d.ts +9 -0
- package/dist/data/templates.d.ts.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +561 -108
- package/dist/index.js.map +1 -1
- package/dist/lib/runtime.d.ts +4 -0
- package/dist/lib/runtime.d.ts.map +1 -0
- package/dist/views/AutomationDetailPage.d.ts +12 -0
- package/dist/views/AutomationDetailPage.d.ts.map +1 -0
- package/dist/views/AutomationsList.d.ts +6 -0
- package/dist/views/AutomationsList.d.ts.map +1 -0
- package/dist/views/RunLog.d.ts +8 -0
- package/dist/views/RunLog.d.ts.map +1 -0
- package/package.json +5 -6
- package/dist/views/AutomationsHome.d.ts +0 -3
- package/dist/views/AutomationsHome.d.ts.map +0 -1
- package/src/index.ts +0 -52
- package/src/views/AutomationsHome.tsx +0 -112
package/dist/index.js
CHANGED
|
@@ -1,144 +1,597 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
1
|
+
import React3 from 'react';
|
|
2
|
+
import { startEventLog, getSupabaseClientOptional, usePluginRuntimeOptional, resolveMessages, resolveMessage, renderMessageTemplate, loadEventRuns } from '@fayz-ai/core';
|
|
3
|
+
import * as LucideIcons from 'lucide-react';
|
|
4
|
+
import { Zap, MessageSquare, Smartphone, MessageCircle, Mail, ChevronRight, RotateCcw, Clock, Monitor, RefreshCw, History, AlertTriangle, CircleSlash, CircleCheck } from 'lucide-react';
|
|
5
|
+
import { HeaderTabs, Card, Switch, SubpageHeader, Button, Badge, Hint, toast } from '@fayz-ai/ui';
|
|
6
|
+
import { adminNavigateTo, useOrganizationStore } from '@fayz-ai/admin';
|
|
7
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
5
8
|
|
|
6
9
|
// src/index.ts
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
{ icon: MessageSquare, label: "Text back" },
|
|
29
|
-
{ icon: Tag, label: "Create task" }
|
|
30
|
-
]
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
id: "3",
|
|
34
|
-
name: "Appointment reminder",
|
|
35
|
-
trigger: "Appointment booked",
|
|
36
|
-
enrolled: 87,
|
|
37
|
-
active: true,
|
|
38
|
-
steps: [
|
|
39
|
-
{ icon: Clock, label: "Wait until 24h before" },
|
|
40
|
-
{ icon: MessageSquare, label: "WhatsApp reminder" }
|
|
41
|
-
]
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
id: "4",
|
|
45
|
-
name: "Won deal \u2192 review request",
|
|
46
|
-
trigger: "Deal marked Won",
|
|
47
|
-
enrolled: 41,
|
|
48
|
-
active: false,
|
|
49
|
-
steps: [
|
|
50
|
-
{ icon: Clock, label: "Wait 2 days" },
|
|
51
|
-
{ icon: Mail, label: "Ask for review" }
|
|
52
|
-
]
|
|
53
|
-
}
|
|
10
|
+
function useOrganizationStoreLike() {
|
|
11
|
+
return useOrganizationStore((s) => s.currentOrg?.id);
|
|
12
|
+
}
|
|
13
|
+
var CHANNEL_ICON = {
|
|
14
|
+
email: Mail,
|
|
15
|
+
whatsapp: MessageCircle,
|
|
16
|
+
sms: Smartphone,
|
|
17
|
+
push: MessageSquare,
|
|
18
|
+
webchat: MessageSquare
|
|
19
|
+
};
|
|
20
|
+
var CHANNEL_LABEL = {
|
|
21
|
+
email: "E-mail",
|
|
22
|
+
whatsapp: "WhatsApp",
|
|
23
|
+
sms: "SMS",
|
|
24
|
+
push: "Push",
|
|
25
|
+
webchat: "Chat"
|
|
26
|
+
};
|
|
27
|
+
var KIND_TABS = [
|
|
28
|
+
{ value: "all", label: "Todos" },
|
|
29
|
+
{ value: "transactional", label: "Transacionais" },
|
|
30
|
+
{ value: "marketing", label: "Marketing" }
|
|
54
31
|
];
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
32
|
+
var KIND_HINT = {
|
|
33
|
+
all: "Tudo que o produto sabe enviar, por qualquer canal.",
|
|
34
|
+
transactional: "Disparados por uma a\xE7\xE3o da pessoa. N\xE3o precisam de opt-in.",
|
|
35
|
+
marketing: "Iniciativa da empresa. S\xF3 para quem aceitou receber."
|
|
36
|
+
};
|
|
37
|
+
function iconFor(name) {
|
|
38
|
+
if (!name) return Zap;
|
|
39
|
+
const icons = LucideIcons;
|
|
40
|
+
return icons[name] ?? Zap;
|
|
60
41
|
}
|
|
61
|
-
function
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
42
|
+
function AutomationsList({ store }) {
|
|
43
|
+
const tenantId = useOrganizationStoreLike();
|
|
44
|
+
const runtime = usePluginRuntimeOptional();
|
|
45
|
+
const [messages, setMessages] = React3.useState(null);
|
|
46
|
+
const [kind, setKind] = React3.useState("all");
|
|
47
|
+
const reload = React3.useCallback(async () => {
|
|
48
|
+
let overrides = [];
|
|
49
|
+
try {
|
|
50
|
+
overrides = await store.list(tenantId);
|
|
51
|
+
} catch (err) {
|
|
52
|
+
console.warn("[automations] could not load template overrides; showing defaults", err);
|
|
53
|
+
}
|
|
54
|
+
setMessages(resolveMessages(overrides, runtime?.events ?? []));
|
|
55
|
+
}, [store, tenantId, runtime]);
|
|
56
|
+
React3.useEffect(() => {
|
|
57
|
+
void reload();
|
|
58
|
+
}, [reload]);
|
|
59
|
+
const toggle = async (m, enabled) => {
|
|
60
|
+
setMessages((prev) => prev?.map((x) => x.event.name === m.event.name && x.channel === m.channel ? { ...x, enabled } : x) ?? null);
|
|
61
|
+
try {
|
|
62
|
+
await store.upsert({ eventKey: m.event.name, channel: m.channel, enabled }, tenantId);
|
|
63
|
+
await reload();
|
|
64
|
+
} catch (err) {
|
|
65
|
+
console.warn("[automations] toggle failed", err);
|
|
66
|
+
toast.error("N\xE3o foi poss\xEDvel salvar");
|
|
67
|
+
await reload();
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const visible = (messages ?? []).filter((m) => kind === "all" || m.kind === kind);
|
|
71
|
+
const activeCount = (messages ?? []).filter((m) => m.enabled).length;
|
|
72
|
+
const groups = [];
|
|
73
|
+
for (const m of visible) {
|
|
74
|
+
let g = groups.find((x) => x.module === m.event.pluginId);
|
|
75
|
+
if (!g) {
|
|
76
|
+
g = { module: m.event.pluginId, label: m.event.pluginName, items: [] };
|
|
77
|
+
groups.push(g);
|
|
78
|
+
}
|
|
79
|
+
g.items.push(m);
|
|
80
|
+
}
|
|
81
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
82
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [
|
|
83
|
+
/* @__PURE__ */ jsx(HeaderTabs, { tabs: KIND_TABS, value: kind, onChange: setKind, "aria-label": "Automa\xE7\xF5es" }),
|
|
84
|
+
messages !== null && /* @__PURE__ */ jsxs("span", { className: "text-sm text-muted-foreground", children: [
|
|
85
|
+
activeCount,
|
|
86
|
+
" de ",
|
|
87
|
+
messages.length,
|
|
88
|
+
" automa\xE7\xF5es ativas"
|
|
89
|
+
] })
|
|
72
90
|
] }),
|
|
73
|
-
/* @__PURE__ */ jsx("
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
91
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: KIND_HINT[kind] }),
|
|
92
|
+
messages === null ? /* @__PURE__ */ jsx("div", { className: "space-y-3", children: Array.from({ length: 4 }).map((_, i) => /* @__PURE__ */ jsxs(Card, { className: "p-4", children: [
|
|
93
|
+
/* @__PURE__ */ jsx("div", { className: "h-4 w-40 animate-pulse rounded bg-muted" }),
|
|
94
|
+
/* @__PURE__ */ jsx("div", { className: "mt-2 h-3 w-64 animate-pulse rounded bg-muted" })
|
|
95
|
+
] }, i)) }) : groups.length === 0 ? /* @__PURE__ */ jsxs(Card, { className: "flex flex-col items-center gap-1 p-12 text-center", children: [
|
|
96
|
+
/* @__PURE__ */ jsx(Zap, { className: "mb-2 h-9 w-9 text-muted-foreground/40", strokeWidth: 1.5 }),
|
|
97
|
+
/* @__PURE__ */ jsx("p", { className: "text-base font-semibold", children: "Nenhuma automa\xE7\xE3o nesta aba" }),
|
|
98
|
+
/* @__PURE__ */ jsx("p", { className: "max-w-sm text-sm text-muted-foreground", children: "Os eventos v\xEAm dos m\xF3dulos instalados \u2014 a loja traz os do pedido, o CRM os do funil." })
|
|
99
|
+
] }) : groups.map((group) => /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
100
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: group.label }),
|
|
101
|
+
group.items.map((m) => {
|
|
102
|
+
const Icon = iconFor(m.event.icon ?? m.event.pluginIcon);
|
|
103
|
+
const ChannelIcon = CHANNEL_ICON[m.channel];
|
|
104
|
+
const href = `/automations/${encodeURIComponent(m.event.name)}/${m.channel}`;
|
|
105
|
+
return /* @__PURE__ */ jsx(
|
|
106
|
+
Card,
|
|
107
|
+
{
|
|
108
|
+
role: "button",
|
|
109
|
+
tabIndex: 0,
|
|
110
|
+
onClick: () => adminNavigateTo(href),
|
|
111
|
+
onKeyDown: (e) => {
|
|
112
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
113
|
+
e.preventDefault();
|
|
114
|
+
adminNavigateTo(href);
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
className: "cursor-pointer overflow-hidden transition-colors hover:border-primary/40 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
118
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-4 p-4", children: [
|
|
119
|
+
/* @__PURE__ */ jsx("span", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border border-border text-muted-foreground", children: /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" }) }),
|
|
120
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
121
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
122
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium", children: m.override?.name ?? m.event.title ?? m.event.name }),
|
|
123
|
+
/* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-muted px-2 py-0.5 text-[11px] text-muted-foreground", children: [
|
|
124
|
+
/* @__PURE__ */ jsx(ChannelIcon, { className: "h-3 w-3" }),
|
|
125
|
+
CHANNEL_LABEL[m.channel]
|
|
126
|
+
] }),
|
|
127
|
+
m.customized && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-primary/10 px-2 py-0.5 text-[11px] text-primary", children: "personalizado" })
|
|
128
|
+
] }),
|
|
129
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: m.event.description }),
|
|
130
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground", children: [
|
|
131
|
+
/* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
|
|
132
|
+
/* @__PURE__ */ jsx(Zap, { className: "h-3 w-3" }),
|
|
133
|
+
m.event.trigger
|
|
134
|
+
] }),
|
|
135
|
+
m.subject && /* @__PURE__ */ jsxs("span", { className: "truncate", children: [
|
|
136
|
+
"Assunto: ",
|
|
137
|
+
m.subject
|
|
138
|
+
] })
|
|
139
|
+
] })
|
|
140
|
+
] }),
|
|
141
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-3", children: [
|
|
142
|
+
/* @__PURE__ */ jsx(
|
|
143
|
+
Switch,
|
|
144
|
+
{
|
|
145
|
+
checked: m.enabled,
|
|
146
|
+
onCheckedChange: (next) => void toggle(m, next),
|
|
147
|
+
label: m.enabled ? "Ativo" : "Inativo",
|
|
148
|
+
"aria-label": `${m.event.title} \u2014 ${CHANNEL_LABEL[m.channel]}`
|
|
149
|
+
}
|
|
150
|
+
),
|
|
151
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4 text-muted-foreground" })
|
|
152
|
+
] })
|
|
86
153
|
] })
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
154
|
+
},
|
|
155
|
+
`${m.event.name}:${m.channel}`
|
|
156
|
+
);
|
|
157
|
+
})
|
|
158
|
+
] }, group.module))
|
|
159
|
+
] });
|
|
160
|
+
}
|
|
161
|
+
var STATUS = {
|
|
162
|
+
sent: { label: "Enviado", hint: "A mensagem saiu.", variant: "success", Icon: CircleCheck },
|
|
163
|
+
recorded: {
|
|
164
|
+
label: "Disparou",
|
|
165
|
+
// O estado honesto de hoje: o gatilho funciona, o envio ainda não existe.
|
|
166
|
+
hint: "O gatilho disparou e ficou registrado. O envio autom\xE1tico ainda n\xE3o est\xE1 ligado neste ambiente.",
|
|
167
|
+
variant: "info",
|
|
168
|
+
Icon: History
|
|
169
|
+
},
|
|
170
|
+
skipped: { label: "Ignorado", hint: "O gatilho disparou, mas nada foi enviado \u2014 automa\xE7\xE3o desligada ou sem canal.", variant: "warning", Icon: CircleSlash },
|
|
171
|
+
failed: { label: "Falhou", hint: "A tentativa de envio quebrou. O erro est\xE1 na linha.", variant: "destructive", Icon: AlertTriangle }
|
|
172
|
+
};
|
|
173
|
+
function when(iso) {
|
|
174
|
+
const then = new Date(iso).getTime();
|
|
175
|
+
const diff = Date.now() - then;
|
|
176
|
+
const min = Math.round(diff / 6e4);
|
|
177
|
+
if (min < 1) return "agora";
|
|
178
|
+
if (min < 60) return `h\xE1 ${min} min`;
|
|
179
|
+
const h = Math.round(min / 60);
|
|
180
|
+
if (h < 24) return `h\xE1 ${h} h`;
|
|
181
|
+
return new Date(iso).toLocaleString("pt-BR", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" });
|
|
182
|
+
}
|
|
183
|
+
function RunRow({ run }) {
|
|
184
|
+
const [open, setOpen] = React3.useState(false);
|
|
185
|
+
const cfg = STATUS[run.status] ?? STATUS.recorded;
|
|
186
|
+
const { Icon } = cfg;
|
|
187
|
+
return /* @__PURE__ */ jsxs("div", { className: "border-b border-border last:border-b-0", children: [
|
|
188
|
+
/* @__PURE__ */ jsxs(
|
|
189
|
+
"button",
|
|
190
|
+
{
|
|
191
|
+
type: "button",
|
|
192
|
+
onClick: () => setOpen(!open),
|
|
193
|
+
className: "flex w-full items-center gap-3 px-4 py-2.5 text-left transition-colors hover:bg-muted/40",
|
|
194
|
+
children: [
|
|
195
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: `h-3.5 w-3.5 shrink-0 text-muted-foreground transition-transform ${open ? "rotate-90" : ""}` }),
|
|
196
|
+
/* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }),
|
|
197
|
+
/* @__PURE__ */ jsx(Badge, { variant: cfg.variant, children: cfg.label }),
|
|
198
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-sm text-muted-foreground", children: typeof run.payload.record_name === "string" ? run.payload.record_name : typeof run.payload.customer_name === "string" ? run.payload.customer_name : typeof run.payload.contact_name === "string" ? run.payload.contact_name : run.subjectId ?? "\u2014" }),
|
|
199
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: when(run.occurredAt) })
|
|
200
|
+
]
|
|
201
|
+
}
|
|
202
|
+
),
|
|
203
|
+
open && /* @__PURE__ */ jsxs("div", { className: "space-y-3 bg-muted/20 px-4 pb-4 pt-1", children: [
|
|
204
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: cfg.hint }),
|
|
205
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
206
|
+
/* @__PURE__ */ jsx("p", { className: "mb-1 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Dados do disparo" }),
|
|
207
|
+
/* @__PURE__ */ jsx("pre", { className: "overflow-x-auto rounded-md border border-border bg-card p-3 font-mono text-[11px] leading-relaxed", children: JSON.stringify(run.payload, null, 2) })
|
|
93
208
|
] }),
|
|
94
|
-
|
|
95
|
-
/* @__PURE__ */
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
209
|
+
run.result && /* @__PURE__ */ jsxs("div", { children: [
|
|
210
|
+
/* @__PURE__ */ jsx("p", { className: "mb-1 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Resultado" }),
|
|
211
|
+
/* @__PURE__ */ jsx("pre", { className: "overflow-x-auto rounded-md border border-border bg-card p-3 font-mono text-[11px] leading-relaxed", children: JSON.stringify(run.result, null, 2) })
|
|
212
|
+
] }),
|
|
213
|
+
run.error && /* @__PURE__ */ jsx("p", { className: "rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 font-mono text-[11px] text-destructive", children: run.error })
|
|
214
|
+
] })
|
|
215
|
+
] });
|
|
216
|
+
}
|
|
217
|
+
function RunLog({ eventKey, tenantId, enabled }) {
|
|
218
|
+
const [runs, setRuns] = React3.useState(null);
|
|
219
|
+
const [loading, setLoading] = React3.useState(false);
|
|
220
|
+
const load = React3.useCallback(async () => {
|
|
221
|
+
setLoading(true);
|
|
222
|
+
setRuns(await loadEventRuns(eventKey, { tenantId, limit: 20 }));
|
|
223
|
+
setLoading(false);
|
|
224
|
+
}, [eventKey, tenantId]);
|
|
225
|
+
React3.useEffect(() => {
|
|
226
|
+
void load();
|
|
227
|
+
}, [load]);
|
|
228
|
+
return /* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
|
|
229
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 border-b border-border px-4 py-2.5", children: [
|
|
230
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
231
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: "Execu\xE7\xF5es recentes" }),
|
|
232
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Cada vez que este gatilho disparou nesta conta." })
|
|
233
|
+
] }),
|
|
234
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => void load(), disabled: loading, children: /* @__PURE__ */ jsx(RefreshCw, { className: `h-3.5 w-3.5 ${loading ? "animate-spin" : ""}` }) })
|
|
235
|
+
] }),
|
|
236
|
+
runs === null ? /* @__PURE__ */ jsx("div", { className: "space-y-2 p-4", children: [1, 2, 3].map((i) => /* @__PURE__ */ jsx("div", { className: "h-8 animate-pulse rounded bg-muted/40" }, i)) }) : runs.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1 px-4 py-10 text-center", children: [
|
|
237
|
+
/* @__PURE__ */ jsx(History, { className: "h-7 w-7 text-muted-foreground/40", strokeWidth: 1.5 }),
|
|
238
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "Nenhum disparo ainda" }),
|
|
239
|
+
/* @__PURE__ */ jsx("p", { className: "max-w-sm text-xs text-muted-foreground", children: enabled ? "Assim que este evento acontecer, ele aparece aqui \u2014 mesmo antes de existir envio." : "A automa\xE7\xE3o est\xE1 desligada. O gatilho continua sendo registrado aqui quando o evento acontecer." })
|
|
240
|
+
] }) : /* @__PURE__ */ jsx("div", { children: runs.map((run) => /* @__PURE__ */ jsx(RunRow, { run }, run.id)) })
|
|
241
|
+
] });
|
|
242
|
+
}
|
|
243
|
+
var TABS = [
|
|
244
|
+
{ value: "preview", label: "Pr\xE9via" },
|
|
245
|
+
{ value: "code", label: "C\xF3digo" },
|
|
246
|
+
// O que já aconteceu. Fica ao lado do texto, e não numa tela à parte, porque
|
|
247
|
+
// é a mesma pergunta em dois tempos: o que vai sair, e o que saiu.
|
|
248
|
+
{ value: "runs", label: "Execu\xE7\xF5es" }
|
|
249
|
+
];
|
|
250
|
+
var CHANNEL_LABEL2 = {
|
|
251
|
+
email: "E-mail",
|
|
252
|
+
whatsapp: "WhatsApp",
|
|
253
|
+
sms: "SMS",
|
|
254
|
+
push: "Push",
|
|
255
|
+
webchat: "Chat"
|
|
256
|
+
};
|
|
257
|
+
var SAMPLE = {
|
|
258
|
+
store_name: "Sua Loja",
|
|
259
|
+
company_name: "Sua Empresa",
|
|
260
|
+
customer_name: "Maria Silva",
|
|
261
|
+
contact_name: "Maria Silva",
|
|
262
|
+
lead_name: "Maria Silva",
|
|
263
|
+
order_number: "1042",
|
|
264
|
+
order_total: "R$ 189,90",
|
|
265
|
+
order_items: "1 \xD7 Camiseta Cl\xE1ssica \u2014 R$ 89,90\n2 \xD7 Caneca Esmaltada \u2014 R$ 100,00",
|
|
266
|
+
tracking_code: "BR123456789BR",
|
|
267
|
+
cart_url: "https://sualoja.com.br/carrinho",
|
|
268
|
+
deal_name: "Proposta \u2014 Reforma da fachada",
|
|
269
|
+
deal_value: "R$ 12.400,00",
|
|
270
|
+
stage_name: "Negocia\xE7\xE3o",
|
|
271
|
+
owner_name: "Jo\xE3o Pereira",
|
|
272
|
+
appointment_date: "12/08/2026",
|
|
273
|
+
appointment_time: "14:30"
|
|
274
|
+
};
|
|
275
|
+
function iconFor2(name) {
|
|
276
|
+
if (!name) return Zap;
|
|
277
|
+
const icons = LucideIcons;
|
|
278
|
+
return icons[name] ?? Zap;
|
|
279
|
+
}
|
|
280
|
+
function AutomationDetailPage({ eventKey, channel, store, backTo = "/automations" }) {
|
|
281
|
+
const tenantId = useOrganizationStoreLike();
|
|
282
|
+
const runtime = usePluginRuntimeOptional();
|
|
283
|
+
const [message, setMessage] = React3.useState(void 0);
|
|
284
|
+
const [tab, setTab] = React3.useState("preview");
|
|
285
|
+
const [device, setDevice] = React3.useState("desktop");
|
|
286
|
+
const [draft, setDraft] = React3.useState(null);
|
|
287
|
+
const [saving, setSaving] = React3.useState(false);
|
|
288
|
+
const back = React3.useCallback(() => adminNavigateTo(backTo), [backTo]);
|
|
289
|
+
const reload = React3.useCallback(async () => {
|
|
290
|
+
if (!eventKey || !channel) {
|
|
291
|
+
setMessage(null);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
let overrides = [];
|
|
295
|
+
try {
|
|
296
|
+
overrides = await store.list(tenantId);
|
|
297
|
+
} catch (err) {
|
|
298
|
+
console.warn("[automations] could not load overrides; showing defaults", err);
|
|
299
|
+
}
|
|
300
|
+
const found = resolveMessage(decodeURIComponent(eventKey), channel, overrides, runtime?.events ?? []);
|
|
301
|
+
setMessage(found);
|
|
302
|
+
if (found) setDraft({ subject: found.subject, body: found.body });
|
|
303
|
+
}, [eventKey, channel, store, tenantId, runtime]);
|
|
304
|
+
React3.useEffect(() => {
|
|
305
|
+
void reload();
|
|
306
|
+
}, [reload]);
|
|
307
|
+
const header = /* @__PURE__ */ jsx(
|
|
308
|
+
SubpageHeader,
|
|
309
|
+
{
|
|
310
|
+
title: message === void 0 ? "" : message?.override?.name ?? message?.event.title ?? message?.event.name ?? "Automa\xE7\xE3o",
|
|
311
|
+
parentLabel: "Automa\xE7\xF5es",
|
|
312
|
+
onBack: back
|
|
313
|
+
}
|
|
314
|
+
);
|
|
315
|
+
if (message === void 0) {
|
|
316
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
317
|
+
header,
|
|
318
|
+
/* @__PURE__ */ jsx("div", { className: "h-8 w-64 animate-pulse rounded bg-muted" }),
|
|
319
|
+
/* @__PURE__ */ jsx(Card, { className: "h-96 animate-pulse" })
|
|
320
|
+
] });
|
|
321
|
+
}
|
|
322
|
+
if (!message) {
|
|
323
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
324
|
+
header,
|
|
325
|
+
/* @__PURE__ */ jsxs(Card, { className: "flex flex-col items-center gap-2 p-12 text-center", children: [
|
|
326
|
+
/* @__PURE__ */ jsx(Mail, { className: "h-9 w-9 text-muted-foreground/40", strokeWidth: 1.5 }),
|
|
327
|
+
/* @__PURE__ */ jsx("p", { className: "font-semibold", children: "Automa\xE7\xE3o n\xE3o encontrada" }),
|
|
328
|
+
/* @__PURE__ */ jsxs("p", { className: "max-w-sm text-sm text-muted-foreground", children: [
|
|
329
|
+
"O evento ",
|
|
330
|
+
/* @__PURE__ */ jsx("code", { className: "font-mono", children: eventKey }),
|
|
331
|
+
" n\xE3o est\xE1 no cat\xE1logo \u2014 o m\xF3dulo que o registra pode n\xE3o estar instalado nesta conta."
|
|
332
|
+
] }),
|
|
333
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", className: "mt-2", onClick: back, children: "Voltar para Automa\xE7\xF5es" })
|
|
334
|
+
] })
|
|
335
|
+
] });
|
|
336
|
+
}
|
|
337
|
+
const event = message.event;
|
|
338
|
+
const fallback = { subject: message.override ? void 0 : message.subject, body: message.body };
|
|
339
|
+
const Icon = iconFor2(event.icon);
|
|
340
|
+
const current = draft ?? { subject: message.subject, body: message.body };
|
|
341
|
+
const dirty = current.subject !== message.subject || current.body !== message.body;
|
|
342
|
+
const hasSubject = message.channel === "email";
|
|
343
|
+
const patch = async (input) => {
|
|
344
|
+
setSaving(true);
|
|
345
|
+
try {
|
|
346
|
+
await store.upsert(input, tenantId);
|
|
347
|
+
await reload();
|
|
348
|
+
} catch (err) {
|
|
349
|
+
console.warn("[automations] save failed", err);
|
|
350
|
+
toast.error("N\xE3o foi poss\xEDvel salvar");
|
|
351
|
+
} finally {
|
|
352
|
+
setSaving(false);
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
const save = () => void patch({
|
|
356
|
+
eventKey: event.name,
|
|
357
|
+
channel: message.channel,
|
|
358
|
+
// Igual ao padrão volta a ser "sem personalização", não uma cópia.
|
|
359
|
+
subject: current.subject.trim() === (fallback?.subject ?? "") ? null : current.subject.trim() || null,
|
|
360
|
+
body: current.body.trim() === fallback?.body ? null : current.body.trim() || null
|
|
361
|
+
});
|
|
362
|
+
const reset = async () => {
|
|
363
|
+
setDraft({ subject: fallback?.subject ?? "", body: fallback?.body ?? "" });
|
|
364
|
+
setSaving(true);
|
|
365
|
+
try {
|
|
366
|
+
await store.reset(event.name, message.channel, tenantId);
|
|
367
|
+
await reload();
|
|
368
|
+
toast.success("Texto restaurado");
|
|
369
|
+
} catch (err) {
|
|
370
|
+
console.warn("[automations] reset failed", err);
|
|
371
|
+
toast.error("N\xE3o foi poss\xEDvel restaurar");
|
|
372
|
+
} finally {
|
|
373
|
+
setSaving(false);
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
const previewSubject = renderMessageTemplate(current.subject, SAMPLE);
|
|
377
|
+
const previewBody = renderMessageTemplate(current.body, SAMPLE);
|
|
378
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
|
|
379
|
+
header,
|
|
380
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-start gap-3", children: [
|
|
381
|
+
/* @__PURE__ */ jsx("span", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border border-border text-muted-foreground", children: /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" }) }),
|
|
382
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
383
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
384
|
+
/* @__PURE__ */ jsx(Badge, { variant: "outline", children: CHANNEL_LABEL2[message.channel] }),
|
|
385
|
+
/* @__PURE__ */ jsx(Badge, { variant: "outline", children: message.kind === "marketing" ? "Marketing" : "Transacional" }),
|
|
386
|
+
message.customized && /* @__PURE__ */ jsx(Badge, { variant: "info", children: "Personalizado" })
|
|
99
387
|
] }),
|
|
100
|
-
|
|
101
|
-
] },
|
|
102
|
-
|
|
103
|
-
|
|
388
|
+
/* @__PURE__ */ jsx("p", { className: "mt-0.5 text-sm text-muted-foreground", children: event.description })
|
|
389
|
+
] }),
|
|
390
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-wrap items-center gap-2", children: [
|
|
391
|
+
/* @__PURE__ */ jsx(Hint, { label: "Descarta o texto personalizado e volta ao padr\xE3o do produto.", children: /* @__PURE__ */ jsx("span", { children: /* @__PURE__ */ jsxs(Button, { variant: "outline", onClick: () => void reset(), disabled: !message.customized || saving, children: [
|
|
392
|
+
/* @__PURE__ */ jsx(RotateCcw, { className: "mr-2 h-4 w-4" }),
|
|
393
|
+
"Restaurar padr\xE3o"
|
|
394
|
+
] }) }) }),
|
|
395
|
+
/* @__PURE__ */ jsx(
|
|
396
|
+
Switch,
|
|
397
|
+
{
|
|
398
|
+
size: "md",
|
|
399
|
+
checked: message.enabled,
|
|
400
|
+
onCheckedChange: (next) => void patch({ eventKey: event.name, channel: message.channel, enabled: next }),
|
|
401
|
+
disabled: saving,
|
|
402
|
+
label: message.enabled ? "Ativa" : "Inativa"
|
|
403
|
+
}
|
|
404
|
+
),
|
|
405
|
+
/* @__PURE__ */ jsx(Button, { onClick: save, disabled: !dirty || saving, children: saving ? "Salvando\u2026" : dirty ? "Salvar" : "Salvo" })
|
|
406
|
+
] })
|
|
407
|
+
] }),
|
|
408
|
+
/* @__PURE__ */ jsx(HeaderTabs, { tabs: TABS, value: tab, onChange: setTab, "aria-label": "Visualiza\xE7\xE3o" }),
|
|
409
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-6 gap-y-2 border-b border-border pb-4 text-xs text-muted-foreground", children: [
|
|
410
|
+
/* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5", children: [
|
|
411
|
+
/* @__PURE__ */ jsx(Zap, { className: "h-3.5 w-3.5" }),
|
|
412
|
+
"Gatilho:",
|
|
413
|
+
/* @__PURE__ */ jsx("code", { className: "rounded bg-muted px-1.5 py-0.5 font-mono text-[11px] text-foreground", children: event.name })
|
|
414
|
+
] }),
|
|
415
|
+
/* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5", children: [
|
|
416
|
+
/* @__PURE__ */ jsx(Clock, { className: "h-3.5 w-3.5" }),
|
|
417
|
+
event.trigger
|
|
418
|
+
] }),
|
|
419
|
+
hasSubject && current.subject && /* @__PURE__ */ jsxs("span", { className: "min-w-0 truncate", children: [
|
|
420
|
+
"Assunto: ",
|
|
421
|
+
current.subject
|
|
422
|
+
] })
|
|
423
|
+
] }),
|
|
424
|
+
tab === "runs" ? /* @__PURE__ */ jsx(RunLog, { eventKey: event.name, tenantId, enabled: message.enabled }) : tab === "preview" ? /* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
|
|
425
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 border-b border-border px-4 py-2.5", children: [
|
|
426
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: "Pr\xE9via" }),
|
|
427
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-1", children: ["desktop", "mobile"].map((d) => /* @__PURE__ */ jsx(
|
|
428
|
+
"button",
|
|
429
|
+
{
|
|
430
|
+
type: "button",
|
|
431
|
+
onClick: () => setDevice(d),
|
|
432
|
+
"aria-label": d === "desktop" ? "Computador" : "Celular",
|
|
433
|
+
className: `flex h-7 w-7 items-center justify-center rounded-md transition-colors ${device === d ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted/60"}`,
|
|
434
|
+
children: d === "desktop" ? /* @__PURE__ */ jsx(Monitor, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Smartphone, { className: "h-4 w-4" })
|
|
435
|
+
},
|
|
436
|
+
d
|
|
437
|
+
)) })
|
|
438
|
+
] }),
|
|
439
|
+
/* @__PURE__ */ jsxs("div", { className: "bg-muted/30 p-6", children: [
|
|
440
|
+
/* @__PURE__ */ jsx(
|
|
441
|
+
"div",
|
|
442
|
+
{
|
|
443
|
+
className: `mx-auto rounded-lg border border-border bg-card shadow-sm transition-[max-width] ${device === "mobile" ? "max-w-[380px]" : hasSubject ? "max-w-[600px]" : "max-w-[420px]"}`,
|
|
444
|
+
children: hasSubject ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
445
|
+
/* @__PURE__ */ jsx("div", { className: "border-b border-border px-6 py-4 text-sm font-semibold", children: SAMPLE.store_name }),
|
|
446
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1 border-b border-border px-6 py-3", children: [
|
|
447
|
+
/* @__PURE__ */ jsx("p", { className: "text-[11px] uppercase tracking-wide text-muted-foreground", children: "Assunto" }),
|
|
448
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: previewSubject })
|
|
449
|
+
] }),
|
|
450
|
+
/* @__PURE__ */ jsx("div", { className: "whitespace-pre-wrap px-6 py-5 text-sm leading-relaxed", children: previewBody })
|
|
451
|
+
] }) : /* @__PURE__ */ jsx("div", { className: "p-4", children: /* @__PURE__ */ jsx("div", { className: "whitespace-pre-wrap rounded-2xl rounded-tl-sm bg-[#dcf8c6] px-3 py-2 text-sm leading-relaxed text-[#111b21]", children: previewBody }) })
|
|
452
|
+
}
|
|
453
|
+
),
|
|
454
|
+
/* @__PURE__ */ jsx("p", { className: "mt-4 text-center text-xs text-muted-foreground", children: "Dados de exemplo \u2014 nenhum registro real foi usado." })
|
|
455
|
+
] })
|
|
456
|
+
] }) : /* @__PURE__ */ jsxs(Card, { className: "space-y-4 p-4", children: [
|
|
457
|
+
hasSubject && /* @__PURE__ */ jsxs("div", { children: [
|
|
458
|
+
/* @__PURE__ */ jsx("label", { className: "mb-1 block text-xs font-medium text-muted-foreground", children: "Assunto" }),
|
|
459
|
+
/* @__PURE__ */ jsx(
|
|
460
|
+
"input",
|
|
461
|
+
{
|
|
462
|
+
value: current.subject,
|
|
463
|
+
onChange: (e) => setDraft({ ...current, subject: e.target.value }),
|
|
464
|
+
className: "w-full rounded-input border border-input bg-card px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
|
465
|
+
}
|
|
466
|
+
)
|
|
467
|
+
] }),
|
|
468
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
469
|
+
/* @__PURE__ */ jsx("label", { className: "mb-1 block text-xs font-medium text-muted-foreground", children: "Mensagem" }),
|
|
470
|
+
/* @__PURE__ */ jsx(
|
|
471
|
+
"textarea",
|
|
472
|
+
{
|
|
473
|
+
rows: 14,
|
|
474
|
+
value: current.body,
|
|
475
|
+
onChange: (e) => setDraft({ ...current, body: e.target.value }),
|
|
476
|
+
className: "w-full rounded-input border border-input bg-card px-3 py-2 font-mono text-xs leading-relaxed focus:outline-none focus:ring-2 focus:ring-ring"
|
|
477
|
+
}
|
|
478
|
+
)
|
|
479
|
+
] }),
|
|
480
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
481
|
+
/* @__PURE__ */ jsx("p", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: "Campos deste evento \u2014 o envio substitui pelo dado real" }),
|
|
482
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1", children: message.placeholders.map((p) => /* @__PURE__ */ jsx("span", { className: "rounded bg-muted px-1.5 py-0.5 font-mono text-[11px] text-muted-foreground", children: p }, p)) })
|
|
483
|
+
] })
|
|
484
|
+
] })
|
|
104
485
|
] });
|
|
105
486
|
}
|
|
487
|
+
var TABLE = "plg_automations_templates";
|
|
488
|
+
function rowToTemplate(row) {
|
|
489
|
+
return {
|
|
490
|
+
id: row.id,
|
|
491
|
+
tenantId: row.tenant_id,
|
|
492
|
+
eventKey: row.event_key,
|
|
493
|
+
channel: row.channel ?? "email",
|
|
494
|
+
// NULL fica NULL: quem decide o padrão é o catálogo, não este mapeamento.
|
|
495
|
+
// Colapsar para `false` aqui desligaria em silêncio todo modelo cuja linha
|
|
496
|
+
// só existe porque o texto foi editado.
|
|
497
|
+
enabled: row.enabled ?? null,
|
|
498
|
+
// String vazia e NULL significam a mesma coisa — "sem personalização" —,
|
|
499
|
+
// senão um assunto apagado sairia em branco em vez de voltar ao padrão.
|
|
500
|
+
subject: row.subject || null,
|
|
501
|
+
body: row.body || null,
|
|
502
|
+
name: row.name || null,
|
|
503
|
+
updatedAt: row.updated_at ?? row.created_at
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
function createSupabaseTemplateStore() {
|
|
507
|
+
const db = () => {
|
|
508
|
+
const client = getSupabaseClientOptional();
|
|
509
|
+
if (!client) throw new Error("@fayz-ai/plugin-automations: Supabase client not initialized");
|
|
510
|
+
return client;
|
|
511
|
+
};
|
|
512
|
+
return {
|
|
513
|
+
async list(tenantId) {
|
|
514
|
+
let q = db().from(TABLE).select("*");
|
|
515
|
+
if (tenantId) q = q.eq("tenant_id", tenantId);
|
|
516
|
+
const { data, error } = await q;
|
|
517
|
+
if (error) throw error;
|
|
518
|
+
return (data ?? []).map(rowToTemplate);
|
|
519
|
+
},
|
|
520
|
+
async upsert(input, tenantId) {
|
|
521
|
+
const payload = {
|
|
522
|
+
tenant_id: tenantId,
|
|
523
|
+
event_key: input.eventKey,
|
|
524
|
+
channel: input.channel
|
|
525
|
+
};
|
|
526
|
+
if (input.enabled !== void 0) payload.enabled = input.enabled;
|
|
527
|
+
if (input.subject !== void 0) payload.subject = input.subject;
|
|
528
|
+
if (input.body !== void 0) payload.body = input.body;
|
|
529
|
+
if (input.name !== void 0) payload.name = input.name;
|
|
530
|
+
const { data, error } = await db().from(TABLE).upsert(payload, { onConflict: "tenant_id,event_key,channel" }).select().single();
|
|
531
|
+
if (error) throw error;
|
|
532
|
+
return rowToTemplate(data);
|
|
533
|
+
},
|
|
534
|
+
async reset(eventKey, channel, tenantId) {
|
|
535
|
+
let q = db().from(TABLE).delete().eq("event_key", eventKey).eq("channel", channel);
|
|
536
|
+
if (tenantId) q = q.eq("tenant_id", tenantId);
|
|
537
|
+
const { error } = await q;
|
|
538
|
+
if (error) throw error;
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
}
|
|
106
542
|
|
|
107
543
|
// src/index.ts
|
|
108
544
|
function createAutomationsPlugin(options) {
|
|
109
|
-
const
|
|
110
|
-
|
|
545
|
+
const store = options?.store ?? createSupabaseTemplateStore();
|
|
546
|
+
startEventLog();
|
|
547
|
+
const inSettings = options?.placement === "settings";
|
|
548
|
+
const ListPage = () => React3.createElement(AutomationsList, { store });
|
|
549
|
+
ListPage.displayName = "AutomationsList";
|
|
550
|
+
const DetailPage = (props) => React3.createElement(AutomationDetailPage, {
|
|
551
|
+
...props,
|
|
552
|
+
store,
|
|
553
|
+
// "Back" must return you through the door you came in: opening the list
|
|
554
|
+
// inside settings must not drop you on a loose page that app's menu does
|
|
555
|
+
// not have.
|
|
556
|
+
backTo: inSettings ? "/settings/automations" : "/automations"
|
|
557
|
+
});
|
|
558
|
+
DetailPage.displayName = "AutomationDetail";
|
|
559
|
+
const READ = { feature: "automations", action: "read" };
|
|
111
560
|
return {
|
|
112
561
|
id: "automations",
|
|
113
|
-
name: options?.navLabel ?? "
|
|
562
|
+
name: options?.navLabel ?? "Automa\xE7\xF5es",
|
|
114
563
|
icon: "Zap",
|
|
115
|
-
version: "
|
|
564
|
+
version: "2.0.0",
|
|
116
565
|
scope: options?.scope ?? "universal",
|
|
117
566
|
verticalId: options?.verticalId,
|
|
118
567
|
defaultEnabled: true,
|
|
119
568
|
dependencies: [],
|
|
120
569
|
declaredFeatures: [{ id: "automations", label: "Automations", group: "Automate" }],
|
|
121
|
-
navigation: [
|
|
570
|
+
navigation: inSettings ? [] : [
|
|
122
571
|
{
|
|
123
572
|
section: options?.navSection ?? "main",
|
|
124
573
|
position: options?.navPosition ?? 6,
|
|
125
|
-
label: options?.navLabel ?? "
|
|
574
|
+
label: options?.navLabel ?? "Automa\xE7\xF5es",
|
|
126
575
|
route: "/automations",
|
|
127
576
|
icon: "Zap",
|
|
128
|
-
permission:
|
|
577
|
+
permission: READ,
|
|
578
|
+
indexChild: false
|
|
129
579
|
}
|
|
130
580
|
],
|
|
581
|
+
// The SAME list, served inside settings — not a second screen.
|
|
582
|
+
settings: inSettings ? [
|
|
583
|
+
{ id: "automations", label: options?.navLabel ?? "Automa\xE7\xF5es", icon: "Zap", component: ListPage, permission: READ }
|
|
584
|
+
] : [],
|
|
131
585
|
routes: [
|
|
132
|
-
{
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
586
|
+
{ path: "/automations", component: ListPage, permission: READ },
|
|
587
|
+
// One automation's detail. Off-menu: it is a row of the list. Its back
|
|
588
|
+
// link comes from `backTo`.
|
|
589
|
+
{ path: "/automations/:eventKey/:channel", component: DetailPage, permission: READ }
|
|
137
590
|
],
|
|
138
591
|
widgets: []
|
|
139
592
|
};
|
|
140
593
|
}
|
|
141
594
|
|
|
142
|
-
export { createAutomationsPlugin };
|
|
595
|
+
export { createAutomationsPlugin, createSupabaseTemplateStore };
|
|
143
596
|
//# sourceMappingURL=index.js.map
|
|
144
597
|
//# sourceMappingURL=index.js.map
|