MedicalInfoSection.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. "use client"
  2. import { useState } from "react"
  3. import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
  4. import { Input } from "@/components/ui/input"
  5. import { Label } from "@/components/ui/label"
  6. import { Textarea } from "@/components/ui/textarea"
  7. import { Heart, AlertCircle } from "lucide-react"
  8. interface MedicalInfoSectionProps {
  9. formData: {
  10. phone?: string
  11. gender?: string
  12. address?: string
  13. emergencyContact?: string
  14. medicalHistory?: string
  15. allergies?: string
  16. currentMedications?: string
  17. }
  18. onInputChange: (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => void
  19. userRole?: string
  20. }
  21. interface ValidationErrors {
  22. phone?: string
  23. emergencyContact?: string
  24. address?: string
  25. medicalHistory?: string
  26. allergies?: string
  27. currentMedications?: string
  28. }
  29. // Exportar función de validación para uso en el hook
  30. export const validateMedicalInfo = (formData: MedicalInfoSectionProps['formData']): { isValid: boolean; errors: ValidationErrors } => {
  31. const errors: ValidationErrors = {}
  32. // Validar teléfono
  33. if (formData.phone) {
  34. const phoneRegex = /^[\d\s()+-]+$/
  35. if (!phoneRegex.test(formData.phone)) {
  36. errors.phone = "Solo se permiten números, espacios, paréntesis, guiones y +"
  37. } else {
  38. const digits = formData.phone.replace(/\D/g, '')
  39. if (digits.length > 0 && digits.length < 10) {
  40. errors.phone = "El número debe tener al menos 9 dígitos"
  41. }
  42. }
  43. if (formData.phone.length > 20) {
  44. errors.phone = "Máximo 20 caracteres"
  45. }
  46. }
  47. // Validar contacto de emergencia
  48. if (formData.emergencyContact && formData.emergencyContact.length > 100) {
  49. errors.emergencyContact = "Máximo 100 caracteres"
  50. }
  51. // Validar dirección
  52. if (formData.address && formData.address.length > 200) {
  53. errors.address = "Máximo 200 caracteres"
  54. }
  55. // Validar campos de texto
  56. if (formData.medicalHistory && formData.medicalHistory.length > 1000) {
  57. errors.medicalHistory = "Máximo 1000 caracteres"
  58. }
  59. if (formData.allergies && formData.allergies.length > 500) {
  60. errors.allergies = "Máximo 500 caracteres"
  61. }
  62. if (formData.currentMedications && formData.currentMedications.length > 1000) {
  63. errors.currentMedications = "Máximo 1000 caracteres"
  64. }
  65. return {
  66. isValid: Object.keys(errors).length === 0,
  67. errors
  68. }
  69. }
  70. export default function MedicalInfoSection({
  71. formData,
  72. onInputChange,
  73. userRole
  74. }: MedicalInfoSectionProps) {
  75. const [errors, setErrors] = useState<ValidationErrors>({})
  76. // Solo mostrar para pacientes
  77. if (userRole !== "PATIENT") {
  78. return null
  79. }
  80. // Validación de teléfono: solo números, espacios, paréntesis, guiones y +
  81. const validatePhone = (value: string): string | undefined => {
  82. if (!value) return undefined
  83. const phoneRegex = /^[\d\s()+-]+$/
  84. if (!phoneRegex.test(value)) {
  85. return "Solo se permiten números, espacios, paréntesis, guiones y +"
  86. }
  87. // Verificar que tenga al menos 7 dígitos
  88. const digits = value.replace(/\D/g, '')
  89. if (digits.length > 0 && digits.length < 7) {
  90. return "El número debe tener al menos 7 dígitos"
  91. }
  92. if (value.length > 20) {
  93. return "Máximo 20 caracteres"
  94. }
  95. return undefined
  96. }
  97. // Validación de contacto de emergencia
  98. const validateEmergencyContact = (value: string): string | undefined => {
  99. if (!value) return undefined
  100. if (value.length > 100) {
  101. return "Máximo 100 caracteres"
  102. }
  103. return undefined
  104. }
  105. // Validación de dirección
  106. const validateAddress = (value: string): string | undefined => {
  107. if (!value) return undefined
  108. if (value.length > 200) {
  109. return "Máximo 200 caracteres"
  110. }
  111. return undefined
  112. }
  113. // Validación de campos de texto largos
  114. const validateTextArea = (value: string, maxLength: number): string | undefined => {
  115. if (!value) return undefined
  116. if (value.length > maxLength) {
  117. return `Máximo ${maxLength} caracteres`
  118. }
  119. return undefined
  120. }
  121. const handleValidatedChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
  122. const { name, value } = e.target
  123. let error: string | undefined
  124. // Validar según el campo
  125. switch (name) {
  126. case 'phone':
  127. error = validatePhone(value)
  128. break
  129. case 'emergencyContact':
  130. error = validateEmergencyContact(value)
  131. break
  132. case 'address':
  133. error = validateAddress(value)
  134. break
  135. case 'medicalHistory':
  136. error = validateTextArea(value, 1000)
  137. break
  138. case 'allergies':
  139. error = validateTextArea(value, 500)
  140. break
  141. case 'currentMedications':
  142. error = validateTextArea(value, 1000)
  143. break
  144. }
  145. // Actualizar errores
  146. setErrors(prev => ({
  147. ...prev,
  148. [name]: error
  149. }))
  150. // Solo llamar onChange si no hay error crítico (permitir seguir escribiendo)
  151. onInputChange(e)
  152. }
  153. return (
  154. <Card>
  155. <CardHeader>
  156. <CardTitle className="flex items-center">
  157. <Heart className="w-5 h-5 mr-2 text-primary" />
  158. Información Médica y Personal
  159. </CardTitle>
  160. <p className="text-sm text-muted-foreground">
  161. Esta información ayudará al chatbot a brindarte recomendaciones más precisas
  162. </p>
  163. </CardHeader>
  164. <CardContent className="space-y-4">
  165. {/* Información Personal */}
  166. <div className="space-y-4 pb-4 border-b">
  167. <h3 className="font-semibold text-sm text-foreground">Datos de Contacto</h3>
  168. <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
  169. <div className="space-y-2">
  170. <Label htmlFor="phone">Teléfono</Label>
  171. <Input
  172. id="phone"
  173. name="phone"
  174. type="tel"
  175. placeholder="096 028 3974"
  176. value={formData.phone || ""}
  177. onChange={handleValidatedChange}
  178. className={errors.phone ? "border-red-500" : ""}
  179. />
  180. {errors.phone && (
  181. <div className="flex items-start gap-1 text-xs text-red-600">
  182. <AlertCircle className="w-3 h-3 mt-0.5 flex-shrink-0" />
  183. <span>{errors.phone}</span>
  184. </div>
  185. )}
  186. </div>
  187. <div className="space-y-2">
  188. <Label htmlFor="gender">Género</Label>
  189. <select
  190. id="gender"
  191. name="gender"
  192. value={formData.gender || ""}
  193. onChange={handleValidatedChange}
  194. className="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-ring focus:border-transparent text-sm bg-background"
  195. >
  196. <option value="">Seleccionar...</option>
  197. <option value="MALE">Masculino</option>
  198. <option value="FEMALE">Femenino</option>
  199. <option value="OTHER">Otro</option>
  200. <option value="PREFER_NOT_TO_SAY">Prefiero no decirlo</option>
  201. </select>
  202. </div>
  203. </div>
  204. <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
  205. <div className="space-y-2">
  206. <Label htmlFor="emergencyContact">Contacto de Emergencia</Label>
  207. <Input
  208. id="emergencyContact"
  209. name="emergencyContact"
  210. type="text"
  211. placeholder="Nombre y teléfono"
  212. value={formData.emergencyContact || ""}
  213. onChange={handleValidatedChange}
  214. className={errors.emergencyContact ? "border-red-500" : ""}
  215. />
  216. {errors.emergencyContact && (
  217. <div className="flex items-start gap-1 text-xs text-red-600">
  218. <AlertCircle className="w-3 h-3 mt-0.5 flex-shrink-0" />
  219. <span>{errors.emergencyContact}</span>
  220. </div>
  221. )}
  222. <p className="text-xs text-muted-foreground">
  223. Ejemplo: María López - +57 300 123 4567
  224. </p>
  225. </div>
  226. <div className="space-y-2">
  227. <Label htmlFor="address">Dirección</Label>
  228. <Input
  229. id="address"
  230. name="address"
  231. type="text"
  232. placeholder="Calle, número, ciudad, código postal"
  233. value={formData.address || ""}
  234. onChange={handleValidatedChange}
  235. className={errors.address ? "border-red-500" : ""}
  236. />
  237. {errors.address && (
  238. <div className="flex items-start gap-1 text-xs text-red-600">
  239. <AlertCircle className="w-3 h-3 mt-0.5 flex-shrink-0" />
  240. <span>{errors.address}</span>
  241. </div>
  242. )}
  243. </div>
  244. </div>
  245. </div>
  246. {/* Información Médica */}
  247. <div className="space-y-4 pt-2">
  248. <h3 className="font-semibold text-sm text-foreground">Información Médica</h3>
  249. <div className="space-y-2">
  250. <Label htmlFor="medicalHistory">Historial Médico</Label>
  251. <Textarea
  252. id="medicalHistory"
  253. name="medicalHistory"
  254. placeholder="Enfermedades previas, cirugías, condiciones crónicas, etc."
  255. value={formData.medicalHistory || ""}
  256. onChange={handleValidatedChange}
  257. rows={3}
  258. className={`resize-none ${errors.medicalHistory ? "border-red-500" : ""}`}
  259. />
  260. <div className="flex justify-between items-start">
  261. <p className="text-xs text-muted-foreground">
  262. Ejemplo: Diabetes tipo 2, hipertensión, apendicectomía en 2020
  263. </p>
  264. <span className="text-xs text-muted-foreground">
  265. {formData.medicalHistory?.length || 0}/1000
  266. </span>
  267. </div>
  268. {errors.medicalHistory && (
  269. <div className="flex items-start gap-1 text-xs text-red-600">
  270. <AlertCircle className="w-3 h-3 mt-0.5 flex-shrink-0" />
  271. <span>{errors.medicalHistory}</span>
  272. </div>
  273. )}
  274. </div>
  275. <div className="space-y-2">
  276. <Label htmlFor="allergies">Alergias</Label>
  277. <Textarea
  278. id="allergies"
  279. name="allergies"
  280. placeholder="Alergias a medicamentos, alimentos, o sustancias"
  281. value={formData.allergies || ""}
  282. onChange={handleValidatedChange}
  283. rows={2}
  284. className={`resize-none ${errors.allergies ? "border-red-500" : ""}`}
  285. />
  286. <div className="flex justify-between items-start">
  287. <p className="text-xs text-muted-foreground">
  288. Ejemplo: Penicilina, mariscos, polen
  289. </p>
  290. <span className="text-xs text-muted-foreground">
  291. {formData.allergies?.length || 0}/500
  292. </span>
  293. </div>
  294. {errors.allergies && (
  295. <div className="flex items-start gap-1 text-xs text-red-600">
  296. <AlertCircle className="w-3 h-3 mt-0.5 flex-shrink-0" />
  297. <span>{errors.allergies}</span>
  298. </div>
  299. )}
  300. </div>
  301. <div className="space-y-2">
  302. <Label htmlFor="currentMedications">Medicamentos Actuales</Label>
  303. <Textarea
  304. id="currentMedications"
  305. name="currentMedications"
  306. placeholder="Medicamentos que estás tomando actualmente"
  307. value={formData.currentMedications || ""}
  308. onChange={handleValidatedChange}
  309. rows={3}
  310. className={`resize-none ${errors.currentMedications ? "border-red-500" : ""}`}
  311. />
  312. <div className="flex justify-between items-start">
  313. <p className="text-xs text-muted-foreground">
  314. Ejemplo: Metformina 850mg (2 veces al día), Losartán 50mg (1 vez al día)
  315. </p>
  316. <span className="text-xs text-muted-foreground">
  317. {formData.currentMedications?.length || 0}/1000
  318. </span>
  319. </div>
  320. {errors.currentMedications && (
  321. <div className="flex items-start gap-1 text-xs text-red-600">
  322. <AlertCircle className="w-3 h-3 mt-0.5 flex-shrink-0" />
  323. <span>{errors.currentMedications}</span>
  324. </div>
  325. )}
  326. </div>
  327. </div>
  328. </CardContent>
  329. </Card>
  330. )
  331. }