From 76c48ff78f9d5333c48d6b82c7a89ddea895f748 Mon Sep 17 00:00:00 2001 From: Claude AI Date: Thu, 29 Jan 2026 11:13:10 +0000 Subject: [PATCH] feat(fase4): add FlowTemplates frontend page Co-Authored-By: Claude Opus 4.5 --- frontend/src/pages/FlowTemplates.tsx | 157 +++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 frontend/src/pages/FlowTemplates.tsx diff --git a/frontend/src/pages/FlowTemplates.tsx b/frontend/src/pages/FlowTemplates.tsx new file mode 100644 index 0000000..0680d1b --- /dev/null +++ b/frontend/src/pages/FlowTemplates.tsx @@ -0,0 +1,157 @@ +import { useState } from 'react'; +import { + Card, + Row, + Col, + Button, + Modal, + Input, + Typography, + Tag, + Empty, + message, +} from 'antd'; +import { CopyOutlined } from '@ant-design/icons'; +import { useQuery, useMutation } from '@tanstack/react-query'; +import { useNavigate } from 'react-router-dom'; +import { apiClient } from '../api/client'; + +const { Title, Text, Paragraph } = Typography; + +interface FlowTemplate { + id: string; + name: string; + description: string | null; + category: string; + nodes: unknown[]; +} + +const categoryColors: Record = { + general: 'blue', + sales: 'green', + support: 'orange', + marketing: 'purple', +}; + +export default function FlowTemplates() { + const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); + const [selectedTemplate, setSelectedTemplate] = useState(null); + const [flowName, setFlowName] = useState(''); + const navigate = useNavigate(); + + const { data: templates, isLoading } = useQuery({ + queryKey: ['flow-templates'], + queryFn: () => apiClient.get('/api/flow-templates'), + }); + + const useTemplateMutation = useMutation({ + mutationFn: ({ templateId, name }: { templateId: string; name: string }) => { + const encodedName = encodeURIComponent(name); + return apiClient.post<{ flow_id: string }>( + `/api/flow-templates/${templateId}/use?flow_name=${encodedName}`, + {} + ); + }, + onSuccess: (data) => { + message.success('Flujo creado desde plantilla'); + setIsCreateModalOpen(false); + setSelectedTemplate(null); + setFlowName(''); + navigate(`/flows/${data.flow_id}`); + }, + }); + + function handleUseTemplate(template: FlowTemplate): void { + setSelectedTemplate(template); + setFlowName(`${template.name} - Copia`); + setIsCreateModalOpen(true); + } + + function handleModalCancel(): void { + setIsCreateModalOpen(false); + setSelectedTemplate(null); + } + + function handleModalOk(): void { + if (selectedTemplate && flowName) { + useTemplateMutation.mutate({ templateId: selectedTemplate.id, name: flowName }); + } + } + + function renderContent() { + if (isLoading) { + return ; + } + + if (!templates || templates.length === 0) { + return ; + } + + return ( + + {templates.map((template) => ( + + } + onClick={() => handleUseTemplate(template)} + > + Usar + , + ]} + > + + + {template.category} + + + {template.description || 'Sin descripcion'} + + + {template.nodes.length} nodos + + + } + /> + + + ))} + + ); + } + + return ( +
+
+ Plantillas de Flujos +
+ + {renderContent()} + + +
+ Plantilla: + {selectedTemplate?.name} +
+ setFlowName(e.target.value)} + /> +
+
+ ); +}