From 20e5c2d13b5c74ebe108186b8ff1a71579a02f72 Mon Sep 17 00:00:00 2001 From: Claude AI Date: Thu, 29 Jan 2026 11:16:20 +0000 Subject: [PATCH] feat(frontend): add GlobalVariables management page Co-Authored-By: Claude Opus 4.5 --- frontend/src/pages/GlobalVariables.tsx | 243 +++++++++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 frontend/src/pages/GlobalVariables.tsx diff --git a/frontend/src/pages/GlobalVariables.tsx b/frontend/src/pages/GlobalVariables.tsx new file mode 100644 index 0000000..ecf1ce8 --- /dev/null +++ b/frontend/src/pages/GlobalVariables.tsx @@ -0,0 +1,243 @@ +import { useState } from 'react'; +import { + Table, + Button, + Modal, + Form, + Input, + Select, + Checkbox, + Space, + Typography, + Popconfirm, + message, + Tag, +} from 'antd'; +import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons'; +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { apiClient } from '../api/client'; + +const { Title } = Typography; +const { TextArea } = Input; + +interface GlobalVariable { + id: string; + key: string; + value: string; + value_type: 'string' | 'number' | 'boolean' | 'json'; + description: string | null; + is_secret: boolean; +} + +interface VariableFormData { + key: string; + value: string; + value_type: 'string' | 'number' | 'boolean' | 'json'; + description?: string; + is_secret: boolean; +} + +export default function GlobalVariables() { + const [isModalOpen, setIsModalOpen] = useState(false); + const [editingVariable, setEditingVariable] = useState(null); + const [form] = Form.useForm(); + const queryClient = useQueryClient(); + + const { data: variables, isLoading } = useQuery({ + queryKey: ['global-variables'], + queryFn: () => apiClient.get('/api/global-variables'), + }); + + const createMutation = useMutation({ + mutationFn: (data: VariableFormData) => + apiClient.post('/api/global-variables', data), + onSuccess: () => { + message.success('Variable creada'); + queryClient.invalidateQueries({ queryKey: ['global-variables'] }); + handleCloseModal(); + }, + }); + + const updateMutation = useMutation({ + mutationFn: ({ key, data }: { key: string; data: Partial }) => + apiClient.put(`/api/global-variables/${key}`, data), + onSuccess: () => { + message.success('Variable actualizada'); + queryClient.invalidateQueries({ queryKey: ['global-variables'] }); + handleCloseModal(); + }, + }); + + const deleteMutation = useMutation({ + mutationFn: (key: string) => + apiClient.delete(`/api/global-variables/${key}`), + onSuccess: () => { + message.success('Variable eliminada'); + queryClient.invalidateQueries({ queryKey: ['global-variables'] }); + }, + }); + + const handleOpenCreate = () => { + setEditingVariable(null); + form.resetFields(); + form.setFieldsValue({ value_type: 'string', is_secret: false }); + setIsModalOpen(true); + }; + + const handleOpenEdit = (variable: GlobalVariable) => { + setEditingVariable(variable); + form.setFieldsValue({ + key: variable.key, + value: variable.is_secret ? '' : variable.value, + value_type: variable.value_type, + description: variable.description || '', + is_secret: variable.is_secret, + }); + setIsModalOpen(true); + }; + + const handleCloseModal = () => { + setIsModalOpen(false); + setEditingVariable(null); + form.resetFields(); + }; + + const handleSubmit = async () => { + const values = await form.validateFields(); + if (editingVariable) { + const updateData: Partial = { + value_type: values.value_type, + description: values.description, + is_secret: values.is_secret, + }; + if (values.value) { + updateData.value = values.value; + } + updateMutation.mutate({ key: editingVariable.key, data: updateData }); + } else { + createMutation.mutate(values); + } + }; + + const columns = [ + { + title: 'Clave', + dataIndex: 'key', + key: 'key', + render: (key: string) => {key}, + }, + { + title: 'Valor', + dataIndex: 'value', + key: 'value', + render: (value: string, record: GlobalVariable) => + record.is_secret ? '••••••••' : value, + }, + { + title: 'Tipo', + dataIndex: 'value_type', + key: 'value_type', + render: (type: string) => {type}, + }, + { + title: 'Descripcion', + dataIndex: 'description', + key: 'description', + ellipsis: true, + }, + { + title: 'Secreto', + dataIndex: 'is_secret', + key: 'is_secret', + render: (isSecret: boolean) => + isSecret ? Si : No, + }, + { + title: 'Acciones', + key: 'actions', + render: (_: unknown, record: GlobalVariable) => ( + + + + + + + +
+ + + + + +