feat(fase4): add FlowTemplates frontend page
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
157
frontend/src/pages/FlowTemplates.tsx
Normal file
157
frontend/src/pages/FlowTemplates.tsx
Normal file
@@ -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<string, string> = {
|
||||
general: 'blue',
|
||||
sales: 'green',
|
||||
support: 'orange',
|
||||
marketing: 'purple',
|
||||
};
|
||||
|
||||
export default function FlowTemplates() {
|
||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
||||
const [selectedTemplate, setSelectedTemplate] = useState<FlowTemplate | null>(null);
|
||||
const [flowName, setFlowName] = useState('');
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { data: templates, isLoading } = useQuery({
|
||||
queryKey: ['flow-templates'],
|
||||
queryFn: () => apiClient.get<FlowTemplate[]>('/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 <Card loading />;
|
||||
}
|
||||
|
||||
if (!templates || templates.length === 0) {
|
||||
return <Empty description="No hay plantillas disponibles" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
{templates.map((template) => (
|
||||
<Col key={template.id} xs={24} sm={12} md={8} lg={6}>
|
||||
<Card
|
||||
hoverable
|
||||
actions={[
|
||||
<Button
|
||||
type="link"
|
||||
icon={<CopyOutlined />}
|
||||
onClick={() => handleUseTemplate(template)}
|
||||
>
|
||||
Usar
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<Card.Meta
|
||||
title={template.name}
|
||||
description={
|
||||
<>
|
||||
<Tag color={categoryColors[template.category] || 'default'}>
|
||||
{template.category}
|
||||
</Tag>
|
||||
<Paragraph ellipsis={{ rows: 2 }} style={{ marginTop: 8, marginBottom: 0 }}>
|
||||
{template.description || 'Sin descripcion'}
|
||||
</Paragraph>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
{template.nodes.length} nodos
|
||||
</Text>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 24 }}>
|
||||
<Title level={4} style={{ margin: 0 }}>Plantillas de Flujos</Title>
|
||||
</div>
|
||||
|
||||
{renderContent()}
|
||||
|
||||
<Modal
|
||||
title="Crear flujo desde plantilla"
|
||||
open={isCreateModalOpen}
|
||||
onCancel={handleModalCancel}
|
||||
onOk={handleModalOk}
|
||||
okText="Crear"
|
||||
confirmLoading={useTemplateMutation.isPending}
|
||||
>
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<Text strong>Plantilla: </Text>
|
||||
<Text>{selectedTemplate?.name}</Text>
|
||||
</div>
|
||||
<Input
|
||||
placeholder="Nombre del nuevo flujo"
|
||||
value={flowName}
|
||||
onChange={(e) => setFlowName(e.target.value)}
|
||||
/>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user