|
|
@@ -1,10 +1,11 @@
|
|
|
"use client"
|
|
|
|
|
|
+import { useState } from "react"
|
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
|
|
import { Input } from "@/components/ui/input"
|
|
|
import { Label } from "@/components/ui/label"
|
|
|
import { Textarea } from "@/components/ui/textarea"
|
|
|
-import { Heart } from "lucide-react"
|
|
|
+import { Heart, AlertCircle } from "lucide-react"
|
|
|
|
|
|
interface MedicalInfoSectionProps {
|
|
|
formData: {
|
|
|
@@ -19,10 +20,161 @@ interface MedicalInfoSectionProps {
|
|
|
onInputChange: (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => void
|
|
|
}
|
|
|
|
|
|
+interface ValidationErrors {
|
|
|
+ phone?: string
|
|
|
+ emergencyContact?: string
|
|
|
+ address?: string
|
|
|
+ medicalHistory?: string
|
|
|
+ allergies?: string
|
|
|
+ currentMedications?: string
|
|
|
+}
|
|
|
+
|
|
|
+// Exportar función de validación para uso en el hook
|
|
|
+export const validateMedicalInfo = (formData: MedicalInfoSectionProps['formData']): { isValid: boolean; errors: ValidationErrors } => {
|
|
|
+ const errors: ValidationErrors = {}
|
|
|
+
|
|
|
+ // Validar teléfono
|
|
|
+ if (formData.phone) {
|
|
|
+ const phoneRegex = /^[\d\s()+-]+$/
|
|
|
+ if (!phoneRegex.test(formData.phone)) {
|
|
|
+ errors.phone = "Solo se permiten números, espacios, paréntesis, guiones y +"
|
|
|
+ } else {
|
|
|
+ const digits = formData.phone.replace(/\D/g, '')
|
|
|
+ if (digits.length > 0 && digits.length < 10) {
|
|
|
+ errors.phone = "El número debe tener al menos 9 dígitos"
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (formData.phone.length > 20) {
|
|
|
+ errors.phone = "Máximo 20 caracteres"
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Validar contacto de emergencia
|
|
|
+ if (formData.emergencyContact && formData.emergencyContact.length > 100) {
|
|
|
+ errors.emergencyContact = "Máximo 100 caracteres"
|
|
|
+ }
|
|
|
+
|
|
|
+ // Validar dirección
|
|
|
+ if (formData.address && formData.address.length > 200) {
|
|
|
+ errors.address = "Máximo 200 caracteres"
|
|
|
+ }
|
|
|
+
|
|
|
+ // Validar campos de texto
|
|
|
+ if (formData.medicalHistory && formData.medicalHistory.length > 1000) {
|
|
|
+ errors.medicalHistory = "Máximo 1000 caracteres"
|
|
|
+ }
|
|
|
+
|
|
|
+ if (formData.allergies && formData.allergies.length > 500) {
|
|
|
+ errors.allergies = "Máximo 500 caracteres"
|
|
|
+ }
|
|
|
+
|
|
|
+ if (formData.currentMedications && formData.currentMedications.length > 1000) {
|
|
|
+ errors.currentMedications = "Máximo 1000 caracteres"
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ isValid: Object.keys(errors).length === 0,
|
|
|
+ errors
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
export default function MedicalInfoSection({
|
|
|
formData,
|
|
|
onInputChange
|
|
|
}: MedicalInfoSectionProps) {
|
|
|
+ const [errors, setErrors] = useState<ValidationErrors>({})
|
|
|
+
|
|
|
+ // Validación de teléfono: solo números, espacios, paréntesis, guiones y +
|
|
|
+ const validatePhone = (value: string): string | undefined => {
|
|
|
+ if (!value) return undefined
|
|
|
+
|
|
|
+ const phoneRegex = /^[\d\s()+-]+$/
|
|
|
+ if (!phoneRegex.test(value)) {
|
|
|
+ return "Solo se permiten números, espacios, paréntesis, guiones y +"
|
|
|
+ }
|
|
|
+
|
|
|
+ // Verificar que tenga al menos 7 dígitos
|
|
|
+ const digits = value.replace(/\D/g, '')
|
|
|
+ if (digits.length > 0 && digits.length < 7) {
|
|
|
+ return "El número debe tener al menos 7 dígitos"
|
|
|
+ }
|
|
|
+
|
|
|
+ if (value.length > 20) {
|
|
|
+ return "Máximo 20 caracteres"
|
|
|
+ }
|
|
|
+
|
|
|
+ return undefined
|
|
|
+ }
|
|
|
+
|
|
|
+ // Validación de contacto de emergencia
|
|
|
+ const validateEmergencyContact = (value: string): string | undefined => {
|
|
|
+ if (!value) return undefined
|
|
|
+
|
|
|
+ if (value.length > 100) {
|
|
|
+ return "Máximo 100 caracteres"
|
|
|
+ }
|
|
|
+
|
|
|
+ return undefined
|
|
|
+ }
|
|
|
+
|
|
|
+ // Validación de dirección
|
|
|
+ const validateAddress = (value: string): string | undefined => {
|
|
|
+ if (!value) return undefined
|
|
|
+
|
|
|
+ if (value.length > 200) {
|
|
|
+ return "Máximo 200 caracteres"
|
|
|
+ }
|
|
|
+
|
|
|
+ return undefined
|
|
|
+ }
|
|
|
+
|
|
|
+ // Validación de campos de texto largos
|
|
|
+ const validateTextArea = (value: string, maxLength: number): string | undefined => {
|
|
|
+ if (!value) return undefined
|
|
|
+
|
|
|
+ if (value.length > maxLength) {
|
|
|
+ return `Máximo ${maxLength} caracteres`
|
|
|
+ }
|
|
|
+
|
|
|
+ return undefined
|
|
|
+ }
|
|
|
+
|
|
|
+ const handleValidatedChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
|
|
|
+ const { name, value } = e.target
|
|
|
+ let error: string | undefined
|
|
|
+
|
|
|
+ // Validar según el campo
|
|
|
+ switch (name) {
|
|
|
+ case 'phone':
|
|
|
+ error = validatePhone(value)
|
|
|
+ break
|
|
|
+ case 'emergencyContact':
|
|
|
+ error = validateEmergencyContact(value)
|
|
|
+ break
|
|
|
+ case 'address':
|
|
|
+ error = validateAddress(value)
|
|
|
+ break
|
|
|
+ case 'medicalHistory':
|
|
|
+ error = validateTextArea(value, 1000)
|
|
|
+ break
|
|
|
+ case 'allergies':
|
|
|
+ error = validateTextArea(value, 500)
|
|
|
+ break
|
|
|
+ case 'currentMedications':
|
|
|
+ error = validateTextArea(value, 1000)
|
|
|
+ break
|
|
|
+ }
|
|
|
+
|
|
|
+ // Actualizar errores
|
|
|
+ setErrors(prev => ({
|
|
|
+ ...prev,
|
|
|
+ [name]: error
|
|
|
+ }))
|
|
|
+
|
|
|
+ // Solo llamar onChange si no hay error crítico (permitir seguir escribiendo)
|
|
|
+ onInputChange(e)
|
|
|
+ }
|
|
|
+
|
|
|
return (
|
|
|
<Card>
|
|
|
<CardHeader>
|
|
|
@@ -46,10 +198,17 @@ export default function MedicalInfoSection({
|
|
|
id="phone"
|
|
|
name="phone"
|
|
|
type="tel"
|
|
|
- placeholder="+1 (555) 123-4567"
|
|
|
+ placeholder="096 028 3974"
|
|
|
value={formData.phone || ""}
|
|
|
- onChange={onInputChange}
|
|
|
+ onChange={handleValidatedChange}
|
|
|
+ className={errors.phone ? "border-red-500" : ""}
|
|
|
/>
|
|
|
+ {errors.phone && (
|
|
|
+ <div className="flex items-start gap-1 text-xs text-red-600">
|
|
|
+ <AlertCircle className="w-3 h-3 mt-0.5 flex-shrink-0" />
|
|
|
+ <span>{errors.phone}</span>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
</div>
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
@@ -58,7 +217,7 @@ export default function MedicalInfoSection({
|
|
|
id="gender"
|
|
|
name="gender"
|
|
|
value={formData.gender || ""}
|
|
|
- onChange={onInputChange}
|
|
|
+ onChange={handleValidatedChange}
|
|
|
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"
|
|
|
>
|
|
|
<option value="">Seleccionar...</option>
|
|
|
@@ -76,11 +235,21 @@ export default function MedicalInfoSection({
|
|
|
<Input
|
|
|
id="emergencyContact"
|
|
|
name="emergencyContact"
|
|
|
- type="tel"
|
|
|
+ type="text"
|
|
|
placeholder="Nombre y teléfono"
|
|
|
value={formData.emergencyContact || ""}
|
|
|
- onChange={onInputChange}
|
|
|
+ onChange={handleValidatedChange}
|
|
|
+ className={errors.emergencyContact ? "border-red-500" : ""}
|
|
|
/>
|
|
|
+ {errors.emergencyContact && (
|
|
|
+ <div className="flex items-start gap-1 text-xs text-red-600">
|
|
|
+ <AlertCircle className="w-3 h-3 mt-0.5 flex-shrink-0" />
|
|
|
+ <span>{errors.emergencyContact}</span>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ <p className="text-xs text-muted-foreground">
|
|
|
+ Ejemplo: María López - +57 300 123 4567
|
|
|
+ </p>
|
|
|
</div>
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
@@ -91,8 +260,15 @@ export default function MedicalInfoSection({
|
|
|
type="text"
|
|
|
placeholder="Calle, número, ciudad, código postal"
|
|
|
value={formData.address || ""}
|
|
|
- onChange={onInputChange}
|
|
|
+ onChange={handleValidatedChange}
|
|
|
+ className={errors.address ? "border-red-500" : ""}
|
|
|
/>
|
|
|
+ {errors.address && (
|
|
|
+ <div className="flex items-start gap-1 text-xs text-red-600">
|
|
|
+ <AlertCircle className="w-3 h-3 mt-0.5 flex-shrink-0" />
|
|
|
+ <span>{errors.address}</span>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -108,13 +284,24 @@ export default function MedicalInfoSection({
|
|
|
name="medicalHistory"
|
|
|
placeholder="Enfermedades previas, cirugías, condiciones crónicas, etc."
|
|
|
value={formData.medicalHistory || ""}
|
|
|
- onChange={onInputChange}
|
|
|
+ onChange={handleValidatedChange}
|
|
|
rows={3}
|
|
|
- className="resize-none"
|
|
|
+ className={`resize-none ${errors.medicalHistory ? "border-red-500" : ""}`}
|
|
|
/>
|
|
|
- <p className="text-xs text-muted-foreground">
|
|
|
- Ejemplo: Diabetes tipo 2, hipertensión, apendicectomía en 2020
|
|
|
- </p>
|
|
|
+ <div className="flex justify-between items-start">
|
|
|
+ <p className="text-xs text-muted-foreground">
|
|
|
+ Ejemplo: Diabetes tipo 2, hipertensión, apendicectomía en 2020
|
|
|
+ </p>
|
|
|
+ <span className="text-xs text-muted-foreground">
|
|
|
+ {formData.medicalHistory?.length || 0}/1000
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ {errors.medicalHistory && (
|
|
|
+ <div className="flex items-start gap-1 text-xs text-red-600">
|
|
|
+ <AlertCircle className="w-3 h-3 mt-0.5 flex-shrink-0" />
|
|
|
+ <span>{errors.medicalHistory}</span>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
</div>
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
@@ -124,13 +311,24 @@ export default function MedicalInfoSection({
|
|
|
name="allergies"
|
|
|
placeholder="Alergias a medicamentos, alimentos, o sustancias"
|
|
|
value={formData.allergies || ""}
|
|
|
- onChange={onInputChange}
|
|
|
+ onChange={handleValidatedChange}
|
|
|
rows={2}
|
|
|
- className="resize-none"
|
|
|
+ className={`resize-none ${errors.allergies ? "border-red-500" : ""}`}
|
|
|
/>
|
|
|
- <p className="text-xs text-muted-foreground">
|
|
|
- Ejemplo: Penicilina, mariscos, polen
|
|
|
- </p>
|
|
|
+ <div className="flex justify-between items-start">
|
|
|
+ <p className="text-xs text-muted-foreground">
|
|
|
+ Ejemplo: Penicilina, mariscos, polen
|
|
|
+ </p>
|
|
|
+ <span className="text-xs text-muted-foreground">
|
|
|
+ {formData.allergies?.length || 0}/500
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ {errors.allergies && (
|
|
|
+ <div className="flex items-start gap-1 text-xs text-red-600">
|
|
|
+ <AlertCircle className="w-3 h-3 mt-0.5 flex-shrink-0" />
|
|
|
+ <span>{errors.allergies}</span>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
</div>
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
@@ -140,13 +338,24 @@ export default function MedicalInfoSection({
|
|
|
name="currentMedications"
|
|
|
placeholder="Medicamentos que estás tomando actualmente"
|
|
|
value={formData.currentMedications || ""}
|
|
|
- onChange={onInputChange}
|
|
|
+ onChange={handleValidatedChange}
|
|
|
rows={3}
|
|
|
- className="resize-none"
|
|
|
+ className={`resize-none ${errors.currentMedications ? "border-red-500" : ""}`}
|
|
|
/>
|
|
|
- <p className="text-xs text-muted-foreground">
|
|
|
- Ejemplo: Metformina 850mg (2 veces al día), Losartán 50mg (1 vez al día)
|
|
|
- </p>
|
|
|
+ <div className="flex justify-between items-start">
|
|
|
+ <p className="text-xs text-muted-foreground">
|
|
|
+ Ejemplo: Metformina 850mg (2 veces al día), Losartán 50mg (1 vez al día)
|
|
|
+ </p>
|
|
|
+ <span className="text-xs text-muted-foreground">
|
|
|
+ {formData.currentMedications?.length || 0}/1000
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ {errors.currentMedications && (
|
|
|
+ <div className="flex items-start gap-1 text-xs text-red-600">
|
|
|
+ <AlertCircle className="w-3 h-3 mt-0.5 flex-shrink-0" />
|
|
|
+ <span>{errors.currentMedications}</span>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
</div>
|
|
|
</div>
|
|
|
</CardContent>
|