Pathora_Colposcopy_Assistant / src /pages /ExaminationRecordsPage.tsx
nusaibah0110's picture
Initial deployment of Pathora Colposcopy Assistant
bab7e89
import { useState } from 'react';
import { Calendar, User, Eye, FileText, Clock, Camera, ArrowLeft, Search, Filter } from 'lucide-react';
// Types for examination records
interface ExamRecord {
id: string;
date: Date;
indication: string;
impression: string;
tzType: string;
biopsy: 'Taken' | 'Not Taken';
finalStatus: string;
performedBy?: string;
scjVisibility: 'Fully' | 'Partially' | 'Not visible';
media: MediaItem[];
findings: Findings;
outcome: Outcome;
}
interface MediaItem {
id: string;
step: string;
timestamp: Date;
thumbnail: string;
type: 'image' | 'video';
}
interface Findings {
acetowhite: string;
borders: string;
vascularPattern: string;
lugolsUptake: string;
}
interface Outcome {
impression: string;
biopsyTaken: boolean;
plan: string;
}
// Dummy data
const dummyExams: ExamRecord[] = [
{
id: 'COLPO-2025-001',
date: new Date('2025-01-10'),
indication: 'VIA positive',
impression: 'Low-grade lesion',
tzType: 'Type 2',
biopsy: 'Taken',
finalStatus: 'Follow-up advised',
performedBy: 'Dr. Sarah Johnson',
scjVisibility: 'Fully',
media: [
{ id: '1', step: 'Native', timestamp: new Date('2025-01-10T09:00:00'), thumbnail: '/C87Aceto_(1).jpg', type: 'image' },
{ id: '2', step: 'Acetic Acid (1 min)', timestamp: new Date('2025-01-10T09:01:00'), thumbnail: '/C87Aceto_(1).jpg', type: 'image' },
{ id: '3', step: 'Acetic Acid (3 min)', timestamp: new Date('2025-01-10T09:03:00'), thumbnail: '/C87Aceto_(1).jpg', type: 'image' },
{ id: '4', step: 'Green Filter', timestamp: new Date('2025-01-10T09:05:00'), thumbnail: '/greenC87Aceto_(1).jpg', type: 'image' },
{ id: '5', step: 'Lugol\'s Iodine', timestamp: new Date('2025-01-10T09:07:00'), thumbnail: '/C87Aceto_(1).jpg', type: 'image' },
{ id: '6', step: 'Biopsy Marking', timestamp: new Date('2025-01-10T09:10:00'), thumbnail: '/C87Aceto_(1).jpg', type: 'image' }
],
findings: {
acetowhite: 'Thin, rapidly fading',
borders: 'Irregular',
vascularPattern: 'Fine punctation',
lugolsUptake: 'Partial'
},
outcome: {
impression: 'Low-grade squamous intraepithelial lesion (LSIL)',
biopsyTaken: true,
plan: 'Follow-up in 6 months with repeat colposcopy'
}
},
{
id: 'COLPO-2025-002',
date: new Date('2025-02-15'),
indication: 'Follow-up',
impression: 'Normal',
tzType: 'Type 1',
biopsy: 'Not Taken',
finalStatus: 'Discharged',
performedBy: 'Dr. Sarah Johnson',
scjVisibility: 'Fully',
media: [
{ id: '7', step: 'Native', timestamp: new Date('2025-02-15T10:30:00'), thumbnail: '/C87Aceto_(1).jpg', type: 'image' },
{ id: '8', step: 'Acetic Acid (1 min)', timestamp: new Date('2025-02-15T10:31:00'), thumbnail: '/C87Aceto_(1).jpg', type: 'image' },
{ id: '9', step: 'Acetic Acid (3 min)', timestamp: new Date('2025-02-15T10:33:00'), thumbnail: '/C87Aceto_(1).jpg', type: 'image' }
],
findings: {
acetowhite: 'None',
borders: 'Regular',
vascularPattern: 'Normal',
lugolsUptake: 'Complete'
},
outcome: {
impression: 'Normal colposcopy',
biopsyTaken: false,
plan: 'Return to routine screening'
}
},
{
id: 'COLPO-2025-003',
date: new Date('2025-03-20'),
indication: 'Abnormal cytology',
impression: 'High-grade lesion',
tzType: 'Type 3',
biopsy: 'Taken',
finalStatus: 'Referred to oncology',
performedBy: 'Dr. Sarah Johnson',
scjVisibility: 'Partially',
media: [
{ id: '10', step: 'Native', timestamp: new Date('2025-03-20T11:00:00'), thumbnail: '/C87Aceto_(1).jpg', type: 'image' },
{ id: '11', step: 'Acetic Acid (1 min)', timestamp: new Date('2025-03-20T11:01:00'), thumbnail: '/C87Aceto_(1).jpg', type: 'image' },
{ id: '12', step: 'Acetic Acid (3 min)', timestamp: new Date('2025-03-20T11:03:00'), thumbnail: '/C87Aceto_(1).jpg', type: 'image' },
{ id: '13', step: 'Green Filter', timestamp: new Date('2025-03-20T11:05:00'), thumbnail: '/greenC87Aceto_(1).jpg', type: 'image' },
{ id: '14', step: 'Lugol\'s Iodine', timestamp: new Date('2025-03-20T11:07:00'), thumbnail: '/C87Aceto_(1).jpg', type: 'image' },
{ id: '15', step: 'Biopsy Marking', timestamp: new Date('2025-03-20T11:10:00'), thumbnail: '/C87Aceto_(1).jpg', type: 'image' }
],
findings: {
acetowhite: 'Dense, persistent',
borders: 'Irregular, jagged',
vascularPattern: 'Coarse mosaicism',
lugolsUptake: 'Negative staining'
},
outcome: {
impression: 'High-grade squamous intraepithelial lesion (HSIL)',
biopsyTaken: true,
plan: 'Referred to gynecologic oncology for further management'
}
}
];
type Props = {
goBack: () => void;
};
export function ExaminationRecordsPage({ goBack }: Props) {
const [selectedExam, setSelectedExam] = useState<ExamRecord | null>(null);
const [searchTerm, setSearchTerm] = useState('');
const [filterStatus, setFilterStatus] = useState<string>('all');
const filteredExams = dummyExams.filter(exam => {
const matchesSearch = exam.id.toLowerCase().includes(searchTerm.toLowerCase()) ||
exam.indication.toLowerCase().includes(searchTerm.toLowerCase()) ||
exam.impression.toLowerCase().includes(searchTerm.toLowerCase());
const matchesFilter = filterStatus === 'all' || exam.finalStatus.toLowerCase().includes(filterStatus.toLowerCase());
return matchesSearch && matchesFilter;
});
const formatDate = (date: Date) => {
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
});
};
const formatTime = (date: Date) => {
return date.toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit'
});
};
if (selectedExam) {
return (
<div className="w-full bg-white/95 relative">
<div className="relative z-10 py-4 md:py-6 lg:py-8">
<div className="w-full max-w-7xl mx-auto px-4 md:px-6">
{/* Header */}
<div className="mb-6 flex items-center gap-4">
<button
onClick={() => setSelectedExam(null)}
className="p-2 hover:bg-gray-100 rounded-lg transition-colors text-gray-600"
>
<ArrowLeft className="w-5 h-5" />
</button>
<div>
<h1 className="text-2xl md:text-3xl font-bold text-[#0A2540]">
Examination Record: {selectedExam.id}
</h1>
<p className="text-gray-600 mt-1">
{formatDate(selectedExam.date)} • Patient ID: PT-2025-8492
</p>
</div>
</div>
<div className="space-y-6">
{/* Examination Summary */}
<div className="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
<h2 className="text-xl font-bold text-[#0A2540] mb-4 flex items-center gap-2">
<FileText className="w-5 h-5 text-[#05998c]" />
Examination Summary
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<div className="space-y-3">
<div className="flex items-center gap-2 text-sm">
<Calendar className="w-4 h-4 text-gray-500" />
<span className="font-medium text-gray-700">Date & Time:</span>
<span>{formatDate(selectedExam.date)} at {formatTime(selectedExam.date)}</span>
</div>
{selectedExam.performedBy && (
<div className="flex items-center gap-2 text-sm">
<User className="w-4 h-4 text-gray-500" />
<span className="font-medium text-gray-700">Performed by:</span>
<span>{selectedExam.performedBy}</span>
</div>
)}
<div className="flex items-center gap-2 text-sm">
<span className="font-medium text-gray-700">Indication:</span>
<span>{selectedExam.indication}</span>
</div>
</div>
<div className="space-y-3">
<div className="flex items-center gap-2 text-sm">
<span className="font-medium text-gray-700">SCJ Visibility:</span>
<span>{selectedExam.scjVisibility}</span>
</div>
<div className="flex items-center gap-2 text-sm">
<span className="font-medium text-gray-700">TZ Type:</span>
<span>{selectedExam.tzType}</span>
</div>
</div>
<div className="space-y-3">
<div className="flex items-center gap-2 text-sm">
<span className="font-medium text-gray-700">Impression:</span>
<span className="font-semibold text-[#05998c]">{selectedExam.impression}</span>
</div>
<div className="flex items-center gap-2 text-sm">
<span className="font-medium text-gray-700">Biopsy:</span>
<span className={selectedExam.biopsy === 'Taken' ? 'text-red-600 font-semibold' : 'text-green-600'}>
{selectedExam.biopsy}
</span>
</div>
</div>
</div>
</div>
{/* Media Timeline */}
<div className="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
<h2 className="text-xl font-bold text-[#0A2540] mb-4 flex items-center gap-2">
<Camera className="w-5 h-5 text-[#05998c]" />
Media Timeline
</h2>
<div className="space-y-4">
{selectedExam.media.map((item, index) => (
<div key={item.id} className="flex items-center gap-4 p-4 bg-gray-50 rounded-lg">
<div className="relative">
<img
src={item.thumbnail}
alt={item.step}
className="w-16 h-16 object-cover rounded-lg border-2 border-gray-200"
/>
<div className="absolute -top-2 -right-2 bg-[#05998c] text-white text-xs px-2 py-1 rounded-full font-semibold">
{index + 1}
</div>
</div>
<div className="flex-1">
<h3 className="font-semibold text-[#0A2540]">{item.step}</h3>
<div className="flex items-center gap-2 text-sm text-gray-600 mt-1">
<Clock className="w-4 h-4" />
<span>{formatTime(item.timestamp)}</span>
<span className="text-gray-400"></span>
<span className="capitalize">{item.type}</span>
</div>
</div>
<button className="px-4 py-2 bg-[#05998c] text-white rounded-lg hover:bg-[#047569] transition-colors flex items-center gap-2">
<Eye className="w-4 h-4" />
View
</button>
</div>
))}
</div>
</div>
{/* Key Findings */}
<div className="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
<h2 className="text-xl font-bold text-[#0A2540] mb-4">Key Findings</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-3">
<div className="flex justify-between items-center py-2 border-b border-gray-100">
<span className="font-medium text-gray-700">Acetowhite:</span>
<span className="text-gray-900">{selectedExam.findings.acetowhite}</span>
</div>
<div className="flex justify-between items-center py-2 border-b border-gray-100">
<span className="font-medium text-gray-700">Borders:</span>
<span className="text-gray-900">{selectedExam.findings.borders}</span>
</div>
</div>
<div className="space-y-3">
<div className="flex justify-between items-center py-2 border-b border-gray-100">
<span className="font-medium text-gray-700">Vascular Pattern:</span>
<span className="text-gray-900">{selectedExam.findings.vascularPattern}</span>
</div>
<div className="flex justify-between items-center py-2 border-b border-gray-100">
<span className="font-medium text-gray-700">Lugol's Uptake:</span>
<span className="text-gray-900">{selectedExam.findings.lugolsUptake}</span>
</div>
</div>
</div>
</div>
{/* Outcome & Plan */}
<div className="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
<h2 className="text-xl font-bold text-[#0A2540] mb-4">Outcome & Plan</h2>
<div className="space-y-4">
<div>
<h3 className="font-semibold text-gray-700 mb-2">Colposcopic Impression</h3>
<p className="text-[#0A2540] font-medium">{selectedExam.outcome.impression}</p>
</div>
<div>
<h3 className="font-semibold text-gray-700 mb-2">Biopsy Status</h3>
<p className={`font-medium ${selectedExam.outcome.biopsyTaken ? 'text-red-600' : 'text-green-600'}`}>
{selectedExam.outcome.biopsyTaken ? 'Biopsy taken' : 'No biopsy taken'}
</p>
</div>
<div>
<h3 className="font-semibold text-gray-700 mb-2">Management Plan</h3>
<p className="text-gray-900 bg-gray-50 p-3 rounded-lg">{selectedExam.outcome.plan}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
return (
<div className="w-full bg-white/95 relative">
<div className="relative z-10 py-4 md:py-6 lg:py-8">
<div className="w-full max-w-7xl mx-auto px-4 md:px-6">
{/* Header */}
<div className="mb-6 flex items-center justify-between">
<div className="flex items-center gap-4">
<button
onClick={goBack}
className="p-2 hover:bg-gray-100 rounded-lg transition-colors text-gray-600"
>
<ArrowLeft className="w-5 h-5" />
</button>
<div>
<h1 className="text-2xl md:text-3xl font-bold text-[#0A2540]">Examination Records</h1>
<p className="text-gray-600 mt-1">Patient ID: PT-2025-8492 • {dummyExams.length} examinations</p>
</div>
</div>
{/* Search and Filter */}
<div className="flex items-center gap-4">
<div className="relative">
<Search className="w-4 h-4 absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400" />
<input
type="text"
placeholder="Search exams..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#05998c] focus:border-transparent"
/>
</div>
<div className="relative">
<Filter className="w-4 h-4 absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400" />
<select
value={filterStatus}
onChange={(e) => setFilterStatus(e.target.value)}
className="pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#05998c] focus:border-transparent"
>
<option value="all">All Status</option>
<option value="follow-up">Follow-up</option>
<option value="discharged">Discharged</option>
<option value="referred">Referred</option>
</select>
</div>
</div>
</div>
{/* Examination List */}
<div className="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
<div className="overflow-x-auto">
<table className="w-full">
<thead className="bg-gray-50 border-b border-gray-200">
<tr>
<th className="px-6 py-4 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Exam ID</th>
<th className="px-6 py-4 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Date</th>
<th className="px-6 py-4 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Indication</th>
<th className="px-6 py-4 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Impression</th>
<th className="px-6 py-4 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">TZ Type</th>
<th className="px-6 py-4 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Biopsy</th>
<th className="px-6 py-4 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Status</th>
<th className="px-6 py-4 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
{filteredExams.map((exam) => (
<tr key={exam.id} className="hover:bg-gray-50">
<td className="px-6 py-4 whitespace-nowrap">
<div className="font-mono font-semibold text-[#0A2540]">{exam.id}</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm text-gray-900">{formatDate(exam.date)}</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm text-gray-900">{exam.indication}</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm text-gray-900">{exam.impression}</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm text-gray-900">{exam.tzType}</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${
exam.biopsy === 'Taken'
? 'bg-red-100 text-red-800'
: 'bg-green-100 text-green-800'
}`}>
{exam.biopsy}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${
exam.finalStatus.includes('Follow-up')
? 'bg-yellow-100 text-yellow-800'
: exam.finalStatus.includes('Discharged')
? 'bg-green-100 text-green-800'
: 'bg-red-100 text-red-800'
}`}>
{exam.finalStatus}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<button
onClick={() => setSelectedExam(exam)}
className="text-[#05998c] hover:text-[#047569] font-medium text-sm flex items-center gap-1"
>
<Eye className="w-4 h-4" />
View
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
{filteredExams.length === 0 && (
<div className="text-center py-12">
<FileText className="w-12 h-12 text-gray-300 mx-auto mb-4" />
<p className="text-gray-500">No examination records found</p>
</div>
)}
</div>
</div>
</div>
</div>
);
}