648 lines
24 KiB
TypeScript
648 lines
24 KiB
TypeScript
|
|
/**
|
||
|
|
* Editor de Fármaco (Vademécum TES)
|
||
|
|
*
|
||
|
|
* Editor completo para crear/editar fármacos
|
||
|
|
* Basado en: docs/VADEMECUM_COMPLETO_TES.md
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { useState, useEffect } from 'react';
|
||
|
|
import { useParams, useNavigate } from 'react-router-dom';
|
||
|
|
import { Save, X, Send, CheckCircle, Plus, Trash2 } from 'lucide-react';
|
||
|
|
import { useAuth } from '../contexts/AuthContext';
|
||
|
|
|
||
|
|
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3000';
|
||
|
|
|
||
|
|
interface DrugFormData {
|
||
|
|
generic_name: string;
|
||
|
|
trade_name?: string;
|
||
|
|
category: string;
|
||
|
|
line: 'first' | 'second';
|
||
|
|
frequency: 'high' | 'medium' | 'low';
|
||
|
|
presentation: string;
|
||
|
|
adult_dose: string;
|
||
|
|
pediatric_dose?: string;
|
||
|
|
routes: string[];
|
||
|
|
dilution?: string;
|
||
|
|
indications: string[];
|
||
|
|
contraindications: string[];
|
||
|
|
side_effects?: string;
|
||
|
|
antidote?: string;
|
||
|
|
notes: string[];
|
||
|
|
critical_points: string[];
|
||
|
|
source?: string;
|
||
|
|
status: 'draft' | 'in_review' | 'approved' | 'published' | 'archived';
|
||
|
|
}
|
||
|
|
|
||
|
|
const ROUTES_OPTIONS = ['IV', 'IO', 'IM', 'Subcutánea', 'Oral', 'Rectal', 'Intranasal', 'Nebulización', 'MDI'];
|
||
|
|
const CATEGORIES = [
|
||
|
|
'cardiovascular',
|
||
|
|
'respiratorio',
|
||
|
|
'neurologico',
|
||
|
|
'analgesico',
|
||
|
|
'fluidos',
|
||
|
|
'antidoto',
|
||
|
|
'hemostatico',
|
||
|
|
'diuretico',
|
||
|
|
'corticosteroide',
|
||
|
|
'antiepileptico',
|
||
|
|
'anestesico',
|
||
|
|
'metabolico',
|
||
|
|
'antiagregante',
|
||
|
|
];
|
||
|
|
|
||
|
|
export default function DrugEditorPage() {
|
||
|
|
const { id } = useParams<{ id: string }>();
|
||
|
|
const navigate = useNavigate();
|
||
|
|
const { hasPermission } = useAuth();
|
||
|
|
const isNew = id === 'new';
|
||
|
|
|
||
|
|
const [isLoading, setIsLoading] = useState(!isNew);
|
||
|
|
const [isSaving, setIsSaving] = useState(false);
|
||
|
|
const [formData, setFormData] = useState<DrugFormData>({
|
||
|
|
generic_name: '',
|
||
|
|
trade_name: '',
|
||
|
|
category: 'cardiovascular',
|
||
|
|
line: 'first',
|
||
|
|
frequency: 'high',
|
||
|
|
presentation: '',
|
||
|
|
adult_dose: '',
|
||
|
|
pediatric_dose: '',
|
||
|
|
routes: [],
|
||
|
|
dilution: '',
|
||
|
|
indications: [],
|
||
|
|
contraindications: [],
|
||
|
|
side_effects: '',
|
||
|
|
antidote: '',
|
||
|
|
notes: [],
|
||
|
|
critical_points: [],
|
||
|
|
source: '',
|
||
|
|
status: 'draft',
|
||
|
|
});
|
||
|
|
|
||
|
|
// Cargar fármaco si es edición
|
||
|
|
useEffect(() => {
|
||
|
|
if (!isNew && id) {
|
||
|
|
loadDrug(id);
|
||
|
|
}
|
||
|
|
}, [id, isNew]);
|
||
|
|
|
||
|
|
const loadDrug = async (drugId: string) => {
|
||
|
|
setIsLoading(true);
|
||
|
|
try {
|
||
|
|
const token = localStorage.getItem('admin_token');
|
||
|
|
const response = await fetch(`${API_URL}/api/drugs/${drugId}`, {
|
||
|
|
headers: {
|
||
|
|
'Authorization': `Bearer ${token}`,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
if (response.ok) {
|
||
|
|
const drug = await response.json();
|
||
|
|
setFormData({
|
||
|
|
generic_name: drug.generic_name || '',
|
||
|
|
trade_name: drug.trade_name || '',
|
||
|
|
category: drug.category || 'cardiovascular',
|
||
|
|
line: drug.line || 'first',
|
||
|
|
frequency: drug.frequency || 'high',
|
||
|
|
presentation: drug.presentation || '',
|
||
|
|
adult_dose: drug.adult_dose || '',
|
||
|
|
pediatric_dose: drug.pediatric_dose || '',
|
||
|
|
routes: drug.routes || [],
|
||
|
|
dilution: drug.dilution || '',
|
||
|
|
indications: drug.indications || [],
|
||
|
|
contraindications: drug.contraindications || [],
|
||
|
|
side_effects: drug.side_effects || '',
|
||
|
|
antidote: drug.antidote || '',
|
||
|
|
notes: drug.notes || [],
|
||
|
|
critical_points: drug.critical_points || [],
|
||
|
|
source: drug.source || '',
|
||
|
|
status: drug.status || 'draft',
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
alert('Error cargando fármaco');
|
||
|
|
navigate('/drugs');
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Error cargando fármaco:', error);
|
||
|
|
alert('Error cargando fármaco');
|
||
|
|
} finally {
|
||
|
|
setIsLoading(false);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleSave = async () => {
|
||
|
|
setIsSaving(true);
|
||
|
|
try {
|
||
|
|
const token = localStorage.getItem('admin_token');
|
||
|
|
const url = isNew ? `${API_URL}/api/drugs` : `${API_URL}/api/drugs/${id}`;
|
||
|
|
const method = isNew ? 'POST' : 'PUT';
|
||
|
|
|
||
|
|
const response = await fetch(url, {
|
||
|
|
method,
|
||
|
|
headers: {
|
||
|
|
'Content-Type': 'application/json',
|
||
|
|
'Authorization': `Bearer ${token}`,
|
||
|
|
},
|
||
|
|
body: JSON.stringify(formData),
|
||
|
|
});
|
||
|
|
|
||
|
|
if (response.ok) {
|
||
|
|
const data = await response.json();
|
||
|
|
if (isNew) {
|
||
|
|
navigate(`/drugs/${data.drug.id}/edit`);
|
||
|
|
} else {
|
||
|
|
alert('Fármaco guardado correctamente');
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
const error = await response.json();
|
||
|
|
alert(`Error: ${error.error || 'Error al guardar'}\n${error.details?.join('\n') || ''}`);
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Error guardando fármaco:', error);
|
||
|
|
alert('Error al guardar fármaco');
|
||
|
|
} finally {
|
||
|
|
setIsSaving(false);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleSubmit = async () => {
|
||
|
|
if (!confirm('¿Enviar este fármaco a revisión?')) return;
|
||
|
|
|
||
|
|
try {
|
||
|
|
const token = localStorage.getItem('admin_token');
|
||
|
|
const response = await fetch(`${API_URL}/api/drugs/${id}/submit`, {
|
||
|
|
method: 'POST',
|
||
|
|
headers: {
|
||
|
|
'Content-Type': 'application/json',
|
||
|
|
'Authorization': `Bearer ${token}`,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
if (response.ok) {
|
||
|
|
alert('Fármaco enviado a revisión');
|
||
|
|
navigate('/drugs');
|
||
|
|
} else {
|
||
|
|
const error = await response.json();
|
||
|
|
alert(`Error: ${error.error || 'Error al enviar a revisión'}`);
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Error enviando a revisión:', error);
|
||
|
|
alert('Error al enviar a revisión');
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const addArrayItem = (field: 'routes' | 'indications' | 'contraindications' | 'notes' | 'critical_points') => {
|
||
|
|
setFormData(prev => ({
|
||
|
|
...prev,
|
||
|
|
[field]: [...prev[field], ''],
|
||
|
|
}));
|
||
|
|
};
|
||
|
|
|
||
|
|
const updateArrayItem = (
|
||
|
|
field: 'routes' | 'indications' | 'contraindications' | 'notes' | 'critical_points',
|
||
|
|
index: number,
|
||
|
|
value: string
|
||
|
|
) => {
|
||
|
|
setFormData(prev => ({
|
||
|
|
...prev,
|
||
|
|
[field]: prev[field].map((item, i) => i === index ? value : item),
|
||
|
|
}));
|
||
|
|
};
|
||
|
|
|
||
|
|
const removeArrayItem = (
|
||
|
|
field: 'routes' | 'indications' | 'contraindications' | 'notes' | 'critical_points',
|
||
|
|
index: number
|
||
|
|
) => {
|
||
|
|
setFormData(prev => ({
|
||
|
|
...prev,
|
||
|
|
[field]: prev[field].filter((_, i) => i !== index),
|
||
|
|
}));
|
||
|
|
};
|
||
|
|
|
||
|
|
if (isLoading) {
|
||
|
|
return <div className="p-6">Cargando fármaco...</div>;
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="p-6 space-y-6 max-w-5xl mx-auto">
|
||
|
|
<div className="flex items-center justify-between">
|
||
|
|
<div>
|
||
|
|
<h1 className="text-3xl font-bold text-foreground">
|
||
|
|
{isNew ? 'Nuevo Fármaco' : `Editar: ${formData.generic_name}`}
|
||
|
|
</h1>
|
||
|
|
<p className="text-muted-foreground mt-1">
|
||
|
|
{isNew ? 'Crear nuevo fármaco en el vademécum' : 'Editar información del fármaco'}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
<div className="flex gap-2">
|
||
|
|
<button
|
||
|
|
onClick={() => navigate('/drugs')}
|
||
|
|
className="px-4 py-2 bg-background border border-border rounded-lg hover:bg-muted transition-colors flex items-center gap-2"
|
||
|
|
>
|
||
|
|
<X className="w-4 h-4" />
|
||
|
|
Cancelar
|
||
|
|
</button>
|
||
|
|
{!isNew && hasPermission('content:submit') && formData.status === 'draft' && (
|
||
|
|
<button
|
||
|
|
onClick={handleSubmit}
|
||
|
|
className="px-4 py-2 bg-orange-500 text-white rounded-lg hover:bg-orange-600 transition-colors flex items-center gap-2"
|
||
|
|
>
|
||
|
|
<Send className="w-4 h-4" />
|
||
|
|
Enviar a Revisión
|
||
|
|
</button>
|
||
|
|
)}
|
||
|
|
{!isNew && hasPermission('validation:approve') && formData.status === 'in_review' && (
|
||
|
|
<button
|
||
|
|
onClick={async () => {
|
||
|
|
if (!confirm('¿Aprobar este fármaco?')) return;
|
||
|
|
try {
|
||
|
|
const token = localStorage.getItem('admin_token');
|
||
|
|
const response = await fetch(`${API_URL}/api/drugs/${id}/approve`, {
|
||
|
|
method: 'POST',
|
||
|
|
headers: {
|
||
|
|
'Content-Type': 'application/json',
|
||
|
|
'Authorization': `Bearer ${token}`,
|
||
|
|
},
|
||
|
|
body: JSON.stringify({ notes: '' }),
|
||
|
|
});
|
||
|
|
if (response.ok) {
|
||
|
|
alert('Fármaco aprobado');
|
||
|
|
navigate('/drugs');
|
||
|
|
} else {
|
||
|
|
const error = await response.json();
|
||
|
|
alert(`Error: ${error.error || 'Error al aprobar'}`);
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Error aprobando fármaco:', error);
|
||
|
|
alert('Error al aprobar fármaco');
|
||
|
|
}
|
||
|
|
}}
|
||
|
|
className="px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors flex items-center gap-2"
|
||
|
|
>
|
||
|
|
<CheckCircle className="w-4 h-4" />
|
||
|
|
Aprobar
|
||
|
|
</button>
|
||
|
|
)}
|
||
|
|
{!isNew && hasPermission('content:publish') && formData.status === 'approved' && (
|
||
|
|
<button
|
||
|
|
onClick={async () => {
|
||
|
|
if (!confirm('¿Publicar este fármaco? (Requiere pediatric_dose)')) return;
|
||
|
|
try {
|
||
|
|
const token = localStorage.getItem('admin_token');
|
||
|
|
const response = await fetch(`${API_URL}/api/drugs/${id}/publish`, {
|
||
|
|
method: 'POST',
|
||
|
|
headers: {
|
||
|
|
'Content-Type': 'application/json',
|
||
|
|
'Authorization': `Bearer ${token}`,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
if (response.ok) {
|
||
|
|
alert('Fármaco publicado');
|
||
|
|
navigate('/drugs');
|
||
|
|
} else {
|
||
|
|
const error = await response.json();
|
||
|
|
alert(`Error: ${error.error || 'Error al publicar'}`);
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Error publicando fármaco:', error);
|
||
|
|
alert('Error al publicar fármaco');
|
||
|
|
}
|
||
|
|
}}
|
||
|
|
className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors flex items-center gap-2"
|
||
|
|
>
|
||
|
|
<CheckCircle className="w-4 h-4" />
|
||
|
|
Publicar
|
||
|
|
</button>
|
||
|
|
)}
|
||
|
|
<button
|
||
|
|
onClick={handleSave}
|
||
|
|
disabled={isSaving}
|
||
|
|
className="px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors flex items-center gap-2 disabled:opacity-50"
|
||
|
|
>
|
||
|
|
<Save className="w-4 h-4" />
|
||
|
|
{isSaving ? 'Guardando...' : 'Guardar'}
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Información Básica */}
|
||
|
|
<div className="bg-card border border-border rounded-xl p-6 space-y-4">
|
||
|
|
<h2 className="text-xl font-semibold text-foreground">Información Básica</h2>
|
||
|
|
|
||
|
|
<div className="grid grid-cols-2 gap-4">
|
||
|
|
<div>
|
||
|
|
<label className="block text-sm font-medium text-foreground mb-1">
|
||
|
|
Nombre Genérico <span className="text-red-500">*</span>
|
||
|
|
</label>
|
||
|
|
<input
|
||
|
|
type="text"
|
||
|
|
value={formData.generic_name}
|
||
|
|
onChange={(e) => setFormData(prev => ({ ...prev, generic_name: e.target.value }))}
|
||
|
|
className="w-full px-3 py-2 bg-background border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary"
|
||
|
|
placeholder="Ej: Adrenalina"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<label className="block text-sm font-medium text-foreground mb-1">Nombre Comercial</label>
|
||
|
|
<input
|
||
|
|
type="text"
|
||
|
|
value={formData.trade_name}
|
||
|
|
onChange={(e) => setFormData(prev => ({ ...prev, trade_name: e.target.value }))}
|
||
|
|
className="w-full px-3 py-2 bg-background border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary"
|
||
|
|
placeholder="Ej: Adrenalina 1mg/1ml"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<label className="block text-sm font-medium text-foreground mb-1">
|
||
|
|
Categoría <span className="text-red-500">*</span>
|
||
|
|
</label>
|
||
|
|
<select
|
||
|
|
value={formData.category}
|
||
|
|
onChange={(e) => setFormData(prev => ({ ...prev, category: e.target.value }))}
|
||
|
|
className="w-full px-3 py-2 bg-background border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary"
|
||
|
|
>
|
||
|
|
{CATEGORIES.map(cat => (
|
||
|
|
<option key={cat} value={cat}>{cat}</option>
|
||
|
|
))}
|
||
|
|
</select>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<label className="block text-sm font-medium text-foreground mb-1">
|
||
|
|
Línea <span className="text-red-500">*</span>
|
||
|
|
</label>
|
||
|
|
<select
|
||
|
|
value={formData.line}
|
||
|
|
onChange={(e) => setFormData(prev => ({ ...prev, line: e.target.value as 'first' | 'second' }))}
|
||
|
|
className="w-full px-3 py-2 bg-background border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary"
|
||
|
|
>
|
||
|
|
<option value="first">Primera línea</option>
|
||
|
|
<option value="second">Segunda línea</option>
|
||
|
|
</select>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<label className="block text-sm font-medium text-foreground mb-1">
|
||
|
|
Frecuencia <span className="text-red-500">*</span>
|
||
|
|
</label>
|
||
|
|
<select
|
||
|
|
value={formData.frequency}
|
||
|
|
onChange={(e) => setFormData(prev => ({ ...prev, frequency: e.target.value as 'high' | 'medium' | 'low' }))}
|
||
|
|
className="w-full px-3 py-2 bg-background border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary"
|
||
|
|
>
|
||
|
|
<option value="high">Alta</option>
|
||
|
|
<option value="medium">Media</option>
|
||
|
|
<option value="low">Baja</option>
|
||
|
|
</select>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Presentación y Dosificación */}
|
||
|
|
<div className="bg-card border border-border rounded-xl p-6 space-y-4">
|
||
|
|
<h2 className="text-xl font-semibold text-foreground">Presentación y Dosificación</h2>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<label className="block text-sm font-medium text-foreground mb-1">
|
||
|
|
Presentación <span className="text-red-500">*</span>
|
||
|
|
</label>
|
||
|
|
<input
|
||
|
|
type="text"
|
||
|
|
value={formData.presentation}
|
||
|
|
onChange={(e) => setFormData(prev => ({ ...prev, presentation: e.target.value }))}
|
||
|
|
className="w-full px-3 py-2 bg-background border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary"
|
||
|
|
placeholder="Ej: 1mg/1ml ampolla"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="grid grid-cols-2 gap-4">
|
||
|
|
<div>
|
||
|
|
<label className="block text-sm font-medium text-foreground mb-1">
|
||
|
|
Dosis Adulto <span className="text-red-500">*</span>
|
||
|
|
</label>
|
||
|
|
<textarea
|
||
|
|
value={formData.adult_dose}
|
||
|
|
onChange={(e) => setFormData(prev => ({ ...prev, adult_dose: e.target.value }))}
|
||
|
|
className="w-full px-3 py-2 bg-background border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary"
|
||
|
|
rows={2}
|
||
|
|
placeholder="Ej: 1mg IV/IO cada 3-5 min"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<label className="block text-sm font-medium text-foreground mb-1">
|
||
|
|
Dosis Pediátrica {formData.status === 'published' && <span className="text-red-500">*</span>}
|
||
|
|
</label>
|
||
|
|
<textarea
|
||
|
|
value={formData.pediatric_dose}
|
||
|
|
onChange={(e) => setFormData(prev => ({ ...prev, pediatric_dose: e.target.value }))}
|
||
|
|
className="w-full px-3 py-2 bg-background border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary"
|
||
|
|
rows={2}
|
||
|
|
placeholder="Ej: 0.01mg/kg IV/IO"
|
||
|
|
/>
|
||
|
|
{formData.status === 'published' && !formData.pediatric_dose && (
|
||
|
|
<p className="text-xs text-red-500 mt-1">Obligatorio para publicar</p>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<label className="block text-sm font-medium text-foreground mb-1">Vías de Administración</label>
|
||
|
|
<div className="flex flex-wrap gap-2">
|
||
|
|
{ROUTES_OPTIONS.map(route => (
|
||
|
|
<label key={route} className="flex items-center gap-2 px-3 py-2 bg-background border border-border rounded-lg cursor-pointer hover:bg-muted">
|
||
|
|
<input
|
||
|
|
type="checkbox"
|
||
|
|
checked={formData.routes.includes(route)}
|
||
|
|
onChange={(e) => {
|
||
|
|
if (e.target.checked) {
|
||
|
|
setFormData(prev => ({ ...prev, routes: [...prev.routes, route] }));
|
||
|
|
} else {
|
||
|
|
setFormData(prev => ({ ...prev, routes: prev.routes.filter(r => r !== route) }));
|
||
|
|
}
|
||
|
|
}}
|
||
|
|
className="rounded"
|
||
|
|
/>
|
||
|
|
<span className="text-sm">{route}</span>
|
||
|
|
</label>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<label className="block text-sm font-medium text-foreground mb-1">Dilución</label>
|
||
|
|
<input
|
||
|
|
type="text"
|
||
|
|
value={formData.dilution}
|
||
|
|
onChange={(e) => setFormData(prev => ({ ...prev, dilution: e.target.value }))}
|
||
|
|
className="w-full px-3 py-2 bg-background border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary"
|
||
|
|
placeholder="Ej: Diluir en 20ml SF 0.9%"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Indicaciones y Contraindicaciones */}
|
||
|
|
<div className="bg-card border border-border rounded-xl p-6 space-y-4">
|
||
|
|
<h2 className="text-xl font-semibold text-foreground">Indicaciones y Contraindicaciones</h2>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<div className="flex items-center justify-between mb-2">
|
||
|
|
<label className="block text-sm font-medium text-foreground">Indicaciones</label>
|
||
|
|
<button
|
||
|
|
onClick={() => addArrayItem('indications')}
|
||
|
|
className="p-1 text-primary hover:bg-primary/10 rounded"
|
||
|
|
>
|
||
|
|
<Plus className="w-4 h-4" />
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
{formData.indications.map((indication, index) => (
|
||
|
|
<div key={index} className="flex gap-2 mb-2">
|
||
|
|
<input
|
||
|
|
type="text"
|
||
|
|
value={indication}
|
||
|
|
onChange={(e) => updateArrayItem('indications', index, e.target.value)}
|
||
|
|
className="flex-1 px-3 py-2 bg-background border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary"
|
||
|
|
placeholder="Ej: Parada cardiorrespiratoria (RCP)"
|
||
|
|
/>
|
||
|
|
<button
|
||
|
|
onClick={() => removeArrayItem('indications', index)}
|
||
|
|
className="p-2 text-red-500 hover:bg-red-500/10 rounded"
|
||
|
|
>
|
||
|
|
<Trash2 className="w-4 h-4" />
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<div className="flex items-center justify-between mb-2">
|
||
|
|
<label className="block text-sm font-medium text-foreground">Contraindicaciones</label>
|
||
|
|
<button
|
||
|
|
onClick={() => addArrayItem('contraindications')}
|
||
|
|
className="p-1 text-primary hover:bg-primary/10 rounded"
|
||
|
|
>
|
||
|
|
<Plus className="w-4 h-4" />
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
{formData.contraindications.map((contraindication, index) => (
|
||
|
|
<div key={index} className="flex gap-2 mb-2">
|
||
|
|
<input
|
||
|
|
type="text"
|
||
|
|
value={contraindication}
|
||
|
|
onChange={(e) => updateArrayItem('contraindications', index, e.target.value)}
|
||
|
|
className="flex-1 px-3 py-2 bg-background border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary"
|
||
|
|
placeholder="Ej: Hipertensión arterial severa"
|
||
|
|
/>
|
||
|
|
<button
|
||
|
|
onClick={() => removeArrayItem('contraindications', index)}
|
||
|
|
className="p-2 text-red-500 hover:bg-red-500/10 rounded"
|
||
|
|
>
|
||
|
|
<Trash2 className="w-4 h-4" />
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<label className="block text-sm font-medium text-foreground mb-1">Efectos Adversos</label>
|
||
|
|
<textarea
|
||
|
|
value={formData.side_effects}
|
||
|
|
onChange={(e) => setFormData(prev => ({ ...prev, side_effects: e.target.value }))}
|
||
|
|
className="w-full px-3 py-2 bg-background border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary"
|
||
|
|
rows={3}
|
||
|
|
placeholder="Ej: Taquicardia, hipertensión, arritmias..."
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<label className="block text-sm font-medium text-foreground mb-1">Antídoto</label>
|
||
|
|
<input
|
||
|
|
type="text"
|
||
|
|
value={formData.antidote}
|
||
|
|
onChange={(e) => setFormData(prev => ({ ...prev, antidote: e.target.value }))}
|
||
|
|
className="w-full px-3 py-2 bg-background border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary"
|
||
|
|
placeholder="Ej: Naloxona (para opioides)"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Información Específica TES */}
|
||
|
|
<div className="bg-card border border-border rounded-xl p-6 space-y-4">
|
||
|
|
<h2 className="text-xl font-semibold text-foreground">Información Específica TES</h2>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<div className="flex items-center justify-between mb-2">
|
||
|
|
<label className="block text-sm font-medium text-foreground">Notas</label>
|
||
|
|
<button
|
||
|
|
onClick={() => addArrayItem('notes')}
|
||
|
|
className="p-1 text-primary hover:bg-primary/10 rounded"
|
||
|
|
>
|
||
|
|
<Plus className="w-4 h-4" />
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
{formData.notes.map((note, index) => (
|
||
|
|
<div key={index} className="flex gap-2 mb-2">
|
||
|
|
<textarea
|
||
|
|
value={note}
|
||
|
|
onChange={(e) => updateArrayItem('notes', index, e.target.value)}
|
||
|
|
className="flex-1 px-3 py-2 bg-background border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary"
|
||
|
|
rows={2}
|
||
|
|
placeholder="Ej: En RCP, administrar cada 3-5 minutos"
|
||
|
|
/>
|
||
|
|
<button
|
||
|
|
onClick={() => removeArrayItem('notes', index)}
|
||
|
|
className="p-2 text-red-500 hover:bg-red-500/10 rounded"
|
||
|
|
>
|
||
|
|
<Trash2 className="w-4 h-4" />
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<div className="flex items-center justify-between mb-2">
|
||
|
|
<label className="block text-sm font-medium text-foreground">Puntos Críticos TES</label>
|
||
|
|
<button
|
||
|
|
onClick={() => addArrayItem('critical_points')}
|
||
|
|
className="p-1 text-primary hover:bg-primary/10 rounded"
|
||
|
|
>
|
||
|
|
<Plus className="w-4 h-4" />
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
{formData.critical_points.map((point, index) => (
|
||
|
|
<div key={index} className="flex gap-2 mb-2">
|
||
|
|
<textarea
|
||
|
|
value={point}
|
||
|
|
onChange={(e) => updateArrayItem('critical_points', index, e.target.value)}
|
||
|
|
className="flex-1 px-3 py-2 bg-background border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary"
|
||
|
|
rows={2}
|
||
|
|
placeholder="Ej: Verificar dosis según peso en pediatría"
|
||
|
|
/>
|
||
|
|
<button
|
||
|
|
onClick={() => removeArrayItem('critical_points', index)}
|
||
|
|
className="p-2 text-red-500 hover:bg-red-500/10 rounded"
|
||
|
|
>
|
||
|
|
<Trash2 className="w-4 h-4" />
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<label className="block text-sm font-medium text-foreground mb-1">Fuente</label>
|
||
|
|
<input
|
||
|
|
type="text"
|
||
|
|
value={formData.source}
|
||
|
|
onChange={(e) => setFormData(prev => ({ ...prev, source: e.target.value }))}
|
||
|
|
className="w-full px-3 py-2 bg-background border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary"
|
||
|
|
placeholder="Ej: Manual TES Digital, ERC 2021"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|