@fayz-ai/plugin-automations 0.10.1 → 0.11.0
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/dist/index.d.ts.map +1 -1
- package/dist/index.js +980 -733
- package/dist/index.js.map +1 -1
- package/dist/views/AutomationDetailPage.d.ts +17 -1
- package/dist/views/AutomationDetailPage.d.ts.map +1 -1
- package/dist/views/AutomationsList.d.ts +21 -1
- package/dist/views/AutomationsList.d.ts.map +1 -1
- package/package.json +8 -8
- package/dist/data/fayz.test.d.ts +0 -2
- package/dist/data/fayz.test.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,154 +1,304 @@
|
|
|
1
|
-
import
|
|
1
|
+
import React2 from 'react';
|
|
2
2
|
import { startEventLog, getSupabaseClientOptional, getMessageDefaults, usePluginRuntimeOptional, resolveMessages, resolveMessage, renderMessageTemplate, loadEventRuns } from '@fayz-ai/core';
|
|
3
|
-
import { resolveFayzAgentConnection, getFayzSupabaseClientOptional, useConnectors, adminNavigateTo, useOrganizationStore, connectorRoute, ConnectorMark, ConnectorDeliveries } from '@fayz-ai/admin';
|
|
3
|
+
import { resolveFayzAgentConnection, registerRailPage, getFayzSupabaseClientOptional, useConnectors, useAgentSurface, AskAIButton, SettingsTabs, adminNavigateTo, RightRailPage, useOrganizationStore, connectorRoute, ConnectorMark, ConnectorDeliveries } from '@fayz-ai/admin';
|
|
4
4
|
import * as LucideIcons from 'lucide-react';
|
|
5
|
-
import { Zap, MessageSquare, Smartphone, MessageCircle, Mail, ChevronRight, RotateCcw, Clock, Monitor, RefreshCw, History, AlertTriangle, CircleCheck, CircleSlash } from 'lucide-react';
|
|
6
|
-
import {
|
|
7
|
-
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
5
|
+
import { Zap, Receipt, Megaphone, MessageSquare, Smartphone, MessageCircle, Mail, ChevronRight, RotateCcw, Clock, Monitor, RefreshCw, History, AlertTriangle, CircleCheck, CircleSlash } from 'lucide-react';
|
|
6
|
+
import { PageHeaderActions, PageTabs, Card, Switch, SubpageHeader, Button, Badge, Hint, HeaderTabs, toast } from '@fayz-ai/ui';
|
|
8
7
|
import { createFayzAutomationsClient, FayzAutomationsError } from '@fayz-ai/sdk/automations';
|
|
8
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
9
9
|
|
|
10
10
|
// src/index.ts
|
|
11
11
|
function useOrganizationStoreLike() {
|
|
12
12
|
return useOrganizationStore((s) => s.currentOrg?.id);
|
|
13
13
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return out;
|
|
14
|
+
function toTemplate(automation) {
|
|
15
|
+
const eventKey = automation.triggerConfig?.eventKey;
|
|
16
|
+
if (!eventKey) return null;
|
|
17
|
+
const action = automation.actions?.[0];
|
|
18
|
+
return {
|
|
19
|
+
id: automation.id,
|
|
20
|
+
tenantId: automation.tenantId ?? "",
|
|
21
|
+
eventKey,
|
|
22
|
+
channel: automation.triggerConfig.sourceChannel ?? "email",
|
|
23
|
+
enabled: automation.enabled,
|
|
24
|
+
// Empty and absent mean the same — "no customisation", the catalogue default.
|
|
25
|
+
subject: action?.config?.subject || null,
|
|
26
|
+
body: action?.config?.body || null,
|
|
27
|
+
// The automation name belongs to the platform, not a user rename: the
|
|
28
|
+
// catalogue title still owns the screen.
|
|
29
|
+
name: null,
|
|
30
|
+
updatedAt: automation.updatedAt
|
|
31
|
+
};
|
|
35
32
|
}
|
|
36
|
-
function
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
33
|
+
function catalogDefaultEnabled(eventKey, channel) {
|
|
34
|
+
const def = getMessageDefaults().find((d) => d.eventKey === eventKey);
|
|
35
|
+
return def?.defaultEnabled?.[channel] ?? false;
|
|
36
|
+
}
|
|
37
|
+
async function defaultSessionToken() {
|
|
38
|
+
const session = (await getFayzSupabaseClientOptional()?.auth.getSession())?.data.session;
|
|
39
|
+
return session?.access_token ?? null;
|
|
40
|
+
}
|
|
41
|
+
function createFayzTemplateStore(connection, deps = {}) {
|
|
42
|
+
const client = deps.client ?? createFayzAutomationsClient({
|
|
43
|
+
baseUrl: connection.apiBaseUrl,
|
|
44
|
+
projectId: connection.projectId,
|
|
45
|
+
publishableKey: connection.publishableKey
|
|
46
|
+
});
|
|
47
|
+
const getSessionToken = deps.getSessionToken ?? defaultSessionToken;
|
|
48
|
+
const ids = /* @__PURE__ */ new Map();
|
|
49
|
+
const keyOf = (eventKey, channel) => `${eventKey}:${channel}`;
|
|
50
|
+
async function refresh(tenantId) {
|
|
51
|
+
const automations = await client.list({ tenantId });
|
|
52
|
+
ids.clear();
|
|
53
|
+
const rows = [];
|
|
54
|
+
for (const automation of automations) {
|
|
55
|
+
const row = toTemplate(automation);
|
|
56
|
+
if (!row) continue;
|
|
57
|
+
ids.set(keyOf(row.eventKey, row.channel), automation.id);
|
|
58
|
+
rows.push(row);
|
|
50
59
|
}
|
|
51
|
-
|
|
60
|
+
return rows;
|
|
52
61
|
}
|
|
53
|
-
|
|
62
|
+
const LIST_TTL_MS = 3e4;
|
|
63
|
+
let cache = null;
|
|
64
|
+
function invalidate() {
|
|
65
|
+
cache = null;
|
|
66
|
+
}
|
|
67
|
+
function load(tenantId) {
|
|
68
|
+
if (cache && cache.tenantId === tenantId && Date.now() - cache.at < LIST_TTL_MS) return cache.promise;
|
|
69
|
+
const promise = refresh(tenantId);
|
|
70
|
+
promise.catch(() => {
|
|
71
|
+
if (cache?.promise === promise) cache = null;
|
|
72
|
+
});
|
|
73
|
+
cache = { tenantId, at: Date.now(), promise };
|
|
74
|
+
return promise;
|
|
75
|
+
}
|
|
76
|
+
async function requireToken() {
|
|
77
|
+
const token = await getSessionToken();
|
|
78
|
+
if (!token) throw new Error("Sess\xE3o expirada \u2014 entre de novo.");
|
|
79
|
+
return token;
|
|
80
|
+
}
|
|
81
|
+
async function resolveId(eventKey, channel, tenantId) {
|
|
82
|
+
const key = keyOf(eventKey, channel);
|
|
83
|
+
if (!ids.has(key)) await load(tenantId);
|
|
84
|
+
return ids.get(key);
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
async list(tenantId) {
|
|
88
|
+
if (!tenantId) return [];
|
|
89
|
+
return load(tenantId);
|
|
90
|
+
},
|
|
91
|
+
async upsert(input, tenantId) {
|
|
92
|
+
if (!tenantId) throw new Error("@fayz-ai/plugin-automations: tenantId is required");
|
|
93
|
+
const appSessionToken = await requireToken();
|
|
94
|
+
const key = keyOf(input.eventKey, input.channel);
|
|
95
|
+
let id = await resolveId(input.eventKey, input.channel, tenantId);
|
|
96
|
+
if (!id) {
|
|
97
|
+
const action = {};
|
|
98
|
+
if (input.subject != null) action.subject = input.subject;
|
|
99
|
+
if (input.body != null) action.body = input.body;
|
|
100
|
+
try {
|
|
101
|
+
const created = await client.create({
|
|
102
|
+
tenantId,
|
|
103
|
+
appSessionToken,
|
|
104
|
+
eventKey: input.eventKey,
|
|
105
|
+
sourceChannel: input.channel,
|
|
106
|
+
// The platform has no "inherit from catalogue": materialise the default.
|
|
107
|
+
enabled: input.enabled ?? catalogDefaultEnabled(input.eventKey, input.channel),
|
|
108
|
+
...Object.keys(action).length ? { action } : {}
|
|
109
|
+
});
|
|
110
|
+
ids.set(key, created.id);
|
|
111
|
+
invalidate();
|
|
112
|
+
const row2 = toTemplate(created);
|
|
113
|
+
if (!row2) throw new Error("@fayz-ai/plugin-automations: platform did not round-trip eventKey");
|
|
114
|
+
return row2;
|
|
115
|
+
} catch (err) {
|
|
116
|
+
if (!(err instanceof FayzAutomationsError) || err.status !== 409) throw err;
|
|
117
|
+
invalidate();
|
|
118
|
+
await refresh(tenantId);
|
|
119
|
+
id = ids.get(key);
|
|
120
|
+
if (!id) throw err;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const updated = await client.update({
|
|
124
|
+
id,
|
|
125
|
+
tenantId,
|
|
126
|
+
appSessionToken,
|
|
127
|
+
// Only what was sent: omitting has to mean "leave it alone", as in the local store.
|
|
128
|
+
...typeof input.enabled === "boolean" ? { enabled: input.enabled } : {},
|
|
129
|
+
...input.subject !== void 0 || input.body !== void 0 ? {
|
|
130
|
+
action: {
|
|
131
|
+
...input.subject !== void 0 ? { subject: input.subject } : {},
|
|
132
|
+
...input.body !== void 0 ? { body: input.body } : {}
|
|
133
|
+
}
|
|
134
|
+
} : {}
|
|
135
|
+
});
|
|
136
|
+
invalidate();
|
|
137
|
+
const row = toTemplate(updated);
|
|
138
|
+
if (!row) throw new Error("@fayz-ai/plugin-automations: platform did not round-trip eventKey");
|
|
139
|
+
return row;
|
|
140
|
+
},
|
|
141
|
+
async reset(eventKey, channel, tenantId) {
|
|
142
|
+
if (!tenantId) return;
|
|
143
|
+
const id = await resolveId(eventKey, channel, tenantId);
|
|
144
|
+
if (!id) return;
|
|
145
|
+
const appSessionToken = await requireToken();
|
|
146
|
+
await client.update({ id, tenantId, appSessionToken, action: { subject: null, body: null } });
|
|
147
|
+
invalidate();
|
|
148
|
+
}
|
|
149
|
+
};
|
|
54
150
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
href
|
|
67
|
-
},
|
|
68
|
-
`${group.connectorId}:${row.eventKey}`
|
|
69
|
-
))
|
|
70
|
-
] }, group.connectorId);
|
|
71
|
-
}) });
|
|
151
|
+
var cachedClient = null;
|
|
152
|
+
function getFayzAutomationsClient(connection) {
|
|
153
|
+
const key = `${connection.apiBaseUrl}|${connection.projectId}|${connection.publishableKey}`;
|
|
154
|
+
if (cachedClient?.key === key) return cachedClient.client;
|
|
155
|
+
const client = createFayzAutomationsClient({
|
|
156
|
+
baseUrl: connection.apiBaseUrl,
|
|
157
|
+
projectId: connection.projectId,
|
|
158
|
+
publishableKey: connection.publishableKey
|
|
159
|
+
});
|
|
160
|
+
cachedClient = { key, client };
|
|
161
|
+
return client;
|
|
72
162
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
163
|
+
var STATUS = {
|
|
164
|
+
sent: { label: "Enviado", hint: "A mensagem saiu.", variant: "success", Icon: CircleCheck },
|
|
165
|
+
recorded: {
|
|
166
|
+
label: "Disparou",
|
|
167
|
+
// Today's honest state: the trigger works, the send doesn't exist yet.
|
|
168
|
+
hint: "O gatilho disparou e ficou registrado. O envio autom\xE1tico ainda n\xE3o est\xE1 ligado neste ambiente.",
|
|
169
|
+
variant: "info",
|
|
170
|
+
Icon: History
|
|
171
|
+
},
|
|
172
|
+
skipped: { label: "Ignorado", hint: "O gatilho disparou, mas nada foi enviado \u2014 automa\xE7\xE3o desligada ou sem canal.", variant: "warning", Icon: CircleSlash },
|
|
173
|
+
failed: { label: "Falhou", hint: "A tentativa de envio quebrou. O erro est\xE1 na linha.", variant: "destructive", Icon: AlertTriangle }
|
|
174
|
+
};
|
|
175
|
+
function when(iso) {
|
|
176
|
+
const then = new Date(iso).getTime();
|
|
177
|
+
const diff = Date.now() - then;
|
|
178
|
+
const min = Math.round(diff / 6e4);
|
|
179
|
+
if (min < 1) return "agora";
|
|
180
|
+
if (min < 60) return `h\xE1 ${min} min`;
|
|
181
|
+
const h = Math.round(min / 60);
|
|
182
|
+
if (h < 24) return `h\xE1 ${h} h`;
|
|
183
|
+
return new Date(iso).toLocaleString("pt-BR", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" });
|
|
184
|
+
}
|
|
185
|
+
function RunRow({ run }) {
|
|
186
|
+
const [open, setOpen] = React2.useState(false);
|
|
187
|
+
const cfg = STATUS[run.status] ?? STATUS.recorded;
|
|
188
|
+
const { Icon } = cfg;
|
|
189
|
+
return /* @__PURE__ */ jsxs("div", { className: "border-b border-border last:border-b-0", children: [
|
|
80
190
|
/* @__PURE__ */ jsxs(
|
|
81
|
-
"
|
|
191
|
+
"button",
|
|
82
192
|
{
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
onKeyDown: (e) => {
|
|
87
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
88
|
-
e.preventDefault();
|
|
89
|
-
adminNavigateTo(href);
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
|
-
className: "flex cursor-pointer items-start gap-4 p-4 transition-colors hover:bg-muted/30 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
193
|
+
type: "button",
|
|
194
|
+
onClick: () => setOpen(!open),
|
|
195
|
+
className: "flex w-full items-center gap-3 px-4 py-2.5 text-left transition-colors hover:bg-muted/40",
|
|
93
196
|
children: [
|
|
94
|
-
/* @__PURE__ */ jsx(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
name: group.connectorName,
|
|
100
|
-
icon: group.icon,
|
|
101
|
-
logoUrl: group.logoUrl
|
|
102
|
-
},
|
|
103
|
-
size: "sm"
|
|
104
|
-
}
|
|
105
|
-
),
|
|
106
|
-
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
107
|
-
/* @__PURE__ */ jsx("p", { className: "font-medium", children: row.label }),
|
|
108
|
-
/* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
|
|
109
|
-
"Quando ",
|
|
110
|
-
/* @__PURE__ */ jsx("code", { className: "font-mono text-xs", children: row.eventKey }),
|
|
111
|
-
" acontece aqui."
|
|
112
|
-
] })
|
|
113
|
-
] }),
|
|
114
|
-
/* @__PURE__ */ jsx("span", { className: "shrink-0 pt-0.5 text-right text-xs text-muted-foreground", children: row.optional ? `Opcional \u2014 desligue em ${group.connectorName}` : `Ligada pela integra\xE7\xE3o \xB7 sai ao desconectar ${group.connectorName}` })
|
|
197
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: `h-3.5 w-3.5 shrink-0 text-muted-foreground transition-transform ${open ? "rotate-90" : ""}` }),
|
|
198
|
+
/* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }),
|
|
199
|
+
/* @__PURE__ */ jsx(Badge, { variant: cfg.variant, children: cfg.label }),
|
|
200
|
+
/* @__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" }),
|
|
201
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: when(run.occurredAt) })
|
|
115
202
|
]
|
|
116
203
|
}
|
|
117
204
|
),
|
|
118
|
-
|
|
119
|
-
/* @__PURE__ */
|
|
120
|
-
|
|
121
|
-
{
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
"O que j\xE1 foi para o ",
|
|
130
|
-
group.connectorName
|
|
131
|
-
] })
|
|
132
|
-
]
|
|
133
|
-
}
|
|
134
|
-
),
|
|
135
|
-
open && /* @__PURE__ */ jsx("div", { className: "border-t border-border p-4", children: /* @__PURE__ */ jsx(
|
|
136
|
-
ConnectorDeliveries,
|
|
137
|
-
{
|
|
138
|
-
load: (options) => row.listDeliveries({ ...options, eventKey: row.eventKey }),
|
|
139
|
-
providerName: group.connectorName
|
|
140
|
-
}
|
|
141
|
-
) })
|
|
205
|
+
open && /* @__PURE__ */ jsxs("div", { className: "space-y-3 bg-muted/20 px-4 pb-4 pt-1", children: [
|
|
206
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: cfg.hint }),
|
|
207
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
208
|
+
/* @__PURE__ */ jsx("p", { className: "mb-1 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Dados do disparo" }),
|
|
209
|
+
/* @__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) })
|
|
210
|
+
] }),
|
|
211
|
+
run.result && /* @__PURE__ */ jsxs("div", { children: [
|
|
212
|
+
/* @__PURE__ */ jsx("p", { className: "mb-1 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Resultado" }),
|
|
213
|
+
/* @__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) })
|
|
214
|
+
] }),
|
|
215
|
+
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 })
|
|
142
216
|
] })
|
|
143
217
|
] });
|
|
144
218
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
219
|
+
function RunLog({ eventKey, tenantId, enabled }) {
|
|
220
|
+
const [runs, setRuns] = React2.useState(null);
|
|
221
|
+
const [loading, setLoading] = React2.useState(false);
|
|
222
|
+
const load = React2.useCallback(async () => {
|
|
223
|
+
setLoading(true);
|
|
224
|
+
setRuns(await loadEventRuns(eventKey, { tenantId, limit: 20 }));
|
|
225
|
+
setLoading(false);
|
|
226
|
+
}, [eventKey, tenantId]);
|
|
227
|
+
React2.useEffect(() => {
|
|
228
|
+
void load();
|
|
229
|
+
}, [load]);
|
|
230
|
+
return /* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
|
|
231
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 border-b border-border px-4 py-2.5", children: [
|
|
232
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
233
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: "Execu\xE7\xF5es recentes" }),
|
|
234
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Cada vez que este gatilho disparou nesta conta." })
|
|
235
|
+
] }),
|
|
236
|
+
/* @__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" : ""}` }) })
|
|
237
|
+
] }),
|
|
238
|
+
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: [
|
|
239
|
+
/* @__PURE__ */ jsx(History, { className: "h-7 w-7 text-muted-foreground/40", strokeWidth: 1.5 }),
|
|
240
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "Nenhum disparo ainda" }),
|
|
241
|
+
/* @__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." })
|
|
242
|
+
] }) : /* @__PURE__ */ jsx("div", { children: runs.map((run) => /* @__PURE__ */ jsx(RunRow, { run }, run.id)) })
|
|
243
|
+
] });
|
|
244
|
+
}
|
|
245
|
+
var EXEC_STATUS = {
|
|
246
|
+
success: { label: "Executada", variant: "success", Icon: CircleCheck },
|
|
247
|
+
error: { label: "Falhou", variant: "destructive", Icon: AlertTriangle }
|
|
151
248
|
};
|
|
249
|
+
function ExecutionRow({ execution }) {
|
|
250
|
+
const cfg = EXEC_STATUS[execution.status] ?? { label: execution.status, variant: "info", Icon: History };
|
|
251
|
+
const { Icon } = cfg;
|
|
252
|
+
return /* @__PURE__ */ jsxs("div", { className: "border-b border-border px-4 py-2.5 last:border-b-0", children: [
|
|
253
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
254
|
+
/* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }),
|
|
255
|
+
/* @__PURE__ */ jsx(Badge, { variant: cfg.variant, children: cfg.label }),
|
|
256
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-sm text-muted-foreground", children: execution.automationName }),
|
|
257
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: when(execution.executedAt) })
|
|
258
|
+
] }),
|
|
259
|
+
execution.error && /* @__PURE__ */ jsx("p", { className: "ml-6 mt-2 rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 font-mono text-[11px] text-destructive", children: execution.error })
|
|
260
|
+
] });
|
|
261
|
+
}
|
|
262
|
+
function FayzRunLog({ connection, eventKey, tenantId, enabled }) {
|
|
263
|
+
const [executions, setExecutions] = React2.useState(null);
|
|
264
|
+
const [loading, setLoading] = React2.useState(false);
|
|
265
|
+
const load = React2.useCallback(async () => {
|
|
266
|
+
if (!tenantId) return;
|
|
267
|
+
setLoading(true);
|
|
268
|
+
try {
|
|
269
|
+
setExecutions(await getFayzAutomationsClient(connection).listExecutions({ tenantId, eventKey, limit: 20 }));
|
|
270
|
+
} catch (err) {
|
|
271
|
+
console.warn("[automations] could not load platform executions", err);
|
|
272
|
+
setExecutions([]);
|
|
273
|
+
} finally {
|
|
274
|
+
setLoading(false);
|
|
275
|
+
}
|
|
276
|
+
}, [connection, eventKey, tenantId]);
|
|
277
|
+
React2.useEffect(() => {
|
|
278
|
+
void load();
|
|
279
|
+
}, [load]);
|
|
280
|
+
return /* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
|
|
281
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 border-b border-border px-4 py-2.5", children: [
|
|
282
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
283
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: "Execu\xE7\xF5es recentes" }),
|
|
284
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "O que a plataforma Fayz executou para este gatilho nesta conta." })
|
|
285
|
+
] }),
|
|
286
|
+
/* @__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" : ""}` }) })
|
|
287
|
+
] }),
|
|
288
|
+
executions === 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)) }) : executions.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1 px-4 py-10 text-center", children: [
|
|
289
|
+
/* @__PURE__ */ jsx(History, { className: "h-7 w-7 text-muted-foreground/40", strokeWidth: 1.5 }),
|
|
290
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "Nenhuma execu\xE7\xE3o ainda" }),
|
|
291
|
+
/* @__PURE__ */ jsx("p", { className: "max-w-sm text-xs text-muted-foreground", children: enabled ? "Assim que este evento acontecer, a plataforma executa a automa\xE7\xE3o e o resultado aparece aqui." : "A automa\xE7\xE3o est\xE1 desligada \u2014 a plataforma n\xE3o executa nada para este gatilho." })
|
|
292
|
+
] }) : /* @__PURE__ */ jsx("div", { children: executions.map((e) => /* @__PURE__ */ jsx(ExecutionRow, { execution: e }, e.id)) })
|
|
293
|
+
] });
|
|
294
|
+
}
|
|
295
|
+
var TABS = [
|
|
296
|
+
{ value: "preview", label: "Pr\xE9via" },
|
|
297
|
+
{ value: "code", label: "C\xF3digo" },
|
|
298
|
+
// O que já aconteceu. Fica ao lado do texto, e não numa tela à parte, porque
|
|
299
|
+
// é a mesma pergunta em dois tempos: o que vai sair, e o que saiu.
|
|
300
|
+
{ value: "runs", label: "Execu\xE7\xF5es" }
|
|
301
|
+
];
|
|
152
302
|
var CHANNEL_LABEL = {
|
|
153
303
|
email: "E-mail",
|
|
154
304
|
whatsapp: "WhatsApp",
|
|
@@ -156,433 +306,490 @@ var CHANNEL_LABEL = {
|
|
|
156
306
|
push: "Push",
|
|
157
307
|
webchat: "Chat"
|
|
158
308
|
};
|
|
159
|
-
var
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
309
|
+
var WRITE_MESSAGE_TOOL = {
|
|
310
|
+
id: "automations.writeMessage",
|
|
311
|
+
name: "writeAutomationMessage",
|
|
312
|
+
description: "Writes the subject and body of the automation message the user has open, into the editor (it does NOT save \u2014 the user does). Use ONLY the placeholders listed in the surface state, spelled exactly, including the braces: an invented one renders as literal text in a message a customer reads. Match the channel: e-mail takes a subject and paragraphs; WhatsApp and SMS take no subject and few lines.",
|
|
313
|
+
icon: "MessageSquare",
|
|
314
|
+
mode: "read",
|
|
315
|
+
execution: { plane: "client" },
|
|
316
|
+
category: "Automations",
|
|
317
|
+
parameters: {
|
|
318
|
+
type: "object",
|
|
319
|
+
properties: {
|
|
320
|
+
subject: { type: "string", description: "Subject line. Leave empty for channels that have none." },
|
|
321
|
+
body: { type: "string", description: "Message body, with placeholders where they belong." }
|
|
322
|
+
},
|
|
323
|
+
required: ["body"]
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
var SAMPLE = {
|
|
327
|
+
store_name: "Sua Loja",
|
|
328
|
+
company_name: "Sua Empresa",
|
|
329
|
+
customer_name: "Maria Silva",
|
|
330
|
+
contact_name: "Maria Silva",
|
|
331
|
+
lead_name: "Maria Silva",
|
|
332
|
+
order_number: "1042",
|
|
333
|
+
order_total: "R$ 189,90",
|
|
334
|
+
order_items: "1 \xD7 Camiseta Cl\xE1ssica \u2014 R$ 89,90\n2 \xD7 Caneca Esmaltada \u2014 R$ 100,00",
|
|
335
|
+
tracking_code: "BR123456789BR",
|
|
336
|
+
cart_url: "https://sualoja.com.br/carrinho",
|
|
337
|
+
deal_name: "Proposta \u2014 Reforma da fachada",
|
|
338
|
+
deal_value: "R$ 12.400,00",
|
|
339
|
+
stage_name: "Negocia\xE7\xE3o",
|
|
340
|
+
owner_name: "Jo\xE3o Pereira",
|
|
341
|
+
appointment_date: "12/08/2026",
|
|
342
|
+
appointment_time: "14:30"
|
|
168
343
|
};
|
|
169
344
|
function iconFor(name) {
|
|
170
345
|
if (!name) return Zap;
|
|
171
346
|
const icons = LucideIcons;
|
|
172
347
|
return icons[name] ?? Zap;
|
|
173
348
|
}
|
|
174
|
-
function
|
|
349
|
+
function AutomationDetailPage({ eventKey, channel, store, backTo = "/automations", embedded, onDone }) {
|
|
175
350
|
const tenantId = useOrganizationStoreLike();
|
|
176
351
|
const runtime = usePluginRuntimeOptional();
|
|
177
|
-
const
|
|
178
|
-
const [
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
const
|
|
352
|
+
const fayzConnection = React2.useMemo(() => resolveFayzAgentConnection(), []);
|
|
353
|
+
const [message, setMessage] = React2.useState(void 0);
|
|
354
|
+
const [tab, setTab] = React2.useState("preview");
|
|
355
|
+
const [device, setDevice] = React2.useState("desktop");
|
|
356
|
+
const [draft, setDraft] = React2.useState(null);
|
|
357
|
+
const [saving, setSaving] = React2.useState(false);
|
|
358
|
+
const back = React2.useCallback(
|
|
359
|
+
() => onDone ? onDone() : adminNavigateTo(backTo),
|
|
360
|
+
[onDone, backTo]
|
|
361
|
+
);
|
|
362
|
+
const surfaceRef = React2.useRef({ message, draft });
|
|
363
|
+
surfaceRef.current = { message, draft };
|
|
364
|
+
const agentSurface = React2.useMemo(() => ({
|
|
365
|
+
id: "automations.message",
|
|
366
|
+
pluginId: "automations",
|
|
367
|
+
instructions: "The user is editing one automation message. `placeholders` in the surface state are the ONLY variables that resolve \u2014 copy them verbatim, braces included. Call writeAutomationMessage once with the whole text; the editor fills and the user reviews before saving.",
|
|
368
|
+
describe: () => {
|
|
369
|
+
const o = surfaceRef.current;
|
|
370
|
+
if (!o.message) return { title: "Automa\xE7\xE3o" };
|
|
371
|
+
const current2 = o.draft ?? { subject: o.message.subject, body: o.message.body };
|
|
372
|
+
return {
|
|
373
|
+
title: `Automa\xE7\xE3o \u2014 ${o.message.event?.title ?? o.message.event?.name ?? ""}`,
|
|
374
|
+
state: {
|
|
375
|
+
event: o.message.event?.name ?? null,
|
|
376
|
+
channel: o.message.channel,
|
|
377
|
+
hasSubject: o.message.channel === "email",
|
|
378
|
+
placeholders: Object.keys(SAMPLE).map((key) => `{{${key}}}`),
|
|
379
|
+
currentSubject: current2.subject,
|
|
380
|
+
currentBody: current2.body
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
},
|
|
384
|
+
tools: [{
|
|
385
|
+
tool: WRITE_MESSAGE_TOOL,
|
|
386
|
+
execute: (args) => {
|
|
387
|
+
const o = surfaceRef.current;
|
|
388
|
+
if (!o.message) return { ok: false, error: "no_message_open" };
|
|
389
|
+
const current2 = o.draft ?? { subject: o.message.subject, body: o.message.body };
|
|
390
|
+
const body = String(args.body ?? "").trim();
|
|
391
|
+
if (!body) return { ok: false, error: "empty_body" };
|
|
392
|
+
const subject = args.subject === void 0 ? current2.subject : String(args.subject);
|
|
393
|
+
setDraft({ subject, body });
|
|
394
|
+
setTab("code");
|
|
395
|
+
return { ok: true, filled: true, saved: false, note: "Draft filled \u2014 the user still has to press Save." };
|
|
396
|
+
}
|
|
397
|
+
}]
|
|
398
|
+
}), []);
|
|
399
|
+
useAgentSurface(agentSurface);
|
|
400
|
+
const reload = React2.useCallback(async () => {
|
|
401
|
+
if (!eventKey || !channel) {
|
|
402
|
+
setMessage(null);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
182
405
|
let overrides = [];
|
|
183
406
|
try {
|
|
184
407
|
overrides = await store.list(tenantId);
|
|
185
408
|
} catch (err) {
|
|
186
|
-
console.warn("[automations] could not load
|
|
409
|
+
console.warn("[automations] could not load overrides; showing defaults", err);
|
|
187
410
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
411
|
+
const found = resolveMessage(decodeURIComponent(eventKey), channel, overrides, runtime?.events ?? []);
|
|
412
|
+
setMessage(found);
|
|
413
|
+
if (found) setDraft({ subject: found.subject, body: found.body });
|
|
414
|
+
}, [eventKey, channel, store, tenantId, runtime]);
|
|
415
|
+
React2.useEffect(() => {
|
|
191
416
|
void reload();
|
|
192
417
|
}, [reload]);
|
|
193
|
-
const
|
|
194
|
-
|
|
418
|
+
const header = embedded ? null : /* @__PURE__ */ jsx(
|
|
419
|
+
SubpageHeader,
|
|
420
|
+
{
|
|
421
|
+
title: message === void 0 ? "" : message?.override?.name ?? message?.event.title ?? message?.event.name ?? "Automa\xE7\xE3o",
|
|
422
|
+
parentLabel: "Automa\xE7\xF5es",
|
|
423
|
+
onBack: back
|
|
424
|
+
}
|
|
425
|
+
);
|
|
426
|
+
if (message === void 0) {
|
|
427
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
428
|
+
header,
|
|
429
|
+
/* @__PURE__ */ jsx("div", { className: "h-8 w-64 animate-pulse rounded bg-muted" }),
|
|
430
|
+
/* @__PURE__ */ jsx(Card, { className: "h-96 animate-pulse" })
|
|
431
|
+
] });
|
|
432
|
+
}
|
|
433
|
+
if (!message) {
|
|
434
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
435
|
+
header,
|
|
436
|
+
/* @__PURE__ */ jsxs(Card, { className: "flex flex-col items-center gap-2 p-12 text-center", children: [
|
|
437
|
+
/* @__PURE__ */ jsx(Mail, { className: "h-9 w-9 text-muted-foreground/40", strokeWidth: 1.5 }),
|
|
438
|
+
/* @__PURE__ */ jsx("p", { className: "font-semibold", children: "Automa\xE7\xE3o n\xE3o encontrada" }),
|
|
439
|
+
/* @__PURE__ */ jsxs("p", { className: "max-w-sm text-sm text-muted-foreground", children: [
|
|
440
|
+
"O evento ",
|
|
441
|
+
/* @__PURE__ */ jsx("code", { className: "font-mono", children: eventKey }),
|
|
442
|
+
" n\xE3o est\xE1 no cat\xE1logo \u2014 o m\xF3dulo que o registra pode n\xE3o estar instalado nesta conta."
|
|
443
|
+
] }),
|
|
444
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", className: "mt-2", onClick: back, children: "Voltar para Automa\xE7\xF5es" })
|
|
445
|
+
] })
|
|
446
|
+
] });
|
|
447
|
+
}
|
|
448
|
+
const event = message.event;
|
|
449
|
+
const fallback = { subject: message.override ? void 0 : message.subject, body: message.body };
|
|
450
|
+
const Icon = iconFor(event.icon);
|
|
451
|
+
const current = draft ?? { subject: message.subject, body: message.body };
|
|
452
|
+
const dirty = current.subject !== message.subject || current.body !== message.body;
|
|
453
|
+
const hasSubject = message.channel === "email";
|
|
454
|
+
const patch = async (input) => {
|
|
455
|
+
setSaving(true);
|
|
195
456
|
try {
|
|
196
|
-
await store.upsert(
|
|
457
|
+
await store.upsert(input, tenantId);
|
|
197
458
|
await reload();
|
|
198
459
|
} catch (err) {
|
|
199
|
-
console.warn("[automations]
|
|
460
|
+
console.warn("[automations] save failed", err);
|
|
200
461
|
toast.error("N\xE3o foi poss\xEDvel salvar");
|
|
201
|
-
|
|
462
|
+
} finally {
|
|
463
|
+
setSaving(false);
|
|
202
464
|
}
|
|
203
465
|
};
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
466
|
+
const save = () => void patch({
|
|
467
|
+
eventKey: event.name,
|
|
468
|
+
channel: message.channel,
|
|
469
|
+
// Igual ao padrão volta a ser "sem personalização", não uma cópia.
|
|
470
|
+
subject: current.subject.trim() === (fallback?.subject ?? "") ? null : current.subject.trim() || null,
|
|
471
|
+
body: current.body.trim() === fallback?.body ? null : current.body.trim() || null
|
|
472
|
+
});
|
|
473
|
+
const reset = async () => {
|
|
474
|
+
setDraft({ subject: fallback?.subject ?? "", body: fallback?.body ?? "" });
|
|
475
|
+
setSaving(true);
|
|
476
|
+
try {
|
|
477
|
+
await store.reset(event.name, message.channel, tenantId);
|
|
478
|
+
await reload();
|
|
479
|
+
toast.success("Texto restaurado");
|
|
480
|
+
} catch (err) {
|
|
481
|
+
console.warn("[automations] reset failed", err);
|
|
482
|
+
toast.error("N\xE3o foi poss\xEDvel restaurar");
|
|
483
|
+
} finally {
|
|
484
|
+
setSaving(false);
|
|
214
485
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
"
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
/* @__PURE__ */
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
] }) : groups.map((group) => /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
236
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: group.label }),
|
|
237
|
-
group.items.map((m) => {
|
|
238
|
-
const Icon = iconFor(m.event.icon ?? m.event.pluginIcon);
|
|
239
|
-
const ChannelIcon = CHANNEL_ICON[m.channel];
|
|
240
|
-
const href = `/automations/${encodeURIComponent(m.event.name)}/${m.channel}`;
|
|
241
|
-
return /* @__PURE__ */ jsx(
|
|
242
|
-
Card,
|
|
486
|
+
};
|
|
487
|
+
const previewSubject = renderMessageTemplate(current.subject, SAMPLE);
|
|
488
|
+
const previewBody = renderMessageTemplate(current.body, SAMPLE);
|
|
489
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
|
|
490
|
+
header,
|
|
491
|
+
/* @__PURE__ */ jsxs("div", { className: embedded ? "space-y-3" : "flex flex-wrap items-start gap-3", children: [
|
|
492
|
+
/* @__PURE__ */ jsxs("div", { className: embedded ? "flex items-start gap-3" : "contents", children: [
|
|
493
|
+
/* @__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" }) }),
|
|
494
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
495
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
496
|
+
/* @__PURE__ */ jsx(Badge, { variant: "outline", children: CHANNEL_LABEL[message.channel] }),
|
|
497
|
+
/* @__PURE__ */ jsx(Badge, { variant: "outline", children: message.kind === "marketing" ? "Marketing" : "Transacional" }),
|
|
498
|
+
message.customized && /* @__PURE__ */ jsx(Badge, { variant: "info", children: "Personalizado" })
|
|
499
|
+
] }),
|
|
500
|
+
/* @__PURE__ */ jsx("p", { className: "mt-0.5 text-sm text-muted-foreground", children: event.description })
|
|
501
|
+
] })
|
|
502
|
+
] }),
|
|
503
|
+
/* @__PURE__ */ jsxs("div", { className: embedded ? "flex items-center gap-2" : "flex shrink-0 flex-wrap items-center gap-2", children: [
|
|
504
|
+
/* @__PURE__ */ jsx(
|
|
505
|
+
Switch,
|
|
243
506
|
{
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
507
|
+
size: "md",
|
|
508
|
+
checked: message.enabled,
|
|
509
|
+
onCheckedChange: (next) => void patch({ eventKey: event.name, channel: message.channel, enabled: next }),
|
|
510
|
+
disabled: saving,
|
|
511
|
+
label: message.enabled ? "Ativa" : "Inativa"
|
|
512
|
+
}
|
|
513
|
+
),
|
|
514
|
+
/* @__PURE__ */ jsxs("div", { className: embedded ? "ml-auto flex items-center gap-2" : "contents", children: [
|
|
515
|
+
/* @__PURE__ */ jsx(Hint, { label: "Descarta o texto personalizado e volta ao padr\xE3o do produto.", children: /* @__PURE__ */ jsx("span", { children: /* @__PURE__ */ jsxs(
|
|
516
|
+
Button,
|
|
517
|
+
{
|
|
518
|
+
variant: embedded ? "ghost" : "outline",
|
|
519
|
+
size: embedded ? "icon" : void 0,
|
|
520
|
+
onClick: () => void reset(),
|
|
521
|
+
disabled: !message.customized || saving,
|
|
522
|
+
"aria-label": embedded ? "Restaurar padr\xE3o" : void 0,
|
|
523
|
+
children: [
|
|
524
|
+
/* @__PURE__ */ jsx(RotateCcw, { className: embedded ? "h-4 w-4" : "mr-2 h-4 w-4" }),
|
|
525
|
+
!embedded && "Restaurar padr\xE3o"
|
|
526
|
+
]
|
|
527
|
+
}
|
|
528
|
+
) }) }),
|
|
529
|
+
/* @__PURE__ */ jsx(
|
|
530
|
+
AskAIButton,
|
|
531
|
+
{
|
|
532
|
+
variant: embedded ? "ghost" : "outline",
|
|
533
|
+
label: embedded ? "" : void 0,
|
|
534
|
+
hint: "Descreva o tom da mensagem\u2026",
|
|
535
|
+
intro: {
|
|
536
|
+
role: "setup",
|
|
537
|
+
persona: "Redator das automa\xE7\xF5es",
|
|
538
|
+
pitch: "Diga como esta mensagem deve soar.",
|
|
539
|
+
description: "Escrevo o assunto e o corpo desta automa\xE7\xE3o usando as vari\xE1veis que ela tem \xE0 disposi\xE7\xE3o. O texto cai no editor ao lado para voc\xEA revisar \u2014 nada \xE9 salvo sem voc\xEA.",
|
|
540
|
+
suggestions: [
|
|
541
|
+
{ label: "Mais curta e direta" },
|
|
542
|
+
{ label: "Mais calorosa, com o nome do cliente" },
|
|
543
|
+
{ label: "Formal, para um p\xFAblico corporativo" }
|
|
544
|
+
]
|
|
251
545
|
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
] })
|
|
546
|
+
}
|
|
547
|
+
),
|
|
548
|
+
/* @__PURE__ */ jsx(Button, { onClick: save, disabled: !dirty || saving, children: saving ? "Salvando\u2026" : dirty ? "Salvar" : "Salvo" })
|
|
549
|
+
] })
|
|
550
|
+
] })
|
|
551
|
+
] }),
|
|
552
|
+
/* @__PURE__ */ jsx(HeaderTabs, { tabs: TABS, value: tab, onChange: setTab, "aria-label": "Visualiza\xE7\xE3o" }),
|
|
553
|
+
/* @__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: [
|
|
554
|
+
/* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5", children: [
|
|
555
|
+
/* @__PURE__ */ jsx(Zap, { className: "h-3.5 w-3.5" }),
|
|
556
|
+
"Gatilho:",
|
|
557
|
+
/* @__PURE__ */ jsx("code", { className: "rounded bg-muted px-1.5 py-0.5 font-mono text-[11px] text-foreground", children: event.name })
|
|
558
|
+
] }),
|
|
559
|
+
/* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5", children: [
|
|
560
|
+
/* @__PURE__ */ jsx(Clock, { className: "h-3.5 w-3.5" }),
|
|
561
|
+
event.trigger
|
|
562
|
+
] }),
|
|
563
|
+
hasSubject && current.subject && /* @__PURE__ */ jsxs("span", { className: "min-w-0 truncate", children: [
|
|
564
|
+
"Assunto: ",
|
|
565
|
+
current.subject
|
|
566
|
+
] })
|
|
567
|
+
] }),
|
|
568
|
+
tab === "runs" ? (
|
|
569
|
+
// Atrás de um projeto Fayz o histórico verdadeiro é o da plataforma —
|
|
570
|
+
// é ela quem executa. Fora dele, o coletor local continua valendo.
|
|
571
|
+
fayzConnection ? /* @__PURE__ */ jsx(FayzRunLog, { connection: fayzConnection, eventKey: event.name, tenantId, enabled: message.enabled }) : /* @__PURE__ */ jsx(RunLog, { eventKey: event.name, tenantId, enabled: message.enabled })
|
|
572
|
+
) : tab === "preview" ? /* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
|
|
573
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 border-b border-border px-4 py-2.5", children: [
|
|
574
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: "Pr\xE9via" }),
|
|
575
|
+
/* @__PURE__ */ jsx("div", { className: embedded ? "hidden" : "flex items-center gap-1", children: ["desktop", "mobile"].map((d) => /* @__PURE__ */ jsx(
|
|
576
|
+
"button",
|
|
577
|
+
{
|
|
578
|
+
type: "button",
|
|
579
|
+
onClick: () => setDevice(d),
|
|
580
|
+
"aria-label": d === "desktop" ? "Computador" : "Celular",
|
|
581
|
+
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"}`,
|
|
582
|
+
children: d === "desktop" ? /* @__PURE__ */ jsx(Monitor, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Smartphone, { className: "h-4 w-4" })
|
|
290
583
|
},
|
|
291
|
-
|
|
292
|
-
)
|
|
293
|
-
})
|
|
294
|
-
|
|
295
|
-
|
|
584
|
+
d
|
|
585
|
+
)) })
|
|
586
|
+
] }),
|
|
587
|
+
/* @__PURE__ */ jsxs("div", { className: embedded ? "bg-muted/30 p-4" : "bg-muted/30 p-6", children: [
|
|
588
|
+
/* @__PURE__ */ jsx(
|
|
589
|
+
"div",
|
|
590
|
+
{
|
|
591
|
+
className: `mx-auto rounded-card border border-border bg-card shadow-sm transition-[max-width] ${device === "mobile" ? "max-w-[380px]" : hasSubject ? "max-w-[600px]" : "max-w-[420px]"}`,
|
|
592
|
+
children: hasSubject ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
593
|
+
/* @__PURE__ */ jsx("div", { className: "border-b border-border px-6 py-4 text-sm font-semibold", children: SAMPLE.store_name }),
|
|
594
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1 border-b border-border px-6 py-3", children: [
|
|
595
|
+
/* @__PURE__ */ jsx("p", { className: "text-[11px] uppercase tracking-wide text-muted-foreground", children: "Assunto" }),
|
|
596
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: previewSubject })
|
|
597
|
+
] }),
|
|
598
|
+
/* @__PURE__ */ jsx("div", { className: "whitespace-pre-wrap px-6 py-5 text-sm leading-relaxed", children: previewBody })
|
|
599
|
+
] }) : /* @__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 }) })
|
|
600
|
+
}
|
|
601
|
+
),
|
|
602
|
+
/* @__PURE__ */ jsx("p", { className: "mt-4 text-center text-xs text-muted-foreground", children: "Dados de exemplo \u2014 nenhum registro real foi usado." })
|
|
603
|
+
] })
|
|
604
|
+
] }) : /* @__PURE__ */ jsxs(Card, { className: "space-y-4 p-4", children: [
|
|
605
|
+
hasSubject && /* @__PURE__ */ jsxs("div", { children: [
|
|
606
|
+
/* @__PURE__ */ jsx("label", { className: "mb-1 block text-xs font-medium text-muted-foreground", children: "Assunto" }),
|
|
607
|
+
/* @__PURE__ */ jsx(
|
|
608
|
+
"input",
|
|
609
|
+
{
|
|
610
|
+
value: current.subject,
|
|
611
|
+
onChange: (e) => setDraft({ ...current, subject: e.target.value }),
|
|
612
|
+
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"
|
|
613
|
+
}
|
|
614
|
+
)
|
|
615
|
+
] }),
|
|
616
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
617
|
+
/* @__PURE__ */ jsx("label", { className: "mb-1 block text-xs font-medium text-muted-foreground", children: "Mensagem" }),
|
|
618
|
+
/* @__PURE__ */ jsx(
|
|
619
|
+
"textarea",
|
|
620
|
+
{
|
|
621
|
+
rows: 14,
|
|
622
|
+
value: current.body,
|
|
623
|
+
onChange: (e) => setDraft({ ...current, body: e.target.value }),
|
|
624
|
+
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"
|
|
625
|
+
}
|
|
626
|
+
)
|
|
627
|
+
] }),
|
|
628
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
629
|
+
/* @__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" }),
|
|
630
|
+
/* @__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)) })
|
|
631
|
+
] })
|
|
632
|
+
] })
|
|
296
633
|
] });
|
|
297
634
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
function catalogDefaultEnabled(eventKey, channel) {
|
|
318
|
-
const def = getMessageDefaults().find((d) => d.eventKey === eventKey);
|
|
319
|
-
return def?.defaultEnabled?.[channel] ?? false;
|
|
320
|
-
}
|
|
321
|
-
async function defaultSessionToken() {
|
|
322
|
-
const session = (await getFayzSupabaseClientOptional()?.auth.getSession())?.data.session;
|
|
323
|
-
return session?.access_token ?? null;
|
|
324
|
-
}
|
|
325
|
-
function createFayzTemplateStore(connection, deps = {}) {
|
|
326
|
-
const client = deps.client ?? createFayzAutomationsClient({
|
|
327
|
-
baseUrl: connection.apiBaseUrl,
|
|
328
|
-
projectId: connection.projectId,
|
|
329
|
-
publishableKey: connection.publishableKey
|
|
330
|
-
});
|
|
331
|
-
const getSessionToken = deps.getSessionToken ?? defaultSessionToken;
|
|
332
|
-
const ids = /* @__PURE__ */ new Map();
|
|
333
|
-
const keyOf = (eventKey, channel) => `${eventKey}:${channel}`;
|
|
334
|
-
async function refresh(tenantId) {
|
|
335
|
-
const automations = await client.list({ tenantId });
|
|
336
|
-
ids.clear();
|
|
337
|
-
const rows = [];
|
|
338
|
-
for (const automation of automations) {
|
|
339
|
-
const row = toTemplate(automation);
|
|
340
|
-
if (!row) continue;
|
|
341
|
-
ids.set(keyOf(row.eventKey, row.channel), automation.id);
|
|
342
|
-
rows.push(row);
|
|
343
|
-
}
|
|
344
|
-
return rows;
|
|
345
|
-
}
|
|
346
|
-
const LIST_TTL_MS = 3e4;
|
|
347
|
-
let cache = null;
|
|
348
|
-
function invalidate() {
|
|
349
|
-
cache = null;
|
|
350
|
-
}
|
|
351
|
-
function load(tenantId) {
|
|
352
|
-
if (cache && cache.tenantId === tenantId && Date.now() - cache.at < LIST_TTL_MS) return cache.promise;
|
|
353
|
-
const promise = refresh(tenantId);
|
|
354
|
-
promise.catch(() => {
|
|
355
|
-
if (cache?.promise === promise) cache = null;
|
|
356
|
-
});
|
|
357
|
-
cache = { tenantId, at: Date.now(), promise };
|
|
358
|
-
return promise;
|
|
359
|
-
}
|
|
360
|
-
async function requireToken() {
|
|
361
|
-
const token = await getSessionToken();
|
|
362
|
-
if (!token) throw new Error("Sess\xE3o expirada \u2014 entre de novo.");
|
|
363
|
-
return token;
|
|
364
|
-
}
|
|
365
|
-
async function resolveId(eventKey, channel, tenantId) {
|
|
366
|
-
const key = keyOf(eventKey, channel);
|
|
367
|
-
if (!ids.has(key)) await load(tenantId);
|
|
368
|
-
return ids.get(key);
|
|
369
|
-
}
|
|
370
|
-
return {
|
|
371
|
-
async list(tenantId) {
|
|
372
|
-
if (!tenantId) return [];
|
|
373
|
-
return load(tenantId);
|
|
374
|
-
},
|
|
375
|
-
async upsert(input, tenantId) {
|
|
376
|
-
if (!tenantId) throw new Error("@fayz-ai/plugin-automations: tenantId is required");
|
|
377
|
-
const appSessionToken = await requireToken();
|
|
378
|
-
const key = keyOf(input.eventKey, input.channel);
|
|
379
|
-
let id = await resolveId(input.eventKey, input.channel, tenantId);
|
|
380
|
-
if (!id) {
|
|
381
|
-
const action = {};
|
|
382
|
-
if (input.subject != null) action.subject = input.subject;
|
|
383
|
-
if (input.body != null) action.body = input.body;
|
|
384
|
-
try {
|
|
385
|
-
const created = await client.create({
|
|
386
|
-
tenantId,
|
|
387
|
-
appSessionToken,
|
|
388
|
-
eventKey: input.eventKey,
|
|
389
|
-
sourceChannel: input.channel,
|
|
390
|
-
// The platform has no "inherit from catalogue": materialise the default.
|
|
391
|
-
enabled: input.enabled ?? catalogDefaultEnabled(input.eventKey, input.channel),
|
|
392
|
-
...Object.keys(action).length ? { action } : {}
|
|
393
|
-
});
|
|
394
|
-
ids.set(key, created.id);
|
|
395
|
-
invalidate();
|
|
396
|
-
const row2 = toTemplate(created);
|
|
397
|
-
if (!row2) throw new Error("@fayz-ai/plugin-automations: platform did not round-trip eventKey");
|
|
398
|
-
return row2;
|
|
399
|
-
} catch (err) {
|
|
400
|
-
if (!(err instanceof FayzAutomationsError) || err.status !== 409) throw err;
|
|
401
|
-
invalidate();
|
|
402
|
-
await refresh(tenantId);
|
|
403
|
-
id = ids.get(key);
|
|
404
|
-
if (!id) throw err;
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
const updated = await client.update({
|
|
408
|
-
id,
|
|
409
|
-
tenantId,
|
|
410
|
-
appSessionToken,
|
|
411
|
-
// Only what was sent: omitting has to mean "leave it alone", as in the local store.
|
|
412
|
-
...typeof input.enabled === "boolean" ? { enabled: input.enabled } : {},
|
|
413
|
-
...input.subject !== void 0 || input.body !== void 0 ? {
|
|
414
|
-
action: {
|
|
415
|
-
...input.subject !== void 0 ? { subject: input.subject } : {},
|
|
416
|
-
...input.body !== void 0 ? { body: input.body } : {}
|
|
417
|
-
}
|
|
418
|
-
} : {}
|
|
635
|
+
|
|
636
|
+
// src/lib/connector-automations.ts
|
|
637
|
+
function connectorAutomationRows(connectors) {
|
|
638
|
+
const out = [];
|
|
639
|
+
for (const connector of connectors) {
|
|
640
|
+
if (connector.state !== "connected") continue;
|
|
641
|
+
for (const auto of connector.definition?.managedAutomations ?? []) {
|
|
642
|
+
out.push({
|
|
643
|
+
connectorId: connector.id,
|
|
644
|
+
connectorName: connector.name,
|
|
645
|
+
hostPluginId: connector.hostPluginId,
|
|
646
|
+
icon: connector.icon,
|
|
647
|
+
logoUrl: connector.logoUrl,
|
|
648
|
+
eventKey: auto.eventKey,
|
|
649
|
+
label: auto.label,
|
|
650
|
+
optional: auto.optional ?? false,
|
|
651
|
+
...connector.definition?.listDeliveries ? { listDeliveries: connector.definition.listDeliveries.bind(connector.definition) } : {}
|
|
419
652
|
});
|
|
420
|
-
invalidate();
|
|
421
|
-
const row = toTemplate(updated);
|
|
422
|
-
if (!row) throw new Error("@fayz-ai/plugin-automations: platform did not round-trip eventKey");
|
|
423
|
-
return row;
|
|
424
|
-
},
|
|
425
|
-
async reset(eventKey, channel, tenantId) {
|
|
426
|
-
if (!tenantId) return;
|
|
427
|
-
const id = await resolveId(eventKey, channel, tenantId);
|
|
428
|
-
if (!id) return;
|
|
429
|
-
const appSessionToken = await requireToken();
|
|
430
|
-
await client.update({ id, tenantId, appSessionToken, action: { subject: null, body: null } });
|
|
431
|
-
invalidate();
|
|
432
653
|
}
|
|
433
|
-
}
|
|
654
|
+
}
|
|
655
|
+
return out;
|
|
434
656
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
657
|
+
function groupConnectorAutomations(rows) {
|
|
658
|
+
const groups = [];
|
|
659
|
+
for (const row of rows) {
|
|
660
|
+
let group = groups.find((g) => g.connectorId === row.connectorId);
|
|
661
|
+
if (!group) {
|
|
662
|
+
group = {
|
|
663
|
+
connectorId: row.connectorId,
|
|
664
|
+
connectorName: row.connectorName,
|
|
665
|
+
hostPluginId: row.hostPluginId,
|
|
666
|
+
icon: row.icon,
|
|
667
|
+
logoUrl: row.logoUrl,
|
|
668
|
+
items: []
|
|
669
|
+
};
|
|
670
|
+
groups.push(group);
|
|
671
|
+
}
|
|
672
|
+
group.items.push(row);
|
|
673
|
+
}
|
|
674
|
+
return groups;
|
|
446
675
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
if (min < 60) return `h\xE1 ${min} min`;
|
|
465
|
-
const h = Math.round(min / 60);
|
|
466
|
-
if (h < 24) return `h\xE1 ${h} h`;
|
|
467
|
-
return new Date(iso).toLocaleString("pt-BR", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" });
|
|
676
|
+
function ConnectorAutomations({ rows }) {
|
|
677
|
+
if (rows.length === 0) return null;
|
|
678
|
+
return /* @__PURE__ */ jsx(Fragment, { children: groupConnectorAutomations(rows).map((group) => {
|
|
679
|
+
const href = connectorRoute({ hostPluginId: group.hostPluginId });
|
|
680
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
681
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: group.connectorName }),
|
|
682
|
+
group.items.map((row) => /* @__PURE__ */ jsx(
|
|
683
|
+
AutomationCard,
|
|
684
|
+
{
|
|
685
|
+
row,
|
|
686
|
+
group,
|
|
687
|
+
href
|
|
688
|
+
},
|
|
689
|
+
`${group.connectorId}:${row.eventKey}`
|
|
690
|
+
))
|
|
691
|
+
] }, group.connectorId);
|
|
692
|
+
}) });
|
|
468
693
|
}
|
|
469
|
-
function
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
694
|
+
function AutomationCard({
|
|
695
|
+
row,
|
|
696
|
+
group,
|
|
697
|
+
href
|
|
698
|
+
}) {
|
|
699
|
+
const [open, setOpen] = React2.useState(false);
|
|
700
|
+
return /* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
|
|
474
701
|
/* @__PURE__ */ jsxs(
|
|
475
|
-
"
|
|
702
|
+
"div",
|
|
476
703
|
{
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
704
|
+
role: "button",
|
|
705
|
+
tabIndex: 0,
|
|
706
|
+
onClick: () => adminNavigateTo(href),
|
|
707
|
+
onKeyDown: (e) => {
|
|
708
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
709
|
+
e.preventDefault();
|
|
710
|
+
adminNavigateTo(href);
|
|
711
|
+
}
|
|
712
|
+
},
|
|
713
|
+
className: "flex cursor-pointer items-start gap-4 p-4 transition-colors hover:bg-muted/30 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
480
714
|
children: [
|
|
481
|
-
/* @__PURE__ */ jsx(
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
715
|
+
/* @__PURE__ */ jsx(
|
|
716
|
+
ConnectorMark,
|
|
717
|
+
{
|
|
718
|
+
connector: {
|
|
719
|
+
id: group.connectorId,
|
|
720
|
+
name: group.connectorName,
|
|
721
|
+
icon: group.icon,
|
|
722
|
+
logoUrl: group.logoUrl
|
|
723
|
+
},
|
|
724
|
+
size: "sm"
|
|
725
|
+
}
|
|
726
|
+
),
|
|
727
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
728
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium", children: row.label }),
|
|
729
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
|
|
730
|
+
"Quando ",
|
|
731
|
+
/* @__PURE__ */ jsx("code", { className: "font-mono text-xs", children: row.eventKey }),
|
|
732
|
+
" acontece aqui."
|
|
733
|
+
] })
|
|
734
|
+
] }),
|
|
735
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 pt-0.5 text-right text-xs text-muted-foreground", children: row.optional ? `Opcional \u2014 desligue em ${group.connectorName}` : `Ligada pela integra\xE7\xE3o \xB7 sai ao desconectar ${group.connectorName}` })
|
|
486
736
|
]
|
|
487
737
|
}
|
|
488
738
|
),
|
|
489
|
-
|
|
490
|
-
/* @__PURE__ */
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
739
|
+
row.listDeliveries && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
740
|
+
/* @__PURE__ */ jsxs(
|
|
741
|
+
"button",
|
|
742
|
+
{
|
|
743
|
+
type: "button",
|
|
744
|
+
onClick: () => setOpen(!open),
|
|
745
|
+
"aria-expanded": open,
|
|
746
|
+
className: "flex w-full items-center gap-2 border-t border-border px-4 py-2 text-left transition-colors hover:bg-muted/40",
|
|
747
|
+
children: [
|
|
748
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: `h-3.5 w-3.5 shrink-0 text-muted-foreground transition-transform ${open ? "rotate-90" : ""}` }),
|
|
749
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs font-medium", children: [
|
|
750
|
+
"O que j\xE1 foi para o ",
|
|
751
|
+
group.connectorName
|
|
752
|
+
] })
|
|
753
|
+
]
|
|
754
|
+
}
|
|
755
|
+
),
|
|
756
|
+
open && /* @__PURE__ */ jsx("div", { className: "border-t border-border p-4", children: /* @__PURE__ */ jsx(
|
|
757
|
+
ConnectorDeliveries,
|
|
758
|
+
{
|
|
759
|
+
load: (options) => row.listDeliveries({ ...options, eventKey: row.eventKey }),
|
|
760
|
+
providerName: group.connectorName
|
|
761
|
+
}
|
|
762
|
+
) })
|
|
500
763
|
] })
|
|
501
764
|
] });
|
|
502
765
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
var EXEC_STATUS = {
|
|
530
|
-
success: { label: "Executada", variant: "success", Icon: CircleCheck },
|
|
531
|
-
error: { label: "Falhou", variant: "destructive", Icon: AlertTriangle }
|
|
766
|
+
var CONFIGURE_TOOL = {
|
|
767
|
+
id: "automations.configure",
|
|
768
|
+
name: "configureAutomation",
|
|
769
|
+
description: "Turns an automation on or off, and/or rewrites its message. Use the `event` and `channel` values exactly as they appear in the surface state. Placeholders must come from that automation's own list \u2014 an invented one renders as literal text in a message a customer reads. To review several, call once per automation.",
|
|
770
|
+
icon: "Zap",
|
|
771
|
+
mode: "persist",
|
|
772
|
+
execution: { plane: "client" },
|
|
773
|
+
category: "Automations",
|
|
774
|
+
parameters: {
|
|
775
|
+
type: "object",
|
|
776
|
+
properties: {
|
|
777
|
+
event: { type: "string", description: "Event key from the surface state." },
|
|
778
|
+
channel: { type: "string", description: "Channel of that automation (email | whatsapp | sms | in_app)." },
|
|
779
|
+
enabled: { type: "boolean", description: "Turn it on or off. Omit to leave as is." },
|
|
780
|
+
subject: { type: "string", description: "New subject. Email only." },
|
|
781
|
+
body: { type: "string", description: "New message body." }
|
|
782
|
+
},
|
|
783
|
+
required: ["event", "channel"]
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
var CHANNEL_ICON = {
|
|
787
|
+
email: Mail,
|
|
788
|
+
whatsapp: MessageCircle,
|
|
789
|
+
sms: Smartphone,
|
|
790
|
+
push: MessageSquare,
|
|
791
|
+
webchat: MessageSquare
|
|
532
792
|
};
|
|
533
|
-
function ExecutionRow({ execution }) {
|
|
534
|
-
const cfg = EXEC_STATUS[execution.status] ?? { label: execution.status, variant: "info", Icon: History };
|
|
535
|
-
const { Icon } = cfg;
|
|
536
|
-
return /* @__PURE__ */ jsxs("div", { className: "border-b border-border px-4 py-2.5 last:border-b-0", children: [
|
|
537
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
538
|
-
/* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }),
|
|
539
|
-
/* @__PURE__ */ jsx(Badge, { variant: cfg.variant, children: cfg.label }),
|
|
540
|
-
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-sm text-muted-foreground", children: execution.automationName }),
|
|
541
|
-
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: when(execution.executedAt) })
|
|
542
|
-
] }),
|
|
543
|
-
execution.error && /* @__PURE__ */ jsx("p", { className: "ml-6 mt-2 rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 font-mono text-[11px] text-destructive", children: execution.error })
|
|
544
|
-
] });
|
|
545
|
-
}
|
|
546
|
-
function FayzRunLog({ connection, eventKey, tenantId, enabled }) {
|
|
547
|
-
const [executions, setExecutions] = React3.useState(null);
|
|
548
|
-
const [loading, setLoading] = React3.useState(false);
|
|
549
|
-
const load = React3.useCallback(async () => {
|
|
550
|
-
if (!tenantId) return;
|
|
551
|
-
setLoading(true);
|
|
552
|
-
try {
|
|
553
|
-
setExecutions(await getFayzAutomationsClient(connection).listExecutions({ tenantId, eventKey, limit: 20 }));
|
|
554
|
-
} catch (err) {
|
|
555
|
-
console.warn("[automations] could not load platform executions", err);
|
|
556
|
-
setExecutions([]);
|
|
557
|
-
} finally {
|
|
558
|
-
setLoading(false);
|
|
559
|
-
}
|
|
560
|
-
}, [connection, eventKey, tenantId]);
|
|
561
|
-
React3.useEffect(() => {
|
|
562
|
-
void load();
|
|
563
|
-
}, [load]);
|
|
564
|
-
return /* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
|
|
565
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 border-b border-border px-4 py-2.5", children: [
|
|
566
|
-
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
567
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: "Execu\xE7\xF5es recentes" }),
|
|
568
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "O que a plataforma Fayz executou para este gatilho nesta conta." })
|
|
569
|
-
] }),
|
|
570
|
-
/* @__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" : ""}` }) })
|
|
571
|
-
] }),
|
|
572
|
-
executions === 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)) }) : executions.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1 px-4 py-10 text-center", children: [
|
|
573
|
-
/* @__PURE__ */ jsx(History, { className: "h-7 w-7 text-muted-foreground/40", strokeWidth: 1.5 }),
|
|
574
|
-
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "Nenhuma execu\xE7\xE3o ainda" }),
|
|
575
|
-
/* @__PURE__ */ jsx("p", { className: "max-w-sm text-xs text-muted-foreground", children: enabled ? "Assim que este evento acontecer, a plataforma executa a automa\xE7\xE3o e o resultado aparece aqui." : "A automa\xE7\xE3o est\xE1 desligada \u2014 a plataforma n\xE3o executa nada para este gatilho." })
|
|
576
|
-
] }) : /* @__PURE__ */ jsx("div", { children: executions.map((e) => /* @__PURE__ */ jsx(ExecutionRow, { execution: e }, e.id)) })
|
|
577
|
-
] });
|
|
578
|
-
}
|
|
579
|
-
var TABS = [
|
|
580
|
-
{ value: "preview", label: "Pr\xE9via" },
|
|
581
|
-
{ value: "code", label: "C\xF3digo" },
|
|
582
|
-
// O que já aconteceu. Fica ao lado do texto, e não numa tela à parte, porque
|
|
583
|
-
// é a mesma pergunta em dois tempos: o que vai sair, e o que saiu.
|
|
584
|
-
{ value: "runs", label: "Execu\xE7\xF5es" }
|
|
585
|
-
];
|
|
586
793
|
var CHANNEL_LABEL2 = {
|
|
587
794
|
email: "E-mail",
|
|
588
795
|
whatsapp: "WhatsApp",
|
|
@@ -590,239 +797,272 @@ var CHANNEL_LABEL2 = {
|
|
|
590
797
|
push: "Push",
|
|
591
798
|
webchat: "Chat"
|
|
592
799
|
};
|
|
593
|
-
var
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
tracking_code: "BR123456789BR",
|
|
603
|
-
cart_url: "https://sualoja.com.br/carrinho",
|
|
604
|
-
deal_name: "Proposta \u2014 Reforma da fachada",
|
|
605
|
-
deal_value: "R$ 12.400,00",
|
|
606
|
-
stage_name: "Negocia\xE7\xE3o",
|
|
607
|
-
owner_name: "Jo\xE3o Pereira",
|
|
608
|
-
appointment_date: "12/08/2026",
|
|
609
|
-
appointment_time: "14:30"
|
|
800
|
+
var KIND_TABS = [
|
|
801
|
+
{ value: "all", label: "Todos", icon: Zap },
|
|
802
|
+
{ value: "transactional", label: "Transacionais", icon: Receipt },
|
|
803
|
+
{ value: "marketing", label: "Marketing", icon: Megaphone }
|
|
804
|
+
];
|
|
805
|
+
var KIND_HINT = {
|
|
806
|
+
all: "Tudo que o produto sabe enviar, por qualquer canal.",
|
|
807
|
+
transactional: "Disparados por uma a\xE7\xE3o da pessoa. N\xE3o precisam de opt-in.",
|
|
808
|
+
marketing: "Iniciativa da empresa. S\xF3 para quem aceitou receber."
|
|
610
809
|
};
|
|
611
810
|
function iconFor2(name) {
|
|
612
811
|
if (!name) return Zap;
|
|
613
812
|
const icons = LucideIcons;
|
|
614
813
|
return icons[name] ?? Zap;
|
|
615
814
|
}
|
|
616
|
-
function
|
|
815
|
+
function readSelection(basePath) {
|
|
816
|
+
const hash = window.location.hash.slice(1) || "/";
|
|
817
|
+
if (!hash.startsWith(basePath + "/")) return null;
|
|
818
|
+
const [eventKey, channel] = hash.slice(basePath.length + 1).split("/");
|
|
819
|
+
if (!eventKey || !channel) return null;
|
|
820
|
+
return { eventKey, channel };
|
|
821
|
+
}
|
|
822
|
+
function AutomationsList({ store, variant = "page", basePath = "/automations" }) {
|
|
617
823
|
const tenantId = useOrganizationStoreLike();
|
|
618
824
|
const runtime = usePluginRuntimeOptional();
|
|
619
|
-
const
|
|
620
|
-
const [
|
|
621
|
-
const
|
|
622
|
-
const
|
|
623
|
-
const [
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
825
|
+
const [messages, setMessages] = React2.useState(null);
|
|
826
|
+
const [kind, setKind] = React2.useState("all");
|
|
827
|
+
const { connectors } = useConnectors({ includeGrants: false, includeCatalog: false });
|
|
828
|
+
const owned = React2.useMemo(() => connectorAutomationRows(connectors ?? []), [connectors]);
|
|
829
|
+
const [selected, setSelected] = React2.useState(() => readSelection(basePath));
|
|
830
|
+
React2.useEffect(() => {
|
|
831
|
+
const sync = () => setSelected(readSelection(basePath));
|
|
832
|
+
sync();
|
|
833
|
+
window.addEventListener("hashchange", sync);
|
|
834
|
+
return () => window.removeEventListener("hashchange", sync);
|
|
835
|
+
}, [basePath]);
|
|
836
|
+
const reload = React2.useCallback(async () => {
|
|
631
837
|
let overrides = [];
|
|
632
838
|
try {
|
|
633
839
|
overrides = await store.list(tenantId);
|
|
634
840
|
} catch (err) {
|
|
635
|
-
console.warn("[automations] could not load overrides; showing defaults", err);
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
}, [eventKey, channel, store, tenantId, runtime]);
|
|
641
|
-
React3.useEffect(() => {
|
|
841
|
+
console.warn("[automations] could not load template overrides; showing defaults", err);
|
|
842
|
+
}
|
|
843
|
+
setMessages(resolveMessages(overrides, runtime?.events ?? []));
|
|
844
|
+
}, [store, tenantId, runtime]);
|
|
845
|
+
React2.useEffect(() => {
|
|
642
846
|
void reload();
|
|
643
847
|
}, [reload]);
|
|
644
|
-
const
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
848
|
+
const surfaceRef = React2.useRef({ messages, store, tenantId, reload });
|
|
849
|
+
surfaceRef.current = { messages, store, tenantId, reload };
|
|
850
|
+
const agentSurface = React2.useMemo(() => ({
|
|
851
|
+
id: "automations.list",
|
|
852
|
+
pluginId: "automations",
|
|
853
|
+
instructions: "The user is on the automations list. `automations` in the surface state is the complete set \u2014 use each entry's `event` and `channel` verbatim, and each entry's own `placeholders` when rewriting its text. Call configureAutomation once per automation you change; say afterwards which ones you touched.",
|
|
854
|
+
describe: () => {
|
|
855
|
+
const o = surfaceRef.current;
|
|
856
|
+
return {
|
|
857
|
+
title: "Automa\xE7\xF5es",
|
|
858
|
+
state: {
|
|
859
|
+
automations: (o.messages ?? []).map((m) => ({
|
|
860
|
+
event: m.event.name,
|
|
861
|
+
title: m.event.title,
|
|
862
|
+
channel: m.channel,
|
|
863
|
+
enabled: m.enabled,
|
|
864
|
+
customized: Boolean(m.customized),
|
|
865
|
+
subject: m.subject,
|
|
866
|
+
body: m.body,
|
|
867
|
+
// `fields` é o contrato do evento — os mesmos descritores que viram
|
|
868
|
+
// as merge fields do editor. É de lá que saem os placeholders.
|
|
869
|
+
placeholders: (m.event.fields ?? []).map((f) => `{{${f.name}}}`)
|
|
870
|
+
}))
|
|
871
|
+
}
|
|
872
|
+
};
|
|
873
|
+
},
|
|
874
|
+
tools: [{
|
|
875
|
+
tool: CONFIGURE_TOOL,
|
|
876
|
+
execute: async (args) => {
|
|
877
|
+
const o = surfaceRef.current;
|
|
878
|
+
const found = (o.messages ?? []).find(
|
|
879
|
+
(m) => m.event.name === args.event && m.channel === args.channel
|
|
880
|
+
);
|
|
881
|
+
if (!found) {
|
|
882
|
+
return {
|
|
883
|
+
ok: false,
|
|
884
|
+
error: "unknown_automation",
|
|
885
|
+
available: (o.messages ?? []).map((m) => `${m.event.name}/${m.channel}`)
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
try {
|
|
889
|
+
await o.store.upsert({
|
|
890
|
+
eventKey: found.event.name,
|
|
891
|
+
channel: found.channel,
|
|
892
|
+
...args.enabled === void 0 ? {} : { enabled: Boolean(args.enabled) },
|
|
893
|
+
...args.subject === void 0 ? {} : { subject: String(args.subject) },
|
|
894
|
+
...args.body === void 0 ? {} : { body: String(args.body) }
|
|
895
|
+
}, o.tenantId);
|
|
896
|
+
await o.reload();
|
|
897
|
+
return { ok: true, event: found.event.name, channel: found.channel };
|
|
898
|
+
} catch (e) {
|
|
899
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}]
|
|
903
|
+
}), []);
|
|
904
|
+
useAgentSurface(agentSurface);
|
|
905
|
+
const toggle = async (m, enabled) => {
|
|
906
|
+
setMessages((prev) => prev?.map((x) => x.event.name === m.event.name && x.channel === m.channel ? { ...x, enabled } : x) ?? null);
|
|
682
907
|
try {
|
|
683
|
-
await store.upsert(
|
|
908
|
+
await store.upsert({ eventKey: m.event.name, channel: m.channel, enabled }, tenantId);
|
|
684
909
|
await reload();
|
|
685
910
|
} catch (err) {
|
|
686
|
-
console.warn("[automations]
|
|
911
|
+
console.warn("[automations] toggle failed", err);
|
|
687
912
|
toast.error("N\xE3o foi poss\xEDvel salvar");
|
|
688
|
-
} finally {
|
|
689
|
-
setSaving(false);
|
|
690
|
-
}
|
|
691
|
-
};
|
|
692
|
-
const save = () => void patch({
|
|
693
|
-
eventKey: event.name,
|
|
694
|
-
channel: message.channel,
|
|
695
|
-
// Igual ao padrão volta a ser "sem personalização", não uma cópia.
|
|
696
|
-
subject: current.subject.trim() === (fallback?.subject ?? "") ? null : current.subject.trim() || null,
|
|
697
|
-
body: current.body.trim() === fallback?.body ? null : current.body.trim() || null
|
|
698
|
-
});
|
|
699
|
-
const reset = async () => {
|
|
700
|
-
setDraft({ subject: fallback?.subject ?? "", body: fallback?.body ?? "" });
|
|
701
|
-
setSaving(true);
|
|
702
|
-
try {
|
|
703
|
-
await store.reset(event.name, message.channel, tenantId);
|
|
704
913
|
await reload();
|
|
705
|
-
toast.success("Texto restaurado");
|
|
706
|
-
} catch (err) {
|
|
707
|
-
console.warn("[automations] reset failed", err);
|
|
708
|
-
toast.error("N\xE3o foi poss\xEDvel restaurar");
|
|
709
|
-
} finally {
|
|
710
|
-
setSaving(false);
|
|
711
914
|
}
|
|
712
915
|
};
|
|
713
|
-
const
|
|
714
|
-
const
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
/* @__PURE__ */
|
|
781
|
-
|
|
782
|
-
|
|
916
|
+
const visible = (messages ?? []).filter((m) => kind === "all" || m.kind === kind);
|
|
917
|
+
const ownedVisible = kind === "all" ? owned : [];
|
|
918
|
+
const activeCount = (messages ?? []).filter((m) => m.enabled).length + owned.length;
|
|
919
|
+
const totalCount = (messages?.length ?? 0) + owned.length;
|
|
920
|
+
const groups = [];
|
|
921
|
+
for (const m of visible) {
|
|
922
|
+
let g = groups.find((x) => x.module === m.event.pluginId);
|
|
923
|
+
if (!g) {
|
|
924
|
+
g = { module: m.event.pluginId, label: m.event.pluginName, items: [] };
|
|
925
|
+
groups.push(g);
|
|
926
|
+
}
|
|
927
|
+
g.items.push(m);
|
|
928
|
+
}
|
|
929
|
+
const counter = messages !== null && /* @__PURE__ */ jsxs("span", { className: "text-sm text-muted-foreground", children: [
|
|
930
|
+
activeCount,
|
|
931
|
+
" de ",
|
|
932
|
+
totalCount,
|
|
933
|
+
" automa\xE7\xF5es ativas"
|
|
934
|
+
] });
|
|
935
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
936
|
+
variant !== "panel" ? /* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsx(
|
|
937
|
+
AskAIButton,
|
|
938
|
+
{
|
|
939
|
+
hint: "O que revisar nas automa\xE7\xF5es\u2026",
|
|
940
|
+
intro: {
|
|
941
|
+
role: "setup",
|
|
942
|
+
persona: "Redator das automa\xE7\xF5es",
|
|
943
|
+
pitch: "Diga o que revisar nas automa\xE7\xF5es.",
|
|
944
|
+
description: "Vejo todas as automa\xE7\xF5es deste app, quais est\xE3o ligadas e o texto de cada uma. Posso ligar, desligar e reescrever \u2014 em uma ou em v\xE1rias de uma vez. Voc\xEA confirma cada mudan\xE7a.",
|
|
945
|
+
suggestions: [
|
|
946
|
+
{ label: "Revise as mensagens de pedido no tom da minha marca" },
|
|
947
|
+
{ label: "Ligue as automa\xE7\xF5es de entrega" },
|
|
948
|
+
{ label: "Deixe as mensagens de WhatsApp mais curtas" }
|
|
949
|
+
]
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
) }) : null,
|
|
953
|
+
variant === "panel" ? /* @__PURE__ */ jsx(
|
|
954
|
+
SettingsTabs,
|
|
955
|
+
{
|
|
956
|
+
tabs: KIND_TABS.map((t) => ({ id: t.value, label: t.label, IconComp: t.icon })),
|
|
957
|
+
value: kind,
|
|
958
|
+
onChange: (id) => setKind(id),
|
|
959
|
+
"aria-label": "Automa\xE7\xF5es",
|
|
960
|
+
actions: counter
|
|
961
|
+
}
|
|
962
|
+
) : (
|
|
963
|
+
/* Mesma faixa das listas e da sub-nav de módulo. */
|
|
964
|
+
/* @__PURE__ */ jsx(
|
|
965
|
+
PageTabs,
|
|
966
|
+
{
|
|
967
|
+
className: "mb-0",
|
|
968
|
+
tabs: KIND_TABS.map((t) => ({ value: t.value, label: t.label, icon: t.icon })),
|
|
969
|
+
value: kind,
|
|
970
|
+
onChange: setKind,
|
|
971
|
+
"aria-label": "Automa\xE7\xF5es",
|
|
972
|
+
actions: counter
|
|
973
|
+
}
|
|
974
|
+
)
|
|
975
|
+
),
|
|
976
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: KIND_HINT[kind] }),
|
|
977
|
+
messages === null ? /* @__PURE__ */ jsx("div", { className: "space-y-3", children: Array.from({ length: 4 }).map((_, i) => /* @__PURE__ */ jsxs(Card, { className: "p-4", children: [
|
|
978
|
+
/* @__PURE__ */ jsx("div", { className: "h-4 w-40 animate-pulse rounded bg-muted" }),
|
|
979
|
+
/* @__PURE__ */ jsx("div", { className: "mt-2 h-3 w-64 animate-pulse rounded bg-muted" })
|
|
980
|
+
] }, i)) }) : groups.length === 0 && ownedVisible.length === 0 ? /* @__PURE__ */ jsxs(Card, { className: "flex flex-col items-center gap-1 p-12 text-center", children: [
|
|
981
|
+
/* @__PURE__ */ jsx(Zap, { className: "mb-2 h-9 w-9 text-muted-foreground/40", strokeWidth: 1.5 }),
|
|
982
|
+
/* @__PURE__ */ jsx("p", { className: "text-base font-semibold", children: "Nenhuma automa\xE7\xE3o nesta aba" }),
|
|
983
|
+
/* @__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." })
|
|
984
|
+
] }) : groups.map((group) => /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
985
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: group.label }),
|
|
986
|
+
group.items.map((m) => {
|
|
987
|
+
const Icon = iconFor2(m.event.icon ?? m.event.pluginIcon);
|
|
988
|
+
const ChannelIcon = CHANNEL_ICON[m.channel];
|
|
989
|
+
const href = `${basePath}/${encodeURIComponent(m.event.name)}/${m.channel}`;
|
|
990
|
+
return /* @__PURE__ */ jsx(
|
|
991
|
+
Card,
|
|
783
992
|
{
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
993
|
+
role: "button",
|
|
994
|
+
tabIndex: 0,
|
|
995
|
+
onClick: () => adminNavigateTo(href),
|
|
996
|
+
onKeyDown: (e) => {
|
|
997
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
998
|
+
e.preventDefault();
|
|
999
|
+
adminNavigateTo(href);
|
|
1000
|
+
}
|
|
1001
|
+
},
|
|
1002
|
+
className: "cursor-pointer overflow-hidden transition-colors hover:border-primary/40 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
1003
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-4 p-4", children: [
|
|
1004
|
+
/* @__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" }) }),
|
|
1005
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
1006
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
1007
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium", children: m.override?.name ?? m.event.title ?? m.event.name }),
|
|
1008
|
+
/* @__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: [
|
|
1009
|
+
/* @__PURE__ */ jsx(ChannelIcon, { className: "h-3 w-3" }),
|
|
1010
|
+
CHANNEL_LABEL2[m.channel]
|
|
1011
|
+
] }),
|
|
1012
|
+
m.customized && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-primary/10 px-2 py-0.5 text-[11px] text-primary", children: "personalizado" })
|
|
1013
|
+
] }),
|
|
1014
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: m.event.description }),
|
|
1015
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground", children: [
|
|
1016
|
+
/* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
|
|
1017
|
+
/* @__PURE__ */ jsx(Zap, { className: "h-3 w-3" }),
|
|
1018
|
+
m.event.trigger
|
|
1019
|
+
] }),
|
|
1020
|
+
m.subject && /* @__PURE__ */ jsxs("span", { className: "truncate", children: [
|
|
1021
|
+
"Assunto: ",
|
|
1022
|
+
m.subject
|
|
1023
|
+
] })
|
|
1024
|
+
] })
|
|
790
1025
|
] }),
|
|
791
|
-
/* @__PURE__ */
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
1026
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-3", children: [
|
|
1027
|
+
/* @__PURE__ */ jsx(
|
|
1028
|
+
Switch,
|
|
1029
|
+
{
|
|
1030
|
+
checked: m.enabled,
|
|
1031
|
+
onCheckedChange: (next) => void toggle(m, next),
|
|
1032
|
+
label: m.enabled ? "Ativo" : "Inativo",
|
|
1033
|
+
"aria-label": `${m.event.title} \u2014 ${CHANNEL_LABEL2[m.channel]}`
|
|
1034
|
+
}
|
|
1035
|
+
),
|
|
1036
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4 text-muted-foreground" })
|
|
1037
|
+
] })
|
|
1038
|
+
] })
|
|
1039
|
+
},
|
|
1040
|
+
`${m.event.name}:${m.channel}`
|
|
1041
|
+
);
|
|
1042
|
+
})
|
|
1043
|
+
] }, group.module)),
|
|
1044
|
+
messages !== null && /* @__PURE__ */ jsx(ConnectorAutomations, { rows: ownedVisible }),
|
|
1045
|
+
selected && /* @__PURE__ */ jsx(
|
|
1046
|
+
RightRailPage,
|
|
1047
|
+
{
|
|
1048
|
+
title: (messages ?? []).find(
|
|
1049
|
+
(m) => m.event.name === decodeURIComponent(selected.eventKey) && m.channel === selected.channel
|
|
1050
|
+
)?.event.title ?? "Automa\xE7\xE3o",
|
|
1051
|
+
open: true,
|
|
1052
|
+
onClose: () => adminNavigateTo(basePath),
|
|
1053
|
+
width: "max-w-2xl",
|
|
1054
|
+
children: /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto p-4", children: /* @__PURE__ */ jsx(
|
|
1055
|
+
AutomationDetailPage,
|
|
813
1056
|
{
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
1057
|
+
eventKey: selected.eventKey,
|
|
1058
|
+
channel: selected.channel,
|
|
1059
|
+
store,
|
|
1060
|
+
backTo: basePath,
|
|
1061
|
+
embedded: true
|
|
818
1062
|
}
|
|
819
|
-
)
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
/* @__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" }),
|
|
823
|
-
/* @__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)) })
|
|
824
|
-
] })
|
|
825
|
-
] })
|
|
1063
|
+
) })
|
|
1064
|
+
}
|
|
1065
|
+
)
|
|
826
1066
|
] });
|
|
827
1067
|
}
|
|
828
1068
|
var TABLE = "plg_automations_templates";
|
|
@@ -887,20 +1127,24 @@ function createAutomationsPlugin(options) {
|
|
|
887
1127
|
const store = options?.store ?? (connection ? createFayzTemplateStore(connection) : createSupabaseTemplateStore());
|
|
888
1128
|
startEventLog();
|
|
889
1129
|
const inSettings = options?.placement === "settings";
|
|
890
|
-
const ListPage = () =>
|
|
1130
|
+
const ListPage = () => React2.createElement(AutomationsList, { store });
|
|
891
1131
|
ListPage.displayName = "AutomationsList";
|
|
892
|
-
const
|
|
893
|
-
|
|
1132
|
+
const SettingsListPage = () => React2.createElement(AutomationsList, { store, variant: "panel", basePath: "/settings/automations" });
|
|
1133
|
+
SettingsListPage.displayName = "AutomationsSettings";
|
|
1134
|
+
const RailDetail = ({ params, onClose }) => React2.createElement(AutomationDetailPage, {
|
|
1135
|
+
eventKey: params.eventKey,
|
|
1136
|
+
channel: params.channel,
|
|
894
1137
|
store,
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
backTo: inSettings ? "/settings/automations" : "/automations"
|
|
1138
|
+
backTo: inSettings ? "/settings/automations" : "/automations",
|
|
1139
|
+
embedded: true,
|
|
1140
|
+
onDone: onClose
|
|
899
1141
|
});
|
|
900
|
-
|
|
1142
|
+
RailDetail.displayName = "AutomationRailDetail";
|
|
1143
|
+
registerRailPage("automations.detail", RailDetail);
|
|
901
1144
|
const READ = { feature: "automations", action: "read" };
|
|
902
1145
|
return {
|
|
903
1146
|
id: "automations",
|
|
1147
|
+
defaultAgentRole: "setup",
|
|
904
1148
|
name: options?.navLabel ?? "Automa\xE7\xF5es",
|
|
905
1149
|
icon: "Zap",
|
|
906
1150
|
version: "2.0.0",
|
|
@@ -922,13 +1166,16 @@ function createAutomationsPlugin(options) {
|
|
|
922
1166
|
],
|
|
923
1167
|
// The SAME list, served inside settings — not a second screen.
|
|
924
1168
|
settings: inSettings ? [
|
|
925
|
-
{ id: "automations", label: options?.navLabel ?? "Automa\xE7\xF5es", icon: "Zap", component:
|
|
1169
|
+
{ id: "automations", label: options?.navLabel ?? "Automa\xE7\xF5es", icon: "Zap", component: SettingsListPage, permission: READ }
|
|
926
1170
|
] : [],
|
|
927
1171
|
routes: [
|
|
928
1172
|
{ path: "/automations", component: ListPage, permission: READ },
|
|
929
|
-
//
|
|
930
|
-
//
|
|
931
|
-
|
|
1173
|
+
// O detalhe de uma automação é a MESMA tela com a coluna da direita
|
|
1174
|
+
// aberta — a lista continua atrás dela. Por isso o curinga aponta para o
|
|
1175
|
+
// mesmo componente: a shell colapsa '/x' e '/x/*' na mesma chave, então a
|
|
1176
|
+
// página não remonta ao abrir o painel (ver AdminShell.routeKey). Um
|
|
1177
|
+
// segundo componente aqui traria de volta a troca de tela.
|
|
1178
|
+
{ path: "/automations/*", component: ListPage, permission: READ }
|
|
932
1179
|
],
|
|
933
1180
|
widgets: []
|
|
934
1181
|
};
|