nusaibah0110's picture
Update application with new features and components
1c68fe6
import React, { useState } from 'react';
import { User, Calendar, Stethoscope, AlertTriangle, Save, ChevronRight, ArrowLeft } from 'lucide-react';
import { sessionStore } from '../store/sessionStore';
import { savePatientRecord } from '../store/patientIdStore';
interface PatientHistoryFormProps {
onContinue: () => void;
onBack: () => void;
patientID?: string | undefined;
autoGeneratedPatientId?: string | undefined;
}
export function PatientHistoryForm({
onContinue,
onBack,
patientID,
autoGeneratedPatientId
}: PatientHistoryFormProps) {
const [formData, setFormData] = useState({
// Patient Profile
name: '',
age: '',
bloodGroup: '',
parity: '',
pregnancyStatus: '',
gestationalAgeWeeks: '',
monthsSinceLastDelivery: '',
monthsSinceAbortion: '',
menstrualStatus: '',
sexualHistory: '',
hpvStatus: '',
hpvVaccination: '',
patientProfileNotes: '',
// Symptoms
postCoitalBleeding: false,
interMenstrualBleeding: false,
persistentDischarge: false,
symptomsNotes: '',
// Screening
papSmearResult: '',
hpvDnaTypes: '',
pastProcedures: {
biopsy: false,
leep: false,
cryotherapy: false,
none: false
},
screeningNotes: '',
// Risk Factors
smoking: '',
immunosuppression: {
hiv: false,
steroids: false,
none: false
},
riskFactorsNotes: ''
});
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
const { name, value, type } = e.target as any;
// Handle checkboxes separately
if (type === 'checkbox') {
const checked = (e.target as HTMLInputElement).checked;
setFormData(prev => ({
...prev,
[name]: checked
}));
} else if (name === 'pregnancyStatus') {
setFormData(prev => ({
...prev,
pregnancyStatus: value,
gestationalAgeWeeks: value === 'Pregnant' ? prev.gestationalAgeWeeks : '',
monthsSinceLastDelivery: value === 'Postpartum' ? prev.monthsSinceLastDelivery : '',
monthsSinceAbortion: value === 'Post-abortion' ? prev.monthsSinceAbortion : ''
}));
} else {
// Handle text inputs, textareas, and selects
setFormData(prev => ({
...prev,
[name]: value
}));
}
};
const handleNestedCheckboxChange = (category: 'pastProcedures' | 'immunosuppression', field: string) => {
if (category === 'pastProcedures') {
setFormData(prev => ({
...prev,
pastProcedures: {
...prev.pastProcedures,
[field]: !prev.pastProcedures[field as keyof typeof prev.pastProcedures]
}
}));
return;
}
// category === 'immunosuppression'
setFormData(prev => ({
...prev,
immunosuppression: {
...prev.immunosuppression,
[field]: !prev.immunosuppression[field as keyof typeof prev.immunosuppression]
}
}));
};
const handleSave = () => {
console.log('Saving patient history:', formData);
// Save patient record if auto-generated ID is available
if (autoGeneratedPatientId && formData.name) {
const examDate = new Date().toISOString().split('T')[0];
savePatientRecord({
id: autoGeneratedPatientId,
name: formData.name,
examDate: examDate
});
// Also update sessionStore with patient info
sessionStore.merge({
patientInfo: {
id: autoGeneratedPatientId,
name: formData.name,
examDate: examDate
}
});
}
};
const handleSaveAndContinue = () => {
handleSave();
onContinue();
};
// Load previously saved patient history from sessionStore on mount
React.useEffect(() => {
const session = sessionStore.get();
if (session.patientHistory) {
setFormData(prev => ({
...prev,
...session.patientHistory
}));
}
}, []);
// Save formData to sessionStore on every change
React.useEffect(() => {
sessionStore.merge({ patientHistory: formData as any });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [JSON.stringify(formData)]);
return <div className="w-full max-w-8xl mx-auto p-4 md:p-6 lg:p-10">
<div className="mb-4 md:mb-6 lg:mb-8 flex flex-col md:flex-row md:items-center md:justify-between gap-3 md:gap-0">
<h2 className="text-xl md:text-2xl lg:text-3xl font-bold text-[#0A2540]">
Patient Information & Clinical History
</h2>
<div className="flex items-center gap-2 text-xs md:text-sm text-gray-500 bg-white px-3 md:px-4 py-2 rounded-lg shadow-sm whitespace-nowrap">
<span>Patient ID:</span>
<span className="font-mono font-bold text-[#0A2540]">
{autoGeneratedPatientId || patientID || 'New - unsaved'}
</span>
</div>
</div>
<form className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 md:gap-6 lg:gap-8">
{/* SECTION 1: Patient Profile */}
<div className="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden flex flex-col h-full">
<div className="bg-blue-50/50 p-3 md:p-4 border-b border-blue-100 flex items-center gap-2 md:gap-3">
<div className="w-8 h-8 md:w-10 md:h-10 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center flex-shrink-0">
<User className="w-4 h-4 md:w-5 md:h-5" />
</div>
<h3 className="font-bold text-sm md:text-base text-[#0A2540]">Patient Name</h3>
<input type="text" name="name" value={formData.name} onChange={handleInputChange} className="w-full px-3 md:px-4 py-2 md:py-3 text-sm md:text-base bg-gray-50 border border-gray-200 rounded-lg focus:ring-2 focus:ring-[#05998c] focus:border-transparent outline-none transition-all" placeholder="Full name" />
</div>
<div className="p-4 md:p-6 lg:p-8 space-y-4 md:space-y-5 lg:space-y-6 flex-1">
<div className="grid grid-cols-3 gap-2 md:gap-3 lg:gap-4">
<div>
<label className="block text-xs font-semibold text-gray-500 uppercase mb-1">
Age
</label>
<input value={formData.age} onChange={handleInputChange} type="number" name="age" className="w-full px-3 md:px-4 py-2 md:py-3 text-sm md:text-base bg-gray-50 border border-gray-200 rounded-lg focus:ring-2 focus:ring-[#4ECDC4] focus:border-transparent outline-none transition-all" placeholder="Yrs" />
</div>
<div>
<label className="block text-xs font-semibold text-gray-500 uppercase mb-1">
Blood Group
</label>
<select value={formData.bloodGroup} onChange={handleInputChange} name="bloodGroup" className="w-full px-4 py-3 text-base bg-gray-50 border border-gray-200 rounded-lg focus:ring-2 focus:ring-[#4ECDC4] focus:border-transparent outline-none transition-all">
<option value="">Select</option>
<option value="A+">A+</option>
<option value="A-">A-</option>
<option value="B+">B+</option>
<option value="B-">B-</option>
<option value="AB+">AB+</option>
<option value="AB-">AB-</option>
<option value="O+">O+</option>
<option value="O-">O-</option>
</select>
</div>
<div>
<label className="block text-xs font-semibold text-gray-500 uppercase mb-1">
Parity
</label>
<input value={formData.parity} onChange={handleInputChange} type="text" name="parity" className="w-full px-4 py-3 text-base bg-gray-50 border border-gray-200 rounded-lg focus:ring-2 focus:ring-[#4ECDC4] focus:border-transparent outline-none transition-all" placeholder="G P A" />
</div>
</div>
<div>
<label className="block text-sm font-semibold text-gray-500 uppercase mb-2">Pregnancy / Recent Pregnancy Status</label>
<div className="space-y-2">
{['Pregnant', 'Not pregnant', 'Postpartum', 'Post-abortion', 'Unknown'].map(status => (
<label key={status} className="flex items-center gap-3 p-2 rounded-lg hover:bg-gray-50 cursor-pointer border border-transparent hover:border-gray-100 transition-all">
<input type="radio" name="pregnancyStatus" value={status} checked={formData.pregnancyStatus === status} onChange={handleInputChange} className="w-4 h-4 text-[#4ECDC4] focus:ring-[#4ECDC4]" />
<span className="text-base text-gray-700">{status}</span>
</label>
))}
</div>
{formData.pregnancyStatus === 'Pregnant' && (
<div className="mt-3">
<label className="block text-xs font-semibold text-gray-500 uppercase mb-1">Gestational age (weeks)</label>
<input
type="number"
name="gestationalAgeWeeks"
value={formData.gestationalAgeWeeks}
onChange={handleInputChange}
className="w-full px-3 py-2 bg-gray-50 border border-gray-200 rounded-lg focus:ring-2 focus:ring-[#4ECDC4] focus:border-transparent outline-none transition-all text-sm"
placeholder="Enter weeks"
/>
</div>
)}
{formData.pregnancyStatus === 'Postpartum' && (
<div className="mt-3">
<label className="block text-xs font-semibold text-gray-500 uppercase mb-1">Months since last delivery</label>
<input
type="number"
name="monthsSinceLastDelivery"
value={formData.monthsSinceLastDelivery}
onChange={handleInputChange}
className="w-full px-3 py-2 bg-gray-50 border border-gray-200 rounded-lg focus:ring-2 focus:ring-[#4ECDC4] focus:border-transparent outline-none transition-all text-sm"
placeholder="Enter months"
/>
</div>
)}
{formData.pregnancyStatus === 'Post-abortion' && (
<div className="mt-3">
<label className="block text-xs font-semibold text-gray-500 uppercase mb-1">Months since abortion</label>
<input
type="number"
name="monthsSinceAbortion"
value={formData.monthsSinceAbortion}
onChange={handleInputChange}
className="w-full px-3 py-2 bg-gray-50 border border-gray-200 rounded-lg focus:ring-2 focus:ring-[#4ECDC4] focus:border-transparent outline-none transition-all text-sm"
placeholder="Enter months"
/>
</div>
)}
</div>
<div>
<label className="block text-sm font-semibold text-gray-500 uppercase mb-2">Menstrual Status</label>
<select
name="menstrualStatus"
value={formData.menstrualStatus}
onChange={handleInputChange}
className="w-full px-3 py-2 bg-gray-50 border border-gray-200 rounded-lg focus:ring-2 focus:ring-[#4ECDC4] focus:border-transparent outline-none transition-all text-sm"
>
<option value="">Select</option>
<option value="Premenarchal">Premenarchal</option>
<option value="Reproductive age">Reproductive age</option>
<option value="Currently menstruating">Currently menstruating</option>
<option value="Perimenopausal">Perimenopausal</option>
<option value="Postmenopausal">Postmenopausal</option>
</select>
</div>
<div className="pt-4 border-t border-gray-100">
<label className="block text-sm font-semibold text-gray-500 uppercase mb-2">Additional Notes</label>
<textarea name="patientProfileNotes" value={formData.patientProfileNotes} onChange={handleInputChange} rows={3} className="w-full px-4 py-3 text-base bg-gray-50 border border-gray-200 rounded-lg focus:ring-2 focus:ring-[#4ECDC4] focus:border-transparent outline-none transition-all resize-none" placeholder="Add any additional notes about patient profile..." />
</div>
</div>
</div>
{/* SECTION 2: Presenting Symptoms */}
<div className="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden flex flex-col h-full">
<div className="bg-indigo-50/50 p-4 border-b border-indigo-100 flex items-center gap-3">
<div className="w-10 h-10 rounded-full bg-indigo-100 text-indigo-600 flex items-center justify-center">
<Calendar className="w-5 h-5" />
</div>
<h3 className="font-bold text-[#0A2540]">Presenting Symptoms</h3>
</div>
<div className="p-8 space-y-4 flex-1">
<div>
<label className="block text-sm font-semibold text-gray-500 uppercase mb-2">Sexual History</label>
<input
type="text"
name="sexualHistory"
value={formData.sexualHistory}
onChange={handleInputChange}
className="w-full px-3 py-2 bg-gray-50 border border-gray-200 rounded-lg focus:ring-2 focus:ring-[#4ECDC4] focus:border-transparent outline-none transition-all text-sm"
placeholder="e.g. Sexually active, Duration of relationship, etc."
/>
</div>
<div>
<label className="block text-sm font-semibold text-gray-500 uppercase mb-2">HPV Status</label>
<div className="flex flex-wrap gap-2">
{['Positive', 'Negative', 'Unknown'].map(status => (
<label key={status} className="flex-1 min-w-[80px] text-center cursor-pointer">
<input type="radio" name="hpvStatus" value={status} checked={formData.hpvStatus === status} onChange={handleInputChange} className="peer sr-only" />
<div className="px-3 py-1.5 rounded-md text-sm font-medium bg-gray-50 text-gray-600 border border-gray-200 peer-checked:bg-[#0A2540] peer-checked:text-white peer-checked:border-[#0A2540] transition-all">
{status}
</div>
</label>
))}
</div>
</div>
<div>
<label className="block text-sm font-semibold text-gray-500 uppercase mb-2">HPV Vaccination</label>
<div className="flex gap-4">
{['Yes', 'No', 'Unknown'].map(opt => (
<label key={opt} className="flex items-center gap-2 cursor-pointer">
<input type="radio" name="hpvVaccination" value={opt} checked={formData.hpvVaccination === opt} onChange={handleInputChange} className="w-4 h-4 text-[#4ECDC4] focus:ring-[#4ECDC4]" />
<span className="text-base text-gray-700">{opt}</span>
</label>
))}
</div>
</div>
<label className="flex items-start gap-3 p-4 rounded-lg border border-gray-100 hover:border-[#4ECDC4]/50 hover:bg-teal-50/10 cursor-pointer transition-all">
<div className="flex items-center h-5">
<input
type="checkbox"
name="postCoitalBleeding"
checked={formData.postCoitalBleeding}
onChange={handleInputChange}
className="w-5 h-5 rounded text-[#4ECDC4] focus:ring-[#4ECDC4] border-gray-300" />
</div>
<div>
<span className="block text-base font-medium text-gray-900">
Post-coital bleeding
</span>
<span className="block text-sm text-gray-500 mt-0.5">
Bleeding after intercourse
</span>
</div>
</label>
<label className="flex items-start gap-3 p-4 rounded-lg border border-gray-100 hover:border-[#4ECDC4]/50 hover:bg-teal-50/10 cursor-pointer transition-all">
<div className="flex items-center h-5">
<input
type="checkbox"
name="interMenstrualBleeding"
checked={formData.interMenstrualBleeding}
onChange={handleInputChange}
className="w-5 h-5 rounded text-[#4ECDC4] focus:ring-[#4ECDC4] border-gray-300" />
</div>
<div>
<span className="block text-base font-medium text-gray-900">
Inter-menstrual / Post-menopausal
</span>
<span className="block text-sm text-gray-500 mt-0.5">
Irregular bleeding patterns
</span>
</div>
</label>
<label className="flex items-start gap-3 p-3 rounded-lg border border-gray-100 hover:border-[#4ECDC4]/50 hover:bg-teal-50/10 cursor-pointer transition-all">
<div className="flex items-center h-5">
<input
type="checkbox"
name="persistentDischarge"
checked={formData.persistentDischarge}
onChange={handleInputChange}
className="w-5 h-5 rounded text-[#4ECDC4] focus:ring-[#4ECDC4] border-gray-300" />
</div>
<div>
<span className="block text-sm font-medium text-gray-900">
Persistent discharge
</span>
<span className="block text-xs text-gray-500 mt-0.5">
Unusual color or odor
</span>
</div>
</label>
<div className="pt-4 border-t border-gray-100">
<label className="block text-xs font-semibold text-gray-500 uppercase mb-2">
Additional Notes
</label>
<textarea name="symptomsNotes" value={formData.symptomsNotes} onChange={handleInputChange} rows={3} className="w-full px-3 py-2 bg-gray-50 border border-gray-200 rounded-lg focus:ring-2 focus:ring-[#4ECDC4] focus:border-transparent outline-none transition-all resize-none text-sm" placeholder="Add any additional notes about symptoms..." />
</div>
</div>
</div>
{/* SECTION 3: Screening History */}
<div className="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden flex flex-col h-full">
<div className="bg-teal-50/50 p-4 border-b border-teal-100 flex items-center gap-3">
<div className="w-10 h-10 rounded-full bg-teal-100 text-teal-700 flex items-center justify-center">
<Stethoscope className="w-5 h-5" />
</div>
<h3 className="font-bold text-[#0A2540]">Screening History</h3>
</div>
<div className="p-8 space-y-6 flex-1">
<div>
<label className="block text-xs font-semibold text-gray-500 uppercase mb-2">Pap Smear Result</label>
<div className="space-y-2">
{['ASC-US', 'LSIL', 'HSIL', 'Normal', 'Not Done'].map(result => (
<label key={result} className="flex items-center justify-between p-2 rounded-lg hover:bg-gray-50 cursor-pointer border border-transparent hover:border-gray-100 transition-all group">
<span className="text-sm text-gray-700 group-hover:text-[#0A2540]">{result}</span>
<input type="radio" name="papSmearResult" value={result} checked={formData.papSmearResult === result} onChange={handleInputChange} className="w-4 h-4 text-[#4ECDC4] focus:ring-[#4ECDC4]" />
</label>
))}
</div>
</div>
<div>
<label className="block text-xs font-semibold text-gray-500 uppercase mb-1">HPV DNA (High-risk)</label>
<input type="text" name="hpvDnaTypes" value={formData.hpvDnaTypes} onChange={handleInputChange} className="w-full px-3 py-2 bg-gray-50 border border-gray-200 rounded-lg focus:ring-2 focus:ring-[#4ECDC4] focus:border-transparent outline-none transition-all" placeholder="e.g. Type 16, 18" />
</div>
<div>
<label className="block text-xs font-semibold text-gray-500 uppercase mb-2">
Past Procedures
</label>
<div className="grid grid-cols-2 gap-2">
{['Biopsy', 'LEEP', 'Cryotherapy', 'None'].map(proc => <label key={proc} className="flex items-center gap-2 cursor-pointer">
<input type="checkbox" checked={formData.pastProcedures[proc.toLowerCase() as keyof typeof formData.pastProcedures]} onChange={() => handleNestedCheckboxChange('pastProcedures', proc.toLowerCase())} className="w-4 h-4 rounded text-[#4ECDC4] focus:ring-[#4ECDC4] border-gray-300" />
<span className="text-sm text-gray-700">{proc}</span>
</label>)}
</div>
</div>
<div className="pt-4 border-t border-gray-100">
<label className="block text-xs font-semibold text-gray-500 uppercase mb-2">
Additional Notes
</label>
<textarea name="screeningNotes" value={formData.screeningNotes} onChange={handleInputChange} rows={3} className="w-full px-3 py-2 bg-gray-50 border border-gray-200 rounded-lg focus:ring-2 focus:ring-[#4ECDC4] focus:border-transparent outline-none transition-all resize-none text-sm" placeholder="Add any additional notes about screening history..." />
</div>
</div>
</div>
{/* SECTION 4: Risk Factors */}
<div className="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden flex flex-col h-full">
<div className="bg-rose-50/50 p-4 border-b border-rose-100 flex items-center gap-3">
<div className="w-10 h-10 rounded-full bg-rose-100 text-rose-600 flex items-center justify-center">
<AlertTriangle className="w-5 h-5" />
</div>
<h3 className="font-bold text-[#0A2540]">Risk Factors</h3>
</div>
<div className="p-8 space-y-6 flex-1">
<div>
<label className="block text-xs font-semibold text-gray-500 uppercase mb-3">Smoking History</label>
<div className="flex gap-2">
<label className="flex-1 cursor-pointer">
<input type="radio" name="smoking" value="Yes" checked={formData.smoking === 'Yes'} onChange={handleInputChange} className="peer sr-only" />
<div className="flex items-center justify-center gap-2 px-4 py-3 rounded-lg border border-gray-200 text-gray-600 hover:bg-gray-50 peer-checked:bg-rose-50 peer-checked:border-rose-200 peer-checked:text-rose-700 transition-all">
<span className="font-medium">Yes</span>
</div>
</label>
<label className="flex-1 cursor-pointer">
<input type="radio" name="smoking" value="No" checked={formData.smoking === 'No'} onChange={handleInputChange} className="peer sr-only" />
<div className="flex items-center justify-center gap-2 px-4 py-3 rounded-lg border border-gray-200 text-gray-600 hover:bg-gray-50 peer-checked:bg-green-50 peer-checked:border-green-200 peer-checked:text-green-700 transition-all">
<span className="font-medium">No</span>
</div>
</label>
</div>
</div>
<div>
<label className="block text-xs font-semibold text-gray-500 uppercase mb-3">
Immunosuppression
</label>
<div className="space-y-3">
<label className="flex items-center p-3 rounded-lg border border-gray-200 hover:border-gray-300 cursor-pointer transition-all bg-gray-50/50">
<input type="checkbox" checked={formData.immunosuppression.hiv} onChange={() => handleNestedCheckboxChange('immunosuppression', 'hiv')} className="w-5 h-5 rounded text-[#4ECDC4] focus:ring-[#4ECDC4] border-gray-300" />
<span className="ml-3 text-sm font-medium text-gray-700">
HIV Positive
</span>
</label>
<label className="flex items-center p-3 rounded-lg border border-gray-200 hover:border-gray-300 cursor-pointer transition-all bg-gray-50/50">
<input type="checkbox" checked={formData.immunosuppression.steroids} onChange={() => handleNestedCheckboxChange('immunosuppression', 'steroids')} className="w-5 h-5 rounded text-[#4ECDC4] focus:ring-[#4ECDC4] border-gray-300" />
<span className="ml-3 text-sm font-medium text-gray-700">
Chronic Steroid Use
</span>
</label>
<label className="flex items-center p-3 rounded-lg border border-gray-200 hover:border-gray-300 cursor-pointer transition-all bg-gray-50/50">
<input type="checkbox" checked={formData.immunosuppression.none} onChange={() => handleNestedCheckboxChange('immunosuppression', 'none')} className="w-5 h-5 rounded text-[#4ECDC4] focus:ring-[#4ECDC4] border-gray-300" />
<span className="ml-3 text-sm font-medium text-gray-700">
None known
</span>
</label>
</div>
</div>
<div className="pt-4 border-t border-gray-100">
<label className="block text-xs font-semibold text-gray-500 uppercase mb-2">
Additional Notes
</label>
<textarea name="riskFactorsNotes" value={formData.riskFactorsNotes} onChange={handleInputChange} rows={3} className="w-full px-3 py-2 bg-gray-50 border border-gray-200 rounded-lg focus:ring-2 focus:ring-[#4ECDC4] focus:border-transparent outline-none transition-all resize-none text-sm" placeholder="Add any additional notes about risk factors..." />
</div>
</div>
</div>
</form>
<div className="mt-6 md:mt-8 flex flex-col md:flex-row md:justify-between gap-3 md:gap-4">
<button onClick={onBack} className="px-4 md:px-6 py-2 md:py-3 rounded-xl text-gray-600 font-medium hover:bg-gray-100 transition-colors text-sm md:text-base flex items-center justify-center md:justify-start gap-2">
<ArrowLeft className="w-4 h-4 md:w-5 md:h-5" />
<span className="hidden md:inline">Back to Registry</span>
<span className="inline md:hidden">Back</span>
</button>
<div className="flex flex-col md:flex-row gap-3 md:gap-4">
<button onClick={handleSave} className="px-4 md:px-6 py-2 md:py-3 rounded-xl text-gray-600 font-medium hover:bg-gray-100 transition-colors text-sm md:text-base">
Save Draft
</button>
<button onClick={handleSaveAndContinue} className="px-6 md:px-8 py-2 md:py-3 rounded-xl bg-[#05998c] text-white font-bold shadow-lg shadow-teal-500/20 hover:bg-[#047569] hover:shadow-teal-500/30 transition-all flex items-center justify-center gap-2 text-sm md:text-base">
<Save className="w-4 h-4 md:w-5 md:h-5" />
Save & Continue
<ChevronRight className="w-4 h-4 md:w-5 md:h-5" />
</button>
</div>
</div>
</div>;
}