feat(frontend): add Odoo configuration page

Add OdooConfig page component with form for Odoo connection settings
(URL, database, username, API key) and test connection functionality.
Integrate into main navigation with ApiOutlined icon.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Claude AI
2026-01-29 22:22:12 +00:00
parent a40811b4a1
commit 63d4409c00
2 changed files with 190 additions and 0 deletions

View File

@@ -15,6 +15,7 @@ import {
BarChartOutlined, BarChartOutlined,
FileTextOutlined, FileTextOutlined,
GlobalOutlined, GlobalOutlined,
ApiOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { useAuthStore } from '../store/auth'; import { useAuthStore } from '../store/auth';
import Dashboard from '../pages/Dashboard'; import Dashboard from '../pages/Dashboard';
@@ -26,6 +27,7 @@ import FlowTemplates from '../pages/FlowTemplates';
import GlobalVariables from '../pages/GlobalVariables'; import GlobalVariables from '../pages/GlobalVariables';
import Queues from '../pages/Queues'; import Queues from '../pages/Queues';
import SupervisorDashboard from '../pages/SupervisorDashboard'; import SupervisorDashboard from '../pages/SupervisorDashboard';
import OdooConfig from '../pages/OdooConfig';
const { Header, Sider, Content } = Layout; const { Header, Sider, Content } = Layout;
const { Text } = Typography; const { Text } = Typography;
@@ -82,6 +84,11 @@ export default function MainLayout() {
icon: <BarChartOutlined />, icon: <BarChartOutlined />,
label: 'Supervisor', label: 'Supervisor',
}, },
{
key: '/odoo',
icon: <ApiOutlined />,
label: 'Odoo',
},
{ {
key: '/settings', key: '/settings',
icon: <SettingOutlined />, icon: <SettingOutlined />,
@@ -194,6 +201,7 @@ export default function MainLayout() {
<Route path="/variables" element={<GlobalVariables />} /> <Route path="/variables" element={<GlobalVariables />} />
<Route path="/queues" element={<Queues />} /> <Route path="/queues" element={<Queues />} />
<Route path="/supervisor" element={<SupervisorDashboard />} /> <Route path="/supervisor" element={<SupervisorDashboard />} />
<Route path="/odoo" element={<OdooConfig />} />
<Route path="/settings" element={<div>Configuración (próximamente)</div>} /> <Route path="/settings" element={<div>Configuración (próximamente)</div>} />
</Routes> </Routes>
</Content> </Content>

View File

@@ -0,0 +1,182 @@
import { useState, useEffect } from 'react';
import {
Card,
Form,
Input,
Button,
Space,
Typography,
Alert,
Spin,
Tag,
message,
} from 'antd';
import {
LinkOutlined,
CheckCircleOutlined,
CloseCircleOutlined,
ReloadOutlined,
} from '@ant-design/icons';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { apiClient } from '../api/client';
const { Title } = Typography;
interface OdooConfig {
url: string;
database: string;
username: string;
is_connected: boolean;
}
interface OdooConfigUpdate {
url: string;
database: string;
username: string;
api_key?: string;
}
export default function OdooConfig() {
const [form] = Form.useForm();
const queryClient = useQueryClient();
const [testStatus, setTestStatus] = useState<'idle' | 'testing' | 'success' | 'error'>('idle');
const { data: config, isLoading } = useQuery({
queryKey: ['odoo-config'],
queryFn: () => apiClient.get<OdooConfig>('/api/integrations/odoo/config'),
});
useEffect(() => {
if (config) {
form.setFieldsValue({
url: config.url,
database: config.database,
username: config.username,
});
}
}, [config, form]);
const saveMutation = useMutation({
mutationFn: (data: OdooConfigUpdate) =>
apiClient.put('/api/integrations/odoo/config', data),
onSuccess: () => {
message.success('Configuracion guardada');
queryClient.invalidateQueries({ queryKey: ['odoo-config'] });
},
onError: () => {
message.error('Error al guardar');
},
});
const testMutation = useMutation({
mutationFn: () => apiClient.post('/api/integrations/odoo/test', {}),
onSuccess: () => {
setTestStatus('success');
message.success('Conexion exitosa');
queryClient.invalidateQueries({ queryKey: ['odoo-config'] });
},
onError: () => {
setTestStatus('error');
message.error('Error de conexion');
},
});
const handleTest = () => {
setTestStatus('testing');
testMutation.mutate();
};
const handleSave = async () => {
const values = await form.validateFields();
saveMutation.mutate(values);
};
if (isLoading) {
return <Spin />;
}
return (
<div>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 24 }}>
<Title level={4} style={{ margin: 0 }}>Configuracion Odoo</Title>
<Space>
{config?.is_connected ? (
<Tag icon={<CheckCircleOutlined />} color="success">Conectado</Tag>
) : (
<Tag icon={<CloseCircleOutlined />} color="error">Desconectado</Tag>
)}
</Space>
</div>
<Card>
<Form form={form} layout="vertical" style={{ maxWidth: 500 }}>
<Form.Item
name="url"
label="URL de Odoo"
rules={[{ required: true, message: 'Ingrese la URL' }]}
>
<Input
prefix={<LinkOutlined />}
placeholder="https://tu-empresa.odoo.com"
/>
</Form.Item>
<Form.Item
name="database"
label="Base de Datos"
rules={[{ required: true, message: 'Ingrese el nombre de la base de datos' }]}
>
<Input placeholder="nombre_bd" />
</Form.Item>
<Form.Item
name="username"
label="Usuario (Email)"
rules={[{ required: true, message: 'Ingrese el usuario' }]}
>
<Input placeholder="usuario@empresa.com" />
</Form.Item>
<Form.Item
name="api_key"
label="API Key"
extra="Dejar vacio para mantener la actual"
>
<Input.Password placeholder="Nueva API Key (opcional)" />
</Form.Item>
<Alert
message="Como obtener la API Key"
description={
<ol style={{ paddingLeft: 20, margin: 0 }}>
<li>Inicia sesion en Odoo</li>
<li>Ve a Ajustes - Usuarios</li>
<li>Selecciona tu usuario</li>
<li>En la pestana Preferencias, genera una API Key</li>
</ol>
}
type="info"
style={{ marginBottom: 24 }}
/>
<Space>
<Button
type="primary"
onClick={handleSave}
loading={saveMutation.isPending}
>
Guardar
</Button>
<Button
icon={<ReloadOutlined spin={testStatus === 'testing'} />}
onClick={handleTest}
loading={testMutation.isPending}
>
Probar Conexion
</Button>
</Space>
</Form>
</Card>
</div>
);
}