@greatapps/greatagents-ui 0.1.0 → 0.2.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/dist/index.d.ts +178 -1
- package/dist/index.js +3378 -0
- package/dist/index.js.map +1 -1
- package/package.json +13 -3
- package/src/components/agents/agent-edit-form.tsx +218 -0
- package/src/components/agents/agent-form-dialog.tsx +177 -0
- package/src/components/agents/agent-objectives-list.tsx +406 -0
- package/src/components/agents/agent-prompt-editor.tsx +406 -0
- package/src/components/agents/agent-tabs.tsx +64 -0
- package/src/components/agents/agent-tools-list.tsx +377 -0
- package/src/components/agents/agents-table.tsx +205 -0
- package/src/components/conversations/agent-conversations-panel.tsx +44 -0
- package/src/components/conversations/agent-conversations-table.tsx +160 -0
- package/src/components/conversations/conversation-view.tsx +124 -0
- package/src/components/tools/tool-credentials-form.tsx +572 -0
- package/src/components/tools/tool-form-dialog.tsx +342 -0
- package/src/components/tools/tools-table.tsx +225 -0
- package/src/components/ui/sortable.tsx +577 -0
- package/src/index.ts +19 -0
- package/src/lib/compose-refs.ts +44 -0
- package/src/pages/agent-detail-page.tsx +111 -0
- package/src/pages/agents-page.tsx +45 -0
- package/src/pages/credentials-page.tsx +50 -0
- package/src/pages/index.ts +4 -0
- package/src/pages/tools-page.tsx +52 -0
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
import { useMemo, useState } from "react";
|
|
2
|
+
import type { ColumnDef } from "@tanstack/react-table";
|
|
3
|
+
import type { Tool, ToolCredential } from "../../types";
|
|
4
|
+
import type { GagentsHookConfig } from "../../hooks/types";
|
|
5
|
+
import {
|
|
6
|
+
useCreateToolCredential,
|
|
7
|
+
useUpdateToolCredential,
|
|
8
|
+
useDeleteToolCredential,
|
|
9
|
+
} from "../../hooks";
|
|
10
|
+
import { useTools } from "../../hooks";
|
|
11
|
+
import { DataTable } from "@greatapps/greatauth-ui";
|
|
12
|
+
import {
|
|
13
|
+
Input,
|
|
14
|
+
Button,
|
|
15
|
+
Badge,
|
|
16
|
+
Tooltip,
|
|
17
|
+
TooltipTrigger,
|
|
18
|
+
TooltipContent,
|
|
19
|
+
Dialog,
|
|
20
|
+
DialogContent,
|
|
21
|
+
DialogHeader,
|
|
22
|
+
DialogTitle,
|
|
23
|
+
DialogFooter,
|
|
24
|
+
AlertDialog,
|
|
25
|
+
AlertDialogAction,
|
|
26
|
+
AlertDialogCancel,
|
|
27
|
+
AlertDialogContent,
|
|
28
|
+
AlertDialogDescription,
|
|
29
|
+
AlertDialogFooter,
|
|
30
|
+
AlertDialogHeader,
|
|
31
|
+
AlertDialogTitle,
|
|
32
|
+
Select,
|
|
33
|
+
SelectContent,
|
|
34
|
+
SelectItem,
|
|
35
|
+
SelectTrigger,
|
|
36
|
+
SelectValue,
|
|
37
|
+
} from "@greatapps/greatauth-ui/ui";
|
|
38
|
+
import { Trash2, Pencil, Link, Search } from "lucide-react";
|
|
39
|
+
import { format } from "date-fns";
|
|
40
|
+
import { ptBR } from "date-fns/locale";
|
|
41
|
+
import { toast } from "sonner";
|
|
42
|
+
|
|
43
|
+
interface ToolCredentialsFormProps {
|
|
44
|
+
credentials: ToolCredential[];
|
|
45
|
+
isLoading: boolean;
|
|
46
|
+
config: GagentsHookConfig;
|
|
47
|
+
gagentsApiUrl: string;
|
|
48
|
+
createOpen?: boolean;
|
|
49
|
+
onCreateOpenChange?: (open: boolean) => void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function formatDate(dateStr: string | null): string {
|
|
53
|
+
if (!dateStr) return "Sem expiração";
|
|
54
|
+
return format(new Date(dateStr), "dd/MM/yyyy", { locale: ptBR });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function useColumns(
|
|
58
|
+
tools: Tool[],
|
|
59
|
+
onEdit: (cred: ToolCredential) => void,
|
|
60
|
+
onConnect: (cred: ToolCredential) => void,
|
|
61
|
+
onRemove: (cred: ToolCredential) => void,
|
|
62
|
+
): ColumnDef<ToolCredential>[] {
|
|
63
|
+
function getToolName(idTool: number | null): string {
|
|
64
|
+
if (!idTool) return "\u2014";
|
|
65
|
+
const tool = tools.find((t) => t.id === idTool);
|
|
66
|
+
return tool?.name || `Ferramenta #${idTool}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function getToolType(idTool: number | null): string | null {
|
|
70
|
+
if (!idTool) return null;
|
|
71
|
+
const tool = tools.find((t) => t.id === idTool);
|
|
72
|
+
return tool?.type || null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return [
|
|
76
|
+
{
|
|
77
|
+
accessorKey: "label",
|
|
78
|
+
header: "Label",
|
|
79
|
+
cell: ({ row }) => (
|
|
80
|
+
<span className="font-medium">{row.original.label || "\u2014"}</span>
|
|
81
|
+
),
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
accessorKey: "id_tool",
|
|
85
|
+
header: "Ferramenta",
|
|
86
|
+
cell: ({ row }) => (
|
|
87
|
+
<span className="text-sm">{getToolName(row.original.id_tool)}</span>
|
|
88
|
+
),
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
accessorKey: "status",
|
|
92
|
+
header: "Status",
|
|
93
|
+
cell: ({ row }) => (
|
|
94
|
+
<Badge
|
|
95
|
+
variant={row.original.status === "active" ? "default" : "destructive"}
|
|
96
|
+
>
|
|
97
|
+
{row.original.status === "active" ? "Ativo" : "Expirado"}
|
|
98
|
+
</Badge>
|
|
99
|
+
),
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
accessorKey: "expires_at",
|
|
103
|
+
header: "Expira em",
|
|
104
|
+
cell: ({ row }) => (
|
|
105
|
+
<span className="text-muted-foreground text-sm">
|
|
106
|
+
{formatDate(row.original.expires_at)}
|
|
107
|
+
</span>
|
|
108
|
+
),
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
accessorKey: "datetime_add",
|
|
112
|
+
header: "Criado em",
|
|
113
|
+
cell: ({ row }) => (
|
|
114
|
+
<span className="text-muted-foreground text-sm">
|
|
115
|
+
{formatDate(row.original.datetime_add)}
|
|
116
|
+
</span>
|
|
117
|
+
),
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: "actions",
|
|
121
|
+
header: "Ações",
|
|
122
|
+
size: 100,
|
|
123
|
+
enableSorting: false,
|
|
124
|
+
cell: ({ row }) => (
|
|
125
|
+
<div className="flex items-center gap-1">
|
|
126
|
+
{getToolType(row.original.id_tool) === "oauth2" && (
|
|
127
|
+
<Tooltip>
|
|
128
|
+
<TooltipTrigger asChild>
|
|
129
|
+
<Button
|
|
130
|
+
variant="ghost"
|
|
131
|
+
size="icon"
|
|
132
|
+
className="h-8 w-8"
|
|
133
|
+
disabled
|
|
134
|
+
>
|
|
135
|
+
<Link className="h-4 w-4" />
|
|
136
|
+
</Button>
|
|
137
|
+
</TooltipTrigger>
|
|
138
|
+
<TooltipContent>Em breve</TooltipContent>
|
|
139
|
+
</Tooltip>
|
|
140
|
+
)}
|
|
141
|
+
<Tooltip>
|
|
142
|
+
<TooltipTrigger asChild>
|
|
143
|
+
<Button
|
|
144
|
+
variant="ghost"
|
|
145
|
+
size="icon"
|
|
146
|
+
className="h-8 w-8"
|
|
147
|
+
onClick={() => onEdit(row.original)}
|
|
148
|
+
>
|
|
149
|
+
<Pencil className="h-4 w-4" />
|
|
150
|
+
</Button>
|
|
151
|
+
</TooltipTrigger>
|
|
152
|
+
<TooltipContent>Editar</TooltipContent>
|
|
153
|
+
</Tooltip>
|
|
154
|
+
<Tooltip>
|
|
155
|
+
<TooltipTrigger asChild>
|
|
156
|
+
<Button
|
|
157
|
+
variant="ghost"
|
|
158
|
+
size="icon"
|
|
159
|
+
className="h-8 w-8 text-destructive hover:text-destructive"
|
|
160
|
+
onClick={() => onRemove(row.original)}
|
|
161
|
+
>
|
|
162
|
+
<Trash2 className="h-4 w-4" />
|
|
163
|
+
</Button>
|
|
164
|
+
</TooltipTrigger>
|
|
165
|
+
<TooltipContent>Remover</TooltipContent>
|
|
166
|
+
</Tooltip>
|
|
167
|
+
</div>
|
|
168
|
+
),
|
|
169
|
+
},
|
|
170
|
+
];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function ToolCredentialsForm({
|
|
174
|
+
credentials,
|
|
175
|
+
isLoading,
|
|
176
|
+
config,
|
|
177
|
+
gagentsApiUrl,
|
|
178
|
+
createOpen: externalCreateOpen,
|
|
179
|
+
onCreateOpenChange,
|
|
180
|
+
}: ToolCredentialsFormProps) {
|
|
181
|
+
const createMutation = useCreateToolCredential(config);
|
|
182
|
+
const updateMutation = useUpdateToolCredential(config);
|
|
183
|
+
const deleteMutation = useDeleteToolCredential(config);
|
|
184
|
+
const { data: toolsData } = useTools(config);
|
|
185
|
+
const tools: Tool[] = toolsData?.data || [];
|
|
186
|
+
|
|
187
|
+
const [search, setSearch] = useState("");
|
|
188
|
+
const [internalCreateOpen, setInternalCreateOpen] = useState(false);
|
|
189
|
+
const showCreateDialog = externalCreateOpen ?? internalCreateOpen;
|
|
190
|
+
const setShowCreateDialog = onCreateOpenChange ?? setInternalCreateOpen;
|
|
191
|
+
const [createForm, setCreateForm] = useState({
|
|
192
|
+
id_tool: "",
|
|
193
|
+
label: "",
|
|
194
|
+
credentials_encrypted: "",
|
|
195
|
+
expires_at: "",
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const [editTarget, setEditTarget] = useState<ToolCredential | null>(null);
|
|
199
|
+
const [editForm, setEditForm] = useState({
|
|
200
|
+
id_tool: "",
|
|
201
|
+
label: "",
|
|
202
|
+
credentials_encrypted: "",
|
|
203
|
+
expires_at: "",
|
|
204
|
+
status: "" as "active" | "expired" | "",
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const [removeTarget, setRemoveTarget] = useState<ToolCredential | null>(null);
|
|
208
|
+
|
|
209
|
+
const filteredCredentials = useMemo(() => {
|
|
210
|
+
if (!search) return credentials;
|
|
211
|
+
const term = search.toLowerCase();
|
|
212
|
+
return credentials.filter((cred) => {
|
|
213
|
+
const toolName = tools.find((t) => t.id === cred.id_tool)?.name || "";
|
|
214
|
+
return (
|
|
215
|
+
(cred.label || "").toLowerCase().includes(term) ||
|
|
216
|
+
toolName.toLowerCase().includes(term)
|
|
217
|
+
);
|
|
218
|
+
});
|
|
219
|
+
}, [credentials, search, tools]);
|
|
220
|
+
|
|
221
|
+
const columns = useColumns(
|
|
222
|
+
tools,
|
|
223
|
+
(cred) => startEdit(cred),
|
|
224
|
+
(cred) => handleConnect(cred),
|
|
225
|
+
(cred) => setRemoveTarget(cred),
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
async function handleCreate() {
|
|
229
|
+
const idTool = parseInt(createForm.id_tool, 10);
|
|
230
|
+
if (!idTool || !createForm.label.trim() || !createForm.credentials_encrypted.trim()) return;
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
const result = await createMutation.mutateAsync({
|
|
234
|
+
id_tool: idTool,
|
|
235
|
+
label: createForm.label.trim(),
|
|
236
|
+
credentials_encrypted: createForm.credentials_encrypted.trim(),
|
|
237
|
+
...(createForm.expires_at ? { expires_at: createForm.expires_at } : {}),
|
|
238
|
+
});
|
|
239
|
+
if (result.status === 1) {
|
|
240
|
+
toast.success("Credencial criada");
|
|
241
|
+
setShowCreateDialog(false);
|
|
242
|
+
setCreateForm({ id_tool: "", label: "", credentials_encrypted: "", expires_at: "" });
|
|
243
|
+
} else {
|
|
244
|
+
toast.error(result.message || "Erro ao criar credencial");
|
|
245
|
+
}
|
|
246
|
+
} catch {
|
|
247
|
+
toast.error("Erro ao criar credencial");
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function startEdit(cred: ToolCredential) {
|
|
252
|
+
setEditTarget(cred);
|
|
253
|
+
setEditForm({
|
|
254
|
+
id_tool: cred.id_tool ? String(cred.id_tool) : "",
|
|
255
|
+
label: cred.label || "",
|
|
256
|
+
credentials_encrypted: "",
|
|
257
|
+
expires_at: cred.expires_at || "",
|
|
258
|
+
status: cred.status,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function handleSaveEdit() {
|
|
263
|
+
if (!editTarget) return;
|
|
264
|
+
const body: Record<string, unknown> = {};
|
|
265
|
+
const newIdTool = editForm.id_tool ? parseInt(editForm.id_tool, 10) : null;
|
|
266
|
+
if (newIdTool && newIdTool !== editTarget.id_tool) {
|
|
267
|
+
body.id_tool = newIdTool;
|
|
268
|
+
}
|
|
269
|
+
if (editForm.label.trim() && editForm.label.trim() !== (editTarget.label || "")) {
|
|
270
|
+
body.label = editForm.label.trim();
|
|
271
|
+
}
|
|
272
|
+
if (editForm.credentials_encrypted.trim()) {
|
|
273
|
+
body.credentials_encrypted = editForm.credentials_encrypted.trim();
|
|
274
|
+
}
|
|
275
|
+
if (editForm.expires_at !== (editTarget.expires_at || "")) {
|
|
276
|
+
body.expires_at = editForm.expires_at || null;
|
|
277
|
+
}
|
|
278
|
+
if (editForm.status && editForm.status !== editTarget.status) {
|
|
279
|
+
body.status = editForm.status;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (Object.keys(body).length === 0) {
|
|
283
|
+
setEditTarget(null);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
try {
|
|
288
|
+
const result = await updateMutation.mutateAsync({
|
|
289
|
+
id: editTarget.id,
|
|
290
|
+
body: body as Parameters<typeof updateMutation.mutateAsync>[0]["body"],
|
|
291
|
+
});
|
|
292
|
+
if (result.status === 1) {
|
|
293
|
+
toast.success("Credencial atualizada");
|
|
294
|
+
setEditTarget(null);
|
|
295
|
+
} else {
|
|
296
|
+
toast.error(result.message || "Erro ao atualizar credencial");
|
|
297
|
+
}
|
|
298
|
+
} catch {
|
|
299
|
+
toast.error("Erro ao atualizar credencial");
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function handleRemove() {
|
|
304
|
+
if (!removeTarget) return;
|
|
305
|
+
try {
|
|
306
|
+
const result = await deleteMutation.mutateAsync(removeTarget.id);
|
|
307
|
+
if (result.status === 1) {
|
|
308
|
+
toast.success("Credencial removida");
|
|
309
|
+
} else {
|
|
310
|
+
toast.error(result.message || "Erro ao remover credencial");
|
|
311
|
+
}
|
|
312
|
+
} catch {
|
|
313
|
+
toast.error("Erro ao remover credencial");
|
|
314
|
+
} finally {
|
|
315
|
+
setRemoveTarget(null);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function handleConnect(cred: ToolCredential) {
|
|
320
|
+
if (!config.accountId || !config.token) return;
|
|
321
|
+
const language = config.language ?? "pt-br";
|
|
322
|
+
const idWl = config.idWl ?? 1;
|
|
323
|
+
const url = `${gagentsApiUrl}/v1/${language}/${idWl}/accounts/${config.accountId}/oauth/connect?id_tool=${cred.id_tool}`;
|
|
324
|
+
window.open(url, "_blank");
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return (
|
|
328
|
+
<div className="space-y-4">
|
|
329
|
+
<div className="flex items-center gap-3">
|
|
330
|
+
<div className="relative flex-1 max-w-md">
|
|
331
|
+
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
|
332
|
+
<Input
|
|
333
|
+
placeholder="Buscar credenciais..."
|
|
334
|
+
value={search}
|
|
335
|
+
onChange={(e) => setSearch(e.target.value)}
|
|
336
|
+
className="pl-9"
|
|
337
|
+
/>
|
|
338
|
+
</div>
|
|
339
|
+
</div>
|
|
340
|
+
|
|
341
|
+
<DataTable
|
|
342
|
+
columns={columns}
|
|
343
|
+
data={filteredCredentials}
|
|
344
|
+
isLoading={isLoading}
|
|
345
|
+
emptyMessage="Nenhuma credencial encontrada"
|
|
346
|
+
/>
|
|
347
|
+
|
|
348
|
+
{/* Create Dialog */}
|
|
349
|
+
<Dialog open={showCreateDialog} onOpenChange={setShowCreateDialog}>
|
|
350
|
+
<DialogContent>
|
|
351
|
+
<DialogHeader>
|
|
352
|
+
<DialogTitle>Nova Credencial</DialogTitle>
|
|
353
|
+
</DialogHeader>
|
|
354
|
+
<div className="space-y-4">
|
|
355
|
+
<div>
|
|
356
|
+
<label className="mb-1 block text-sm font-medium">
|
|
357
|
+
Ferramenta *
|
|
358
|
+
</label>
|
|
359
|
+
<Select
|
|
360
|
+
value={createForm.id_tool}
|
|
361
|
+
onValueChange={(val) =>
|
|
362
|
+
setCreateForm((f) => ({ ...f, id_tool: val }))
|
|
363
|
+
}
|
|
364
|
+
>
|
|
365
|
+
<SelectTrigger>
|
|
366
|
+
<SelectValue placeholder="Selecione a ferramenta" />
|
|
367
|
+
</SelectTrigger>
|
|
368
|
+
<SelectContent>
|
|
369
|
+
{tools.map((tool) => (
|
|
370
|
+
<SelectItem key={tool.id} value={String(tool.id)}>
|
|
371
|
+
{tool.name}
|
|
372
|
+
</SelectItem>
|
|
373
|
+
))}
|
|
374
|
+
</SelectContent>
|
|
375
|
+
</Select>
|
|
376
|
+
</div>
|
|
377
|
+
<div>
|
|
378
|
+
<label className="mb-1 block text-sm font-medium">
|
|
379
|
+
Label *
|
|
380
|
+
</label>
|
|
381
|
+
<Input
|
|
382
|
+
value={createForm.label}
|
|
383
|
+
onChange={(e) =>
|
|
384
|
+
setCreateForm((f) => ({ ...f, label: e.target.value }))
|
|
385
|
+
}
|
|
386
|
+
placeholder="Ex: Google Calendar - Clínica São Paulo"
|
|
387
|
+
/>
|
|
388
|
+
</div>
|
|
389
|
+
<div>
|
|
390
|
+
<label className="mb-1 block text-sm font-medium">
|
|
391
|
+
Credencial *
|
|
392
|
+
</label>
|
|
393
|
+
<Input
|
|
394
|
+
type="password"
|
|
395
|
+
value={createForm.credentials_encrypted}
|
|
396
|
+
onChange={(e) =>
|
|
397
|
+
setCreateForm((f) => ({
|
|
398
|
+
...f,
|
|
399
|
+
credentials_encrypted: e.target.value,
|
|
400
|
+
}))
|
|
401
|
+
}
|
|
402
|
+
placeholder="Credencial encriptada"
|
|
403
|
+
/>
|
|
404
|
+
</div>
|
|
405
|
+
<div>
|
|
406
|
+
<label className="mb-1 block text-sm font-medium">
|
|
407
|
+
Data de Expiração (opcional)
|
|
408
|
+
</label>
|
|
409
|
+
<Input
|
|
410
|
+
type="date"
|
|
411
|
+
value={createForm.expires_at}
|
|
412
|
+
onChange={(e) =>
|
|
413
|
+
setCreateForm((f) => ({ ...f, expires_at: e.target.value }))
|
|
414
|
+
}
|
|
415
|
+
/>
|
|
416
|
+
</div>
|
|
417
|
+
</div>
|
|
418
|
+
<DialogFooter>
|
|
419
|
+
<Button
|
|
420
|
+
variant="outline"
|
|
421
|
+
onClick={() => setShowCreateDialog(false)}
|
|
422
|
+
>
|
|
423
|
+
Cancelar
|
|
424
|
+
</Button>
|
|
425
|
+
<Button
|
|
426
|
+
onClick={handleCreate}
|
|
427
|
+
disabled={
|
|
428
|
+
!createForm.id_tool ||
|
|
429
|
+
!createForm.label.trim() ||
|
|
430
|
+
!createForm.credentials_encrypted.trim() ||
|
|
431
|
+
createMutation.isPending
|
|
432
|
+
}
|
|
433
|
+
>
|
|
434
|
+
Criar
|
|
435
|
+
</Button>
|
|
436
|
+
</DialogFooter>
|
|
437
|
+
</DialogContent>
|
|
438
|
+
</Dialog>
|
|
439
|
+
|
|
440
|
+
{/* Edit Dialog */}
|
|
441
|
+
<Dialog
|
|
442
|
+
open={!!editTarget}
|
|
443
|
+
onOpenChange={(open) => !open && setEditTarget(null)}
|
|
444
|
+
>
|
|
445
|
+
<DialogContent>
|
|
446
|
+
<DialogHeader>
|
|
447
|
+
<DialogTitle>Editar Credencial</DialogTitle>
|
|
448
|
+
</DialogHeader>
|
|
449
|
+
<div className="space-y-4">
|
|
450
|
+
<div>
|
|
451
|
+
<label className="mb-1 block text-sm font-medium">
|
|
452
|
+
Ferramenta *
|
|
453
|
+
</label>
|
|
454
|
+
<Select
|
|
455
|
+
value={editForm.id_tool}
|
|
456
|
+
onValueChange={(val) =>
|
|
457
|
+
setEditForm((f) => ({ ...f, id_tool: val }))
|
|
458
|
+
}
|
|
459
|
+
>
|
|
460
|
+
<SelectTrigger>
|
|
461
|
+
<SelectValue placeholder="Selecione a ferramenta" />
|
|
462
|
+
</SelectTrigger>
|
|
463
|
+
<SelectContent>
|
|
464
|
+
{tools.map((tool) => (
|
|
465
|
+
<SelectItem key={tool.id} value={String(tool.id)}>
|
|
466
|
+
{tool.name}
|
|
467
|
+
</SelectItem>
|
|
468
|
+
))}
|
|
469
|
+
</SelectContent>
|
|
470
|
+
</Select>
|
|
471
|
+
</div>
|
|
472
|
+
<div>
|
|
473
|
+
<label className="mb-1 block text-sm font-medium">
|
|
474
|
+
Label
|
|
475
|
+
</label>
|
|
476
|
+
<Input
|
|
477
|
+
value={editForm.label}
|
|
478
|
+
onChange={(e) =>
|
|
479
|
+
setEditForm((f) => ({ ...f, label: e.target.value }))
|
|
480
|
+
}
|
|
481
|
+
placeholder="Label da credencial"
|
|
482
|
+
/>
|
|
483
|
+
</div>
|
|
484
|
+
<div>
|
|
485
|
+
<label className="mb-1 block text-sm font-medium">
|
|
486
|
+
Nova Credencial (vazio = manter atual)
|
|
487
|
+
</label>
|
|
488
|
+
<Input
|
|
489
|
+
type="password"
|
|
490
|
+
value={editForm.credentials_encrypted}
|
|
491
|
+
onChange={(e) =>
|
|
492
|
+
setEditForm((f) => ({
|
|
493
|
+
...f,
|
|
494
|
+
credentials_encrypted: e.target.value,
|
|
495
|
+
}))
|
|
496
|
+
}
|
|
497
|
+
placeholder="Nova credencial"
|
|
498
|
+
/>
|
|
499
|
+
</div>
|
|
500
|
+
<div>
|
|
501
|
+
<label className="mb-1 block text-sm font-medium">
|
|
502
|
+
Data de Expiração
|
|
503
|
+
</label>
|
|
504
|
+
<Input
|
|
505
|
+
type="date"
|
|
506
|
+
value={editForm.expires_at}
|
|
507
|
+
onChange={(e) =>
|
|
508
|
+
setEditForm((f) => ({ ...f, expires_at: e.target.value }))
|
|
509
|
+
}
|
|
510
|
+
/>
|
|
511
|
+
</div>
|
|
512
|
+
<div>
|
|
513
|
+
<label className="mb-1 block text-sm font-medium">Status</label>
|
|
514
|
+
<Select
|
|
515
|
+
value={editForm.status || undefined}
|
|
516
|
+
onValueChange={(val) =>
|
|
517
|
+
setEditForm((f) => ({
|
|
518
|
+
...f,
|
|
519
|
+
status: val as "active" | "expired",
|
|
520
|
+
}))
|
|
521
|
+
}
|
|
522
|
+
>
|
|
523
|
+
<SelectTrigger>
|
|
524
|
+
<SelectValue />
|
|
525
|
+
</SelectTrigger>
|
|
526
|
+
<SelectContent>
|
|
527
|
+
<SelectItem value="active">Ativo</SelectItem>
|
|
528
|
+
<SelectItem value="expired">Expirado</SelectItem>
|
|
529
|
+
</SelectContent>
|
|
530
|
+
</Select>
|
|
531
|
+
</div>
|
|
532
|
+
</div>
|
|
533
|
+
<DialogFooter>
|
|
534
|
+
<Button variant="outline" onClick={() => setEditTarget(null)}>
|
|
535
|
+
Cancelar
|
|
536
|
+
</Button>
|
|
537
|
+
<Button
|
|
538
|
+
onClick={handleSaveEdit}
|
|
539
|
+
disabled={updateMutation.isPending}
|
|
540
|
+
>
|
|
541
|
+
Salvar
|
|
542
|
+
</Button>
|
|
543
|
+
</DialogFooter>
|
|
544
|
+
</DialogContent>
|
|
545
|
+
</Dialog>
|
|
546
|
+
|
|
547
|
+
{/* Delete confirmation */}
|
|
548
|
+
<AlertDialog
|
|
549
|
+
open={!!removeTarget}
|
|
550
|
+
onOpenChange={(open) => !open && setRemoveTarget(null)}
|
|
551
|
+
>
|
|
552
|
+
<AlertDialogContent>
|
|
553
|
+
<AlertDialogHeader>
|
|
554
|
+
<AlertDialogTitle>Remover credencial?</AlertDialogTitle>
|
|
555
|
+
<AlertDialogDescription>
|
|
556
|
+
A credencial será removida permanentemente.
|
|
557
|
+
</AlertDialogDescription>
|
|
558
|
+
</AlertDialogHeader>
|
|
559
|
+
<AlertDialogFooter>
|
|
560
|
+
<AlertDialogCancel>Cancelar</AlertDialogCancel>
|
|
561
|
+
<AlertDialogAction
|
|
562
|
+
onClick={handleRemove}
|
|
563
|
+
disabled={deleteMutation.isPending}
|
|
564
|
+
>
|
|
565
|
+
Remover
|
|
566
|
+
</AlertDialogAction>
|
|
567
|
+
</AlertDialogFooter>
|
|
568
|
+
</AlertDialogContent>
|
|
569
|
+
</AlertDialog>
|
|
570
|
+
</div>
|
|
571
|
+
);
|
|
572
|
+
}
|