arshenoy commited on
Commit
12deb2e
·
verified ·
1 Parent(s): 4b466c0

Last Analysis logic fixed

Browse files
Files changed (1) hide show
  1. components/Dashboard.tsx +370 -342
components/Dashboard.tsx CHANGED
@@ -6,380 +6,408 @@ import ReportView from './ReportView';
6
  import { extractClinicalData } from '../services/geminiService';
7
 
8
  interface DashboardProps {
9
- vitals: ClinicalVitals;
10
- setVitals: React.Dispatch<React.SetStateAction<ClinicalVitals>>;
11
- riskResult: RiskAnalysisResult | null;
12
- chatSummary: string;
13
- handleRunAnalysis: () => void;
14
- isAnalyzing: boolean;
15
- onPrint: () => void;
16
- profile: PatientProfile;
17
- setProfile: (p: PatientProfile | ((prev: PatientProfile) => PatientProfile)) => void;
18
- medications: Medication[];
19
- chatHistory: ChatMessage[];
20
- insights: HealthInsights | null;
21
- statusMessage?: string; // New
22
- setStatusMessage: (msg: string) => void; // New
23
  }
24
 
25
  // --- ANOMALY DETECTION LOGIC ---
26
  const getVitalStatus = (type: string, value: number) => {
27
- if (!value) return 'normal';
28
- switch (type) {
29
- case 'systolicBp':
30
- if (value > 180 || value < 90) return 'critical';
31
- if (value > 140 || value < 100) return 'warning';
32
- return 'normal';
33
- case 'glucose':
34
- if (value > 250 || value < 50) return 'critical';
35
- if (value > 140 || value < 70) return 'warning';
36
- return 'normal';
37
- case 'heartRate':
38
- if (value > 120 || value < 40) return 'critical';
39
- if (value > 100 || value < 50) return 'warning';
40
- return 'normal';
41
- case 'spo2':
42
- if (value < 90) return 'critical';
43
- if (value < 95) return 'warning';
44
- return 'normal';
45
- case 'temperature':
46
- if (value > 103 || value < 95) return 'critical';
47
- if (value > 99.5) return 'warning';
48
- return 'normal';
49
- default:
50
- return 'normal';
51
- }
52
  };
53
 
54
  const VitalInput = ({ label, value, onChange, type, unit }: { label: string, value: number, onChange: (v: number) => void, type: string, unit: string }) => {
55
- const status = getVitalStatus(type, value);
56
- const borderColor = status === 'critical' ? 'border-red-500 shadow-[0_0_10px_rgba(239,68,68,0.3)]' : status === 'warning' ? 'border-yellow-500 shadow-[0_0_10px_rgba(234,179,8,0.3)]' : 'border-white/10';
57
- const textColor = status === 'critical' ? 'text-red-500' : status === 'warning' ? 'text-yellow-500' : 'text-white';
58
 
59
- return (
60
- <div>
61
- <label className="text-[10px] text-gray-500 uppercase font-bold flex justify-between">
62
- {label}
63
- {status !== 'normal' && <AlertCircle size={12} className={textColor} />}
64
- </label>
65
- <div className="flex items-end gap-1">
66
- <input
67
- type="number"
68
- value={value || ''}
69
- onChange={e => onChange(parseFloat(e.target.value))}
70
- className={`w-full bg-black/40 rounded p-2 text-sm font-mono outline-none transition-all border ${borderColor} ${textColor}`}
71
- />
72
- <span className="text-[10px] text-gray-600 mb-1">{unit}</span>
73
- </div>
74
- </div>
75
- );
76
  };
77
 
78
- const Dashboard: React.FC<DashboardProps> = ({
79
- vitals, setVitals, riskResult, chatSummary, handleRunAnalysis, isAnalyzing, onPrint, profile, setProfile, medications, chatHistory, insights, statusMessage, setStatusMessage
80
  }) => {
81
- const [showProfileModal, setShowProfileModal] = useState(false);
82
- const [localProfile, setLocalProfile] = useState(profile);
83
- const [isExtracting, setIsExtracting] = useState(false);
84
- const fileInputRef = useRef<HTMLInputElement>(null);
85
 
86
- const handleVitalChange = (key: keyof ClinicalVitals, val: number | string) => {
87
- setVitals(prev => ({ ...prev, [key]: val }));
88
- };
89
-
90
- const saveProfile = () => { setProfile(localProfile); setShowProfileModal(false); };
91
 
92
- const handleClearNote = () => setVitals(prev => ({ ...prev, clinicalNote: '' }));
 
93
 
94
- const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
95
- const file = e.target.files?.[0];
96
- if (!file) return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
- setIsExtracting(true);
99
- setStatusMessage('Reading document...');
 
100
 
101
- const reader = new FileReader();
102
- reader.onloadend = async () => {
103
- try {
104
- const extracted = await extractClinicalData(reader.result as string, setStatusMessage);
 
 
 
 
 
 
 
 
 
 
 
105
 
106
- if (extracted.vitals) {
107
- setVitals(prev => {
108
- const newVitals = { ...prev };
109
- if (extracted.vitals?.systolicBp) {
110
- const bp = Number(extracted.vitals.systolicBp);
111
- newVitals.systolicBpMorning = bp;
112
- newVitals.systolicBpEvening = bp;
113
- }
114
- if (extracted.vitals?.glucose) newVitals.glucose = Number(extracted.vitals.glucose);
115
- if (extracted.vitals?.heartRate) newVitals.heartRate = Number(extracted.vitals.heartRate);
116
- if (extracted.vitals?.weight) newVitals.weight = Number(extracted.vitals.weight);
117
- if (extracted.vitals?.temperature) newVitals.temperature = Number(extracted.vitals.temperature);
118
- if (extracted.vitals?.spo2) newVitals.spo2 = Number(extracted.vitals.spo2);
119
 
120
- const newNote = extracted.vitals?.clinicalNote || "";
121
- if (newNote) newVitals.clinicalNote = prev.clinicalNote ? prev.clinicalNote + "\n\n[Extracted]: " + newNote : "[Extracted]: " + newNote;
122
- return newVitals;
123
- });
124
- }
125
- if (extracted.profile) {
126
- setProfile(prev => ({ ...prev, ...extracted.profile }));
127
- setLocalProfile(prev => ({ ...prev, ...extracted.profile }));
128
- }
129
- alert(" Magic Upload Complete!");
130
- } catch (error) { alert("Could not extract data."); }
131
- finally {
132
- setIsExtracting(false);
133
- setStatusMessage('');
134
- if (fileInputRef.current) fileInputRef.current.value = '';
135
- }
136
- };
137
- reader.readAsDataURL(file);
138
- };
139
-
140
- const adherence = medications.length > 0
141
- ? Math.round((medications.filter(m => m.taken).length / medications.length) * 100)
142
- : 0;
143
- const daysSinceCheckup = Math.floor((new Date().getTime() - new Date(profile.lastCheckup).getTime()) / (1000 * 3600 * 24));
144
- const anomalyCount = [
145
- getVitalStatus('systolicBp', vitals.systolicBpMorning),
146
- getVitalStatus('systolicBp', vitals.systolicBpEvening),
147
- getVitalStatus('glucose', vitals.glucose),
148
- getVitalStatus('spo2', vitals.spo2),
149
- getVitalStatus('heartRate', vitals.heartRate),
150
- getVitalStatus('temperature', vitals.temperature),
151
- ].filter(s => s === 'critical').length;
152
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
 
154
- return (
155
- <div className="animate-in fade-in slide-in-from-bottom-4 duration-500">
156
- {/* HEADER & QUICK STATS */}
157
- <div className="flex flex-col md:flex-row justify-between items-start md:items-center mb-6 gap-4">
158
- <h2 className="text-2xl font-bold text-white flex items-center gap-2">
159
- <Activity className="text-neon-green" /> Dashboard
160
- </h2>
161
- <div className="flex gap-2">
162
- <button onClick={() => setShowProfileModal(true)} className="flex items-center gap-2 px-4 py-2 rounded-lg text-xs font-bold border border-white/10 text-gray-300 hover:text-white bg-white/5"><User size={14} className="text-neon-blue"/> PROFILE</button>
163
- <button onClick={onPrint} className="flex items-center gap-2 px-4 py-2 rounded-lg text-xs font-bold border border-white/10 text-gray-300 hover:text-white bg-white/5"><FileText size={14} /> REPORT</button>
164
- </div>
165
- </div>
166
- {/* QUICK STATS CARDS */}
167
- <div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
168
- <div className="glass-card p-4 rounded-xl border-l-4 border-l-neon-green">
169
- <p className="text-[10px] uppercase font-bold text-gray-500">Risk Level</p>
170
- <p className="text-xl font-bold text-white mt-1">{riskResult ? `${riskResult.numericScore}/100` : 'Pending'}</p>
171
- </div>
172
- <div className="glass-card p-4 rounded-xl border-l-4 border-l-neon-yellow">
173
- <p className="text-[10px] uppercase font-bold text-gray-500">Adherence</p>
174
- <p className="text-xl font-bold text-white mt-1">{adherence}%</p>
175
- </div>
176
- <div className="glass-card p-4 rounded-xl border-l-4 border-l-neon-blue">
177
- <p className="text-[10px] uppercase font-bold text-gray-500">Active Streak</p>
178
- <p className="text-xl font-bold text-white mt-1">{profile.streak} Days</p>
179
- </div>
180
- <div className="glass-card p-4 rounded-xl border-l-4 border-l-purple-500">
181
- <p className="text-[10px] uppercase font-bold text-gray-500">Last Checkup</p>
182
- <p className="text-xl font-bold text-white mt-1">{daysSinceCheckup} days ago</p>
183
- </div>
184
- </div>
185
- {/* ANOMALY ALERT BANNER */}
186
- {anomalyCount > 0 && (
187
- <div className="mb-8 p-4 bg-red-500/10 border border-red-500/30 rounded-xl flex items-center gap-3 animate-pulse">
188
- <AlertTriangle className="text-red-500" />
189
- <div>
190
- <h4 className="font-bold text-red-400 text-sm">Critical Anomalies Detected</h4>
191
- <p className="text-xs text-red-300/80">Some vital signs are outside normal ranges. Please review and consult a professional.</p>
192
- </div>
193
- </div>
194
- )}
195
- <div className="grid grid-cols-1 lg:grid-cols-12 gap-8 mb-8">
196
-
197
- {/* LEFT COLUMN: VITALS INPUT */}
198
- <div className="lg:col-span-4 space-y-6">
199
- <div className="glass-panel p-6 rounded-2xl border-t border-neon-green">
200
- <div className="flex justify-between items-center mb-4">
201
- <h3 className="text-sm font-bold text-gray-400 uppercase tracking-wide">Vital Signs</h3>
202
- <div className="relative">
203
- <input type="file" accept="image/*,.pdf" className="hidden" ref={fileInputRef} onChange={handleFileUpload} />
204
- <button onClick={() => fileInputRef.current?.click()} disabled={isExtracting} className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-neon-green/10 text-neon-green text-[10px] font-bold border border-neon-green/20 hover:bg-neon-green/20 transition-all disabled:opacity-50">
205
- {isExtracting ? <Loader2 size={12} className="animate-spin" /> : <ScanLine size={12} />} {isExtracting ? 'SCANNING...' : 'SCAN REPORT'}
206
- </button>
207
  </div>
208
  </div>
209
- {statusMessage && isExtracting && <div className="text-xs text-neon-green mb-2 animate-pulse font-mono">{statusMessage}</div>}
210
-
211
- <div className="space-y-4">
212
- {/* BP TREND: Morning vs Evening */}
213
- <div className="bg-black/20 p-3 rounded-xl border border-white/5">
214
- <label className="text-[10px] text-gray-500 uppercase font-bold mb-2 flex items-center gap-1"><TrendingUp size={12} /> Blood Pressure (Sys)</label>
215
- <div className="grid grid-cols-2 gap-3">
216
- <VitalInput label="Morning" value={vitals.systolicBpMorning} onChange={v => handleVitalChange('systolicBpMorning', v)} type="systolicBp" unit="mmHg" />
217
- <VitalInput label="Evening" value={vitals.systolicBpEvening} onChange={v => handleVitalChange('systolicBpEvening', v)} type="systolicBp" unit="mmHg" />
218
- </div>
219
- </div>
220
- <div className="grid grid-cols-2 gap-4">
221
- <VitalInput label="Glucose" value={vitals.glucose} onChange={v => handleVitalChange('glucose', v)} type="glucose" unit="mg/dL" />
222
- <VitalInput label="Heart Rate" value={vitals.heartRate} onChange={v => handleVitalChange('heartRate', v)} type="heartRate" unit="bpm" />
223
- </div>
224
- <div className="grid grid-cols-2 gap-4">
225
- <VitalInput label="SpO2" value={vitals.spo2} onChange={v => handleVitalChange('spo2', v)} type="spo2" unit="%" />
226
- <VitalInput label="Weight" value={vitals.weight} onChange={v => handleVitalChange('weight', v)} type="weight" unit="kg" />
227
- </div>
228
- <div className="grid grid-cols-2 gap-4">
229
- <VitalInput label="Temperature" value={vitals.temperature} onChange={v => handleVitalChange('temperature', v)} type="temperature" unit="°F" />
230
- <div>
231
- <label className="text-[10px] text-gray-500 uppercase font-bold">Missed Meds</label>
232
- <input type="number" value={vitals.missedDoses} onChange={e => handleVitalChange('missedDoses', parseInt(e.target.value))} className="w-full bg-black/40 border border-white/10 rounded p-2 text-white text-sm font-mono mt-auto" />
233
- </div>
234
- </div>
235
- <div>
236
- <div className="flex justify-between text-[10px] uppercase font-bold text-gray-500 mb-1">
237
- <span>Sleep Quality</span>
238
- <span className="text-neon-blue">{vitals.sleepQuality}/10</span>
239
- </div>
240
- <input type="range" min="0" max="10" value={vitals.sleepQuality} onChange={e => handleVitalChange('sleepQuality', parseInt(e.target.value))} className="w-full h-1 bg-gray-800 rounded-lg appearance-none cursor-pointer accent-neon-blue" />
241
- </div>
242
- <div className="pt-2 border-t border-white/5 relative">
243
- <div className="flex justify-between items-center mb-2">
244
- <label className="text-[10px] text-gray-500 uppercase font-bold flex items-center gap-2"><Clipboard size={12} /> Clinical Note</label>
245
- {vitals.clinicalNote && <button onClick={handleClearNote} className="text-[10px] text-gray-500 hover:text-red-400 flex items-center gap-1"><Trash2 size={10} /> Clear</button>}
246
- </div>
247
- <textarea value={vitals.clinicalNote} onChange={e => handleVitalChange('clinicalNote', e.target.value)} placeholder="Paste reports or symptoms..." className="w-full h-20 bg-black/40 border border-white/10 rounded-lg p-3 text-xs text-gray-300 focus:border-neon-green outline-none resize-none" />
248
- </div>
249
- <button onClick={handleRunAnalysis} disabled={isAnalyzing} className="w-full bg-neon-green hover:bg-neon-green/90 text-black py-3 rounded-xl font-mono font-bold transition-all flex justify-center items-center gap-2 shadow-[0_0_20px_rgba(0,255,128,0.2)] disabled:opacity-50 text-sm">
250
- {isAnalyzing ? <Loader2 size={18} className="animate-spin" /> : <HeartPulse size={18} />} {isAnalyzing ? 'ANALYZING...' : 'RUN ASSESSMENT'}
251
- </button>
252
- </div>
253
- </div>
254
- </div>
255
- {/* RIGHT COLUMN: RESULTS & INSIGHTS */}
256
- <div className="lg:col-span-8 space-y-6">
257
- <div className="glass-panel p-6 rounded-2xl min-h-[600px] flex flex-col">
258
- <h3 className="text-xl font-bold text-white mb-6 flex justify-between items-center">
259
- <span>SomAI Clinical Analysis</span>
260
- {riskResult?.source && (
261
- <span className="text-[10px] font-mono font-bold uppercase px-2 py-1 rounded bg-white/5 border border-white/10 text-gray-400 flex items-center gap-1">
262
- {riskResult.source.includes('Gemini') ? <Zap size={10} className="text-neon-yellow"/> : <Server size={10} className="text-neon-red"/>}
263
- {riskResult.source}
264
- </span>
265
- )}
266
- </h3>
267
 
268
- {riskResult ? (
269
- <div className="flex flex-col gap-6 animate-in fade-in">
270
- {/* HEALTH INSIGHTS PANEL (NEW) */}
271
- {insights && (
272
- <div className="bg-gradient-to-r from-neon-blue/10 to-purple-500/10 p-5 rounded-xl border border-white/10 relative overflow-hidden">
273
- <div className="absolute top-0 right-0 p-3 opacity-20"><ScanLine size={48} /></div>
274
- <h4 className="text-sm font-bold text-white flex items-center gap-2 mb-3"><Activity size={16} className="text-neon-blue"/> Your Health This Week</h4>
275
- <p className="text-sm text-gray-200 mb-2">{insights.weeklySummary}</p>
276
- <p className="text-xs text-gray-400 italic mb-4">Progress: {insights.progress}</p>
277
- <div className="flex flex-wrap gap-2">
278
- {insights.tips.map((tip, i) => (
279
- <span key={i} className="text-xs px-3 py-1 bg-white/5 rounded-full border border-white/5 text-gray-300 flex items-center gap-1">
280
- <Info size={10} className="text-neon-yellow"/> {tip}
281
- </span>
282
- ))}
283
- </div>
284
- </div>
285
- )}
286
- <div className="flex flex-col md:flex-row items-center gap-8">
287
- <div className="w-full md:w-48 h-48 flex-shrink-0"><GaugeChart value={riskResult.numericScore} /></div>
288
- <div className="flex-1 bg-white/5 p-5 rounded-xl border border-white/5">
289
- <h4 className="text-xs text-neon-blue font-bold uppercase mb-2">Assessment</h4>
290
- <p className="text-sm text-gray-200 leading-relaxed">{riskResult.summary}</p>
291
- {statusMessage && isAnalyzing && <p className="text-xs text-neon-green mt-2 animate-pulse">{statusMessage}</p>}
292
  </div>
293
  </div>
 
294
 
295
- <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
296
- <div className="glass-card p-4 rounded-xl">
297
- <h4 className="text-xs font-mono text-gray-500 uppercase mb-3 font-bold">Action Plan</h4>
298
- <ul className="space-y-2">
299
- {riskResult.actionItems.map((item, i) => (
300
- <li key={i} className="flex items-start gap-2 text-sm text-gray-300"><span className="text-neon-green mt-1">✓</span> {item}</li>
301
- ))}
302
- </ul>
303
- </div>
304
- <div className="glass-card p-4 rounded-xl">
305
- <h4 className="text-xs font-mono text-gray-500 uppercase mb-3 font-bold flex items-center gap-2"><ShieldCheck size={14} className="text-purple-400"/> Coding Pipeline</h4>
306
- <div className="space-y-2 max-h-40 overflow-y-auto pr-1 scrollbar-hide">
307
- {riskResult.codingPipeline?.map((code, i) => (
308
- <div key={i} className="flex justify-between items-center text-xs border-b border-white/5 pb-1 last:border-0">
309
- <span className="text-gray-400 truncate max-w-[150px]" title={code.description}>{code.description}</span>
310
- <span className="font-mono text-purple-300 bg-purple-500/10 px-1.5 py-0.5 rounded">{code.code}</span>
311
- </div>
312
- ))}
313
- </div>
314
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315
  </div>
316
  </div>
317
- ) : (
318
- <div className="flex-1 flex flex-col items-center justify-center text-gray-600 border-2 border-dashed border-white/10 rounded-xl bg-black/20">
319
- <Activity size={48} className="mb-4 opacity-30" />
320
- <p className="font-medium text-lg text-gray-500">Awaiting Clinical Data</p>
321
- {statusMessage && isAnalyzing && <p className="text-xs text-neon-green mt-2 animate-pulse">{statusMessage}</p>}
322
- </div>
323
- )}
324
- </div>
325
- </div>
326
- </div>
327
- {/* EXTENDED PROFILE MODAL */}
328
- {showProfileModal && (
329
- <div className="fixed inset-0 z-[60] flex items-center justify-center bg-black/90 backdrop-blur-sm p-4 overflow-y-auto">
330
- <div className="glass-panel w-full max-w-4xl p-6 rounded-2xl border border-neon-blue/30 relative animate-in zoom-in-95 my-auto">
331
- <button onClick={() => setShowProfileModal(false)} className="absolute top-4 right-4 text-gray-500 hover:text-white"><X size={20} /></button>
332
- <h2 className="text-xl font-bold text-white mb-6 flex items-center gap-2"><User className="text-neon-blue" size={20} /> Comprehensive Patient Profile</h2>
333
-
334
- <div className="grid grid-cols-1 md:grid-cols-2 gap-8 max-h-[70vh] overflow-y-auto pr-2 custom-scrollbar">
335
- {/* BASIC */}
336
- <section className="space-y-4">
337
- <h3 className="text-xs font-bold text-neon-green uppercase border-b border-white/10 pb-1">Basic Info</h3>
338
- <div className="grid grid-cols-2 gap-4">
339
- <div><label className="text-[10px] uppercase text-gray-500 font-bold">Name</label><input value={localProfile.name} onChange={e => setLocalProfile({...localProfile, name: e.target.value})} className="w-full bg-black/40 border border-white/10 rounded p-2 text-white text-sm" /></div>
340
- <div><label className="text-[10px] uppercase text-gray-500 font-bold">Age</label><input type="number" value={localProfile.age} onChange={e => setLocalProfile({...localProfile, age: parseInt(e.target.value)})} className="w-full bg-black/40 border border-white/10 rounded p-2 text-white text-sm" /></div>
341
- <div><label className="text-[10px] uppercase text-gray-500 font-bold">Gender</label><select value={localProfile.gender} onChange={e => setLocalProfile({...localProfile, gender: e.target.value})} className="w-full bg-black/40 border border-white/10 rounded p-2 text-white text-sm"><option>Male</option><option>Female</option><option>Other</option><option>Prefer not to say</option></select></div>
342
- <div><label className="text-[10px] uppercase text-gray-500 font-bold">Blood Group</label><input value={localProfile.bloodGroup} onChange={e => setLocalProfile({...localProfile, bloodGroup: e.target.value})} className="w-full bg-black/40 border border-white/10 rounded p-2 text-white text-sm" /></div>
343
- </div>
344
- </section>
345
- {/* EMERGENCY */}
346
- <section className="space-y-4">
347
- <h3 className="text-xs font-bold text-neon-red uppercase border-b border-white/10 pb-1 flex items-center gap-2"><Phone size={12}/> Emergency Contact</h3>
348
- <div className="grid grid-cols-2 gap-4">
349
- <div><label className="text-[10px] uppercase text-gray-500 font-bold">Contact Name</label><input value={localProfile.emergencyContactName} onChange={e => setLocalProfile({...localProfile, emergencyContactName: e.target.value})} className="w-full bg-black/40 border border-white/10 rounded p-2 text-white text-sm" /></div>
350
- <div><label className="text-[10px] uppercase text-gray-500 font-bold">Relation</label><input value={localProfile.emergencyContactRelation} onChange={e => setLocalProfile({...localProfile, emergencyContactRelation: e.target.value})} className="w-full bg-black/40 border border-white/10 rounded p-2 text-white text-sm" /></div>
351
- <div className="col-span-2"><label className="text-[10px] uppercase text-gray-500 font-bold">Phone Number</label><input value={localProfile.emergencyContactPhone} onChange={e => setLocalProfile({...localProfile, emergencyContactPhone: e.target.value})} className="w-full bg-black/40 border border-white/10 rounded p-2 text-white text-sm" /></div>
 
 
 
 
 
 
352
  </div>
353
- </section>
354
- {/* MEDICAL */}
355
- <section className="space-y-4">
356
- <h3 className="text-xs font-bold text-neon-blue uppercase border-b border-white/10 pb-1 flex items-center gap-2"><Stethoscope size={12}/> Medical History</h3>
357
- <div><label className="text-[10px] uppercase text-gray-500 font-bold">Primary Condition</label><input value={localProfile.condition} onChange={e => setLocalProfile({...localProfile, condition: e.target.value})} className="w-full bg-black/40 border border-white/10 rounded p-2 text-white text-sm" /></div>
358
- <div><label className="text-[10px] uppercase text-gray-500 font-bold">Past Conditions</label><textarea value={localProfile.history} onChange={e => setLocalProfile({...localProfile, history: e.target.value})} className="w-full h-16 bg-black/40 border border-white/10 rounded p-2 text-white text-sm resize-none" /></div>
359
- <div><label className="text-[10px] uppercase text-gray-500 font-bold">Surgeries/Procedures</label><textarea value={localProfile.surgeries} onChange={e => setLocalProfile({...localProfile, surgeries: e.target.value})} className="w-full h-16 bg-black/40 border border-white/10 rounded p-2 text-white text-sm resize-none" /></div>
360
- <div><label className="text-[10px] uppercase text-gray-500 font-bold">Allergies</label><textarea value={localProfile.allergies} onChange={e => setLocalProfile({...localProfile, allergies: e.target.value})} className="w-full h-16 bg-black/40 border border-white/10 rounded p-2 text-white text-sm resize-none" /></div>
361
- </section>
362
- {/* LIFESTYLE */}
363
- <section className="space-y-4">
364
- <h3 className="text-xs font-bold text-neon-yellow uppercase border-b border-white/10 pb-1 flex items-center gap-2"><Utensils size={12}/> Lifestyle</h3>
365
- <div className="grid grid-cols-2 gap-4">
366
- <div><label className="text-[10px] uppercase text-gray-500 font-bold">Diet</label><select value={localProfile.diet} onChange={e => setLocalProfile({...localProfile, diet: e.target.value as any})} className="w-full bg-black/40 border border-white/10 rounded p-2 text-white text-sm"><option>Omnivore</option><option>Vegetarian</option><option>Vegan</option><option>Keto</option><option>Other</option></select></div>
367
- <div><label className="text-[10px] uppercase text-gray-500 font-bold">Exercise</label><select value={localProfile.exerciseFrequency} onChange={e => setLocalProfile({...localProfile, exerciseFrequency: e.target.value as any})} className="w-full bg-black/40 border border-white/10 rounded p-2 text-white text-sm"><option>Sedentary</option><option>Light</option><option>Moderate</option><option>Active</option></select></div>
368
- <div><label className="text-[10px] uppercase text-gray-500 font-bold flex items-center gap-1"><Cigarette size={10}/> Smoking</label><select value={localProfile.smokingStatus} onChange={e => setLocalProfile({...localProfile, smokingStatus: e.target.value as any})} className="w-full bg-black/40 border border-white/10 rounded p-2 text-white text-sm"><option>Never</option><option>Former</option><option>Current</option></select></div>
369
- <div><label className="text-[10px] uppercase text-gray-500 font-bold">Alcohol</label><select value={localProfile.alcoholConsumption} onChange={e => setLocalProfile({...localProfile, alcoholConsumption: e.target.value as any})} className="w-full bg-black/40 border border-white/10 rounded p-2 text-white text-sm"><option>None</option><option>Occasional</option><option>Regular</option></select></div>
 
 
 
 
370
  </div>
371
- <div><label className="text-[10px] uppercase text-gray-500 font-bold">Family History</label><textarea value={localProfile.familyHistory} onChange={e => setLocalProfile({...localProfile, familyHistory: e.target.value})} className="w-full h-16 bg-black/40 border border-white/10 rounded p-2 text-white text-sm resize-none" /></div>
372
- </section>
373
- </div>
374
- <div className="mt-6 flex justify-end">
375
- <button onClick={saveProfile} className="bg-neon-blue text-black font-bold px-6 py-2 rounded-lg hover:bg-neon-blue/80 flex items-center gap-2"><Check size={16} /> Save Profile</button>
 
 
 
376
  </div>
377
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
378
  </div>
379
- )}
380
- <ReportView profile={profile} vitals={vitals} riskResult={riskResult} chatHistory={chatHistory} chatSummary={chatSummary} medications={medications} />
381
- </div>
382
- );
383
  };
384
 
385
  export default Dashboard;
 
6
  import { extractClinicalData } from '../services/geminiService';
7
 
8
  interface DashboardProps {
9
+ vitals: ClinicalVitals;
10
+ setVitals: React.Dispatch<React.SetStateAction<ClinicalVitals>>;
11
+ riskResult: RiskAnalysisResult | null;
12
+ chatSummary: string;
13
+ handleRunAnalysis: () => void;
14
+ isAnalyzing: boolean;
15
+ onPrint: () => void;
16
+ profile: PatientProfile;
17
+ setProfile: (p: PatientProfile | ((prev: PatientProfile) => PatientProfile)) => void;
18
+ medications: Medication[];
19
+ chatHistory: ChatMessage[];
20
+ insights: HealthInsights | null;
21
+ statusMessage?: string; // New
22
+ setStatusMessage: (msg: string) => void; // New
23
  }
24
 
25
  // --- ANOMALY DETECTION LOGIC ---
26
  const getVitalStatus = (type: string, value: number) => {
27
+ if (!value) return 'normal';
28
+ switch (type) {
29
+ case 'systolicBp':
30
+ if (value > 180 || value < 90) return 'critical';
31
+ if (value > 140 || value < 100) return 'warning';
32
+ return 'normal';
33
+ case 'glucose':
34
+ if (value > 250 || value < 50) return 'critical';
35
+ if (value > 140 || value < 70) return 'warning';
36
+ return 'normal';
37
+ case 'heartRate':
38
+ if (value > 120 || value < 40) return 'critical';
39
+ if (value > 100 || value < 50) return 'warning';
40
+ return 'normal';
41
+ case 'spo2':
42
+ if (value < 90) return 'critical';
43
+ if (value < 95) return 'warning';
44
+ return 'normal';
45
+ case 'temperature':
46
+ if (value > 103 || value < 95) return 'critical';
47
+ if (value > 99.5) return 'warning';
48
+ return 'normal';
49
+ default:
50
+ return 'normal';
51
+ }
52
  };
53
 
54
  const VitalInput = ({ label, value, onChange, type, unit }: { label: string, value: number, onChange: (v: number) => void, type: string, unit: string }) => {
55
+ const status = getVitalStatus(type, value);
56
+ const borderColor = status === 'critical' ? 'border-red-500 shadow-[0_0_10px_rgba(239,68,68,0.3)]' : status === 'warning' ? 'border-yellow-500 shadow-[0_0_10px_rgba(234,179,8,0.3)]' : 'border-white/10';
57
+ const textColor = status === 'critical' ? 'text-red-500' : status === 'warning' ? 'text-yellow-500' : 'text-white';
58
 
59
+ return (
60
+ <div>
61
+ <label className="text-[10px] text-gray-500 uppercase font-bold flex justify-between">
62
+ {label}
63
+ {status !== 'normal' && <AlertCircle size={12} className={textColor} />}
64
+ </label>
65
+ <div className="flex items-end gap-1">
66
+ <input
67
+ type="number"
68
+ value={value || ''}
69
+ onChange={e => onChange(parseFloat(e.target.value))}
70
+ className={`w-full bg-black/40 rounded p-2 text-sm font-mono outline-none transition-all border ${borderColor} ${textColor}`}
71
+ />
72
+ <span className="text-[10px] text-gray-600 mb-1">{unit}</span>
73
+ </div>
74
+ </div>
75
+ );
76
  };
77
 
78
+ const Dashboard: React.FC<DashboardProps> = ({
79
+ vitals, setVitals, riskResult, chatSummary, handleRunAnalysis, isAnalyzing, onPrint, profile, setProfile, medications, chatHistory, insights, statusMessage, setStatusMessage
80
  }) => {
81
+ const [showProfileModal, setShowProfileModal] = useState(false);
82
+ const [localProfile, setLocalProfile] = useState(profile);
83
+ const [isExtracting, setIsExtracting] = useState(false);
84
+ const fileInputRef = useRef<HTMLInputElement>(null);
85
 
86
+ const handleVitalChange = (key: keyof ClinicalVitals, val: number | string) => {
87
+ setVitals(prev => ({ ...prev, [key]: val }));
88
+ };
 
 
89
 
90
+ const saveProfile = () => { setProfile(localProfile); setShowProfileModal(false); };
91
+ const handleClearNote = () => setVitals(prev => ({ ...prev, clinicalNote: '' }));
92
 
93
+ const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
94
+ const file = e.target.files?.[0];
95
+ if (!file) return;
96
+ setIsExtracting(true);
97
+ setStatusMessage('Reading document...');
98
+ const reader = new FileReader();
99
+ reader.onloadend = async () => {
100
+ try {
101
+ const extracted = await extractClinicalData(reader.result as string, setStatusMessage);
102
+ if (extracted.vitals) {
103
+ setVitals(prev => {
104
+ const newVitals = { ...prev };
105
+ if (extracted.vitals?.systolicBp) {
106
+ const bp = Number(extracted.vitals.systolicBp);
107
+ newVitals.systolicBpMorning = bp;
108
+ newVitals.systolicBpEvening = bp;
109
+ }
110
+ if (extracted.vitals?.glucose) newVitals.glucose = Number(extracted.vitals.glucose);
111
+ if (extracted.vitals?.heartRate) newVitals.heartRate = Number(extracted.vitals.heartRate);
112
+ if (extracted.vitals?.weight) newVitals.weight = Number(extracted.vitals.weight);
113
+ if (extracted.vitals?.temperature) newVitals.temperature = Number(extracted.vitals.temperature);
114
+ if (extracted.vitals?.spo2) newVitals.spo2 = Number(extracted.vitals.spo2);
115
+
116
+ const newNote = extracted.vitals?.clinicalNote || "";
117
+ if (newNote) newVitals.clinicalNote = prev.clinicalNote ? prev.clinicalNote + "\n\n[Extracted]: " + newNote : "[Extracted]: " + newNote;
118
+ return newVitals;
119
+ });
120
+ }
121
+ if (extracted.profile) {
122
+ setProfile(prev => ({ ...prev, ...extracted.profile }));
123
+ setLocalProfile(prev => ({ ...prev, ...extracted.profile }));
124
+ }
125
+ alert("✨ Magic Upload Complete!");
126
+ } catch (error) { alert("Could not extract data."); }
127
+ finally {
128
+ setIsExtracting(false);
129
+ setStatusMessage('');
130
+ if (fileInputRef.current) fileInputRef.current.value = '';
131
+ }
132
+ };
133
+ reader.readAsDataURL(file);
134
+ };
135
 
136
+ const adherence = medications.length > 0
137
+ ? Math.round((medications.filter(m => m.taken).length / medications.length) * 100)
138
+ : 0;
139
 
140
+ // IMPROVED TIME CALCULATION
141
+ const getDaysSinceCheckup = () => {
142
+ const last = new Date(profile.lastCheckup);
143
+ const now = new Date();
144
+ // Set to midnight to compare days properly
145
+ last.setHours(0,0,0,0);
146
+ now.setHours(0,0,0,0);
147
+
148
+ const diffTime = now.getTime() - last.getTime();
149
+ const diffDays = Math.round(diffTime / (1000 * 3600 * 24));
150
+
151
+ if (diffDays === 0) return 'Today';
152
+ if (diffDays === 1) return 'Yesterday';
153
+ return `${diffDays} days ago`;
154
+ };
155
 
156
+ const anomalyCount = [
157
+ getVitalStatus('systolicBp', vitals.systolicBpMorning),
158
+ getVitalStatus('systolicBp', vitals.systolicBpEvening),
159
+ getVitalStatus('glucose', vitals.glucose),
160
+ getVitalStatus('spo2', vitals.spo2),
161
+ getVitalStatus('heartRate', vitals.heartRate),
162
+ getVitalStatus('temperature', vitals.temperature),
163
+ ].filter(s => s === 'critical').length;
 
 
 
 
 
164
 
165
+ return (
166
+ <div className="animate-in fade-in slide-in-from-bottom-4 duration-500">
167
+
168
+ {/* HEADER & QUICK STATS */}
169
+ <div className="flex flex-col md:flex-row justify-between items-start md:items-center mb-6 gap-4">
170
+ <h2 className="text-2xl font-bold text-white flex items-center gap-2">
171
+ <Activity className="text-neon-green" /> Dashboard
172
+ </h2>
173
+ <div className="flex gap-2">
174
+ <button onClick={() => setShowProfileModal(true)} className="flex items-center gap-2 px-4 py-2 rounded-lg text-xs font-bold border border-white/10 text-gray-300 hover:text-white bg-white/5"><User size={14} className="text-neon-blue"/> PROFILE</button>
175
+ <button onClick={onPrint} className="flex items-center gap-2 px-4 py-2 rounded-lg text-xs font-bold border border-white/10 text-gray-300 hover:text-white bg-white/5"><FileText size={14} /> REPORT</button>
176
+ </div>
177
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
 
179
+ {/* QUICK STATS CARDS */}
180
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
181
+ <div className="glass-card p-4 rounded-xl border-l-4 border-l-neon-green">
182
+ <p className="text-[10px] uppercase font-bold text-gray-500">Risk Level</p>
183
+ <p className="text-xl font-bold text-white mt-1">{riskResult ? `${riskResult.numericScore}/100` : 'Pending'}</p>
184
+ </div>
185
+ <div className="glass-card p-4 rounded-xl border-l-4 border-l-neon-yellow">
186
+ <p className="text-[10px] uppercase font-bold text-gray-500">Adherence</p>
187
+ <p className="text-xl font-bold text-white mt-1">{adherence}%</p>
188
+ </div>
189
+ <div className="glass-card p-4 rounded-xl border-l-4 border-l-neon-blue">
190
+ <p className="text-[10px] uppercase font-bold text-gray-500">Active Streak</p>
191
+ <p className="text-xl font-bold text-white mt-1">{profile.streak} Days</p>
192
+ </div>
193
+ <div className="glass-card p-4 rounded-xl border-l-4 border-l-purple-500">
194
+ <p className="text-[10px] uppercase font-bold text-gray-500">Last Analysis</p>
195
+ <p className="text-xl font-bold text-white mt-1">{getDaysSinceCheckup()}</p>
196
+ </div>
197
+ </div>
198
 
199
+ {/* ANOMALY ALERT BANNER */}
200
+ {anomalyCount > 0 && (
201
+ <div className="mb-8 p-4 bg-red-500/10 border border-red-500/30 rounded-xl flex items-center gap-3 animate-pulse">
202
+ <AlertTriangle className="text-red-500" />
203
+ <div>
204
+ <h4 className="font-bold text-red-400 text-sm">Critical Anomalies Detected</h4>
205
+ <p className="text-xs text-red-300/80">Some vital signs are outside normal ranges. Please review and consult a professional.</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  </div>
207
  </div>
208
+ )}
209
+
210
+ <div className="grid grid-cols-1 lg:grid-cols-12 gap-8 mb-8">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211
 
212
+ {/* LEFT COLUMN: VITALS INPUT */}
213
+ <div className="lg:col-span-4 space-y-6">
214
+ <div className="glass-panel p-6 rounded-2xl border-t border-neon-green">
215
+ <div className="flex justify-between items-center mb-4">
216
+ <h3 className="text-sm font-bold text-gray-400 uppercase tracking-wide">Vital Signs</h3>
217
+ <div className="relative">
218
+ <input type="file" accept="image/*,.pdf" className="hidden" ref={fileInputRef} onChange={handleFileUpload} />
219
+ <button onClick={() => fileInputRef.current?.click()} disabled={isExtracting} className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-neon-green/10 text-neon-green text-[10px] font-bold border border-neon-green/20 hover:bg-neon-green/20 transition-all disabled:opacity-50">
220
+ {isExtracting ? <Loader2 size={12} className="animate-spin" /> : <ScanLine size={12} />} {isExtracting ? 'SCANNING...' : 'SCAN REPORT'}
221
+ </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
  </div>
223
  </div>
224
+ {statusMessage && isExtracting && <div className="text-xs text-neon-green mb-2 animate-pulse font-mono">{statusMessage}</div>}
225
 
226
+ <div className="space-y-4">
227
+ {/* BP TREND: Morning vs Evening */}
228
+ <div className="bg-black/20 p-3 rounded-xl border border-white/5">
229
+ <label className="text-[10px] text-gray-500 uppercase font-bold mb-2 flex items-center gap-1"><TrendingUp size={12} /> Blood Pressure (Sys)</label>
230
+ <div className="grid grid-cols-2 gap-3">
231
+ <VitalInput label="Morning" value={vitals.systolicBpMorning} onChange={v => handleVitalChange('systolicBpMorning', v)} type="systolicBp" unit="mmHg" />
232
+ <VitalInput label="Evening" value={vitals.systolicBpEvening} onChange={v => handleVitalChange('systolicBpEvening', v)} type="systolicBp" unit="mmHg" />
233
+ </div>
234
+ </div>
235
+
236
+ <div className="grid grid-cols-2 gap-4">
237
+ <VitalInput label="Glucose" value={vitals.glucose} onChange={v => handleVitalChange('glucose', v)} type="glucose" unit="mg/dL" />
238
+ <VitalInput label="Heart Rate" value={vitals.heartRate} onChange={v => handleVitalChange('heartRate', v)} type="heartRate" unit="bpm" />
239
+ </div>
240
+
241
+ <div className="grid grid-cols-2 gap-4">
242
+ <VitalInput label="SpO2" value={vitals.spo2} onChange={v => handleVitalChange('spo2', v)} type="spo2" unit="%" />
243
+ <VitalInput label="Weight" value={vitals.weight} onChange={v => handleVitalChange('weight', v)} type="weight" unit="kg" />
244
+ </div>
245
+
246
+ <div className="grid grid-cols-2 gap-4">
247
+ <VitalInput label="Temperature" value={vitals.temperature} onChange={v => handleVitalChange('temperature', v)} type="temperature" unit="°F" />
248
+ <div>
249
+ <label className="text-[10px] text-gray-500 uppercase font-bold">Missed Meds</label>
250
+ <input type="number" value={vitals.missedDoses} onChange={e => handleVitalChange('missedDoses', parseInt(e.target.value))} className="w-full bg-black/40 border border-white/10 rounded p-2 text-white text-sm font-mono mt-auto" />
251
+ </div>
252
+ </div>
253
+
254
+ <div>
255
+ <div className="flex justify-between text-[10px] uppercase font-bold text-gray-500 mb-1">
256
+ <span>Sleep Quality</span>
257
+ <span className="text-neon-blue">{vitals.sleepQuality}/10</span>
258
+ </div>
259
+ <input type="range" min="0" max="10" value={vitals.sleepQuality} onChange={e => handleVitalChange('sleepQuality', parseInt(e.target.value))} className="w-full h-1 bg-gray-800 rounded-lg appearance-none cursor-pointer accent-neon-blue" />
260
+ </div>
261
+
262
+ <div className="pt-2 border-t border-white/5 relative">
263
+ <div className="flex justify-between items-center mb-2">
264
+ <label className="text-[10px] text-gray-500 uppercase font-bold flex items-center gap-2"><Clipboard size={12} /> Clinical Note</label>
265
+ {vitals.clinicalNote && <button onClick={handleClearNote} className="text-[10px] text-gray-500 hover:text-red-400 flex items-center gap-1"><Trash2 size={10} /> Clear</button>}
266
+ </div>
267
+ <textarea value={vitals.clinicalNote} onChange={e => handleVitalChange('clinicalNote', e.target.value)} placeholder="Paste reports or symptoms..." className="w-full h-20 bg-black/40 border border-white/10 rounded-lg p-3 text-xs text-gray-300 focus:border-neon-green outline-none resize-none" />
268
+ </div>
269
+
270
+ <button onClick={handleRunAnalysis} disabled={isAnalyzing} className="w-full bg-neon-green hover:bg-neon-green/90 text-black py-3 rounded-xl font-mono font-bold transition-all flex justify-center items-center gap-2 shadow-[0_0_20px_rgba(0,255,128,0.2)] disabled:opacity-50 text-sm">
271
+ {isAnalyzing ? <Loader2 size={18} className="animate-spin" /> : <HeartPulse size={18} />} {isAnalyzing ? 'ANALYZING...' : 'RUN ASSESSMENT'}
272
+ </button>
273
  </div>
274
  </div>
275
+ </div>
276
+
277
+ {/* RIGHT COLUMN: RESULTS & INSIGHTS */}
278
+ <div className="lg:col-span-8 space-y-6">
279
+ <div className="glass-panel p-6 rounded-2xl min-h-[600px] flex flex-col">
280
+ <h3 className="text-xl font-bold text-white mb-6 flex justify-between items-center">
281
+ <span>SomAI Clinical Analysis</span>
282
+ {riskResult?.source && (
283
+ <span className="text-[10px] font-mono font-bold uppercase px-2 py-1 rounded bg-white/5 border border-white/10 text-gray-400 flex items-center gap-1">
284
+ {riskResult.source.includes('Gemini') ? <Zap size={10} className="text-neon-yellow"/> : <Server size={10} className="text-neon-red"/>}
285
+ {riskResult.source}
286
+ </span>
287
+ )}
288
+ </h3>
289
+
290
+ {riskResult ? (
291
+ <div className="flex flex-col gap-6 animate-in fade-in">
292
+ {/* HEALTH INSIGHTS PANEL (NEW) */}
293
+ {insights && (
294
+ <div className="bg-gradient-to-r from-neon-blue/10 to-purple-500/10 p-5 rounded-xl border border-white/10 relative overflow-hidden">
295
+ <div className="absolute top-0 right-0 p-3 opacity-20"><ScanLine size={48} /></div>
296
+ <h4 className="text-sm font-bold text-white flex items-center gap-2 mb-3"><Activity size={16} className="text-neon-blue"/> Your Health This Week</h4>
297
+ <p className="text-sm text-gray-200 mb-2">{insights.weeklySummary}</p>
298
+ <p className="text-xs text-gray-400 italic mb-4">Progress: {insights.progress}</p>
299
+ <div className="flex flex-wrap gap-2">
300
+ {insights.tips.map((tip, i) => (
301
+ <span key={i} className="text-xs px-3 py-1 bg-white/5 rounded-full border border-white/5 text-gray-300 flex items-center gap-1">
302
+ <Info size={10} className="text-neon-yellow"/> {tip}
303
+ </span>
304
+ ))}
305
+ </div>
306
+ </div>
307
+ )}
308
+
309
+ <div className="flex flex-col md:flex-row items-center gap-8">
310
+ <div className="w-full md:w-48 h-48 flex-shrink-0"><GaugeChart value={riskResult.numericScore} /></div>
311
+ <div className="flex-1 bg-white/5 p-5 rounded-xl border border-white/5">
312
+ <h4 className="text-xs text-neon-blue font-bold uppercase mb-2">Assessment</h4>
313
+ <p className="text-sm text-gray-200 leading-relaxed">{riskResult.summary}</p>
314
+ {statusMessage && isAnalyzing && <p className="text-xs text-neon-green mt-2 animate-pulse">{statusMessage}</p>}
315
+ </div>
316
  </div>
317
+
318
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
319
+ <div className="glass-card p-4 rounded-xl">
320
+ <h4 className="text-xs font-mono text-gray-500 uppercase mb-3 font-bold">Action Plan</h4>
321
+ <ul className="space-y-2">
322
+ {riskResult.actionItems.map((item, i) => (
323
+ <li key={i} className="flex items-start gap-2 text-sm text-gray-300"><span className="text-neon-green mt-1">✓</span> {item}</li>
324
+ ))}
325
+ </ul>
326
+ </div>
327
+ <div className="glass-card p-4 rounded-xl">
328
+ <h4 className="text-xs font-mono text-gray-500 uppercase mb-3 font-bold flex items-center gap-2"><ShieldCheck size={14} className="text-purple-400"/> Coding Pipeline</h4>
329
+ <div className="space-y-2 max-h-40 overflow-y-auto pr-1 scrollbar-hide">
330
+ {riskResult.codingPipeline?.map((code, i) => (
331
+ <div key={i} className="flex justify-between items-center text-xs border-b border-white/5 pb-1 last:border-0">
332
+ <span className="text-gray-400 truncate max-w-[150px]" title={code.description}>{code.description}</span>
333
+ <span className="font-mono text-purple-300 bg-purple-500/10 px-1.5 py-0.5 rounded">{code.code}</span>
334
+ </div>
335
+ ))}
336
+ </div>
337
+ </div>
338
  </div>
339
+ </div>
340
+ ) : (
341
+ <div className="flex-1 flex flex-col items-center justify-center text-gray-600 border-2 border-dashed border-white/10 rounded-xl bg-black/20">
342
+ <Activity size={48} className="mb-4 opacity-30" />
343
+ <p className="font-medium text-lg text-gray-500">Awaiting Clinical Data</p>
344
+ {statusMessage && isAnalyzing && <p className="text-xs text-neon-green mt-2 animate-pulse">{statusMessage}</p>}
345
+ </div>
346
+ )}
347
  </div>
348
+ </div>
349
+ </div>
350
+
351
+ {/* EXTENDED PROFILE MODAL */}
352
+ {showProfileModal && (
353
+ <div className="fixed inset-0 z-[60] flex items-center justify-center bg-black/90 backdrop-blur-sm p-4 overflow-y-auto">
354
+ <div className="glass-panel w-full max-w-4xl p-6 rounded-2xl border border-neon-blue/30 relative animate-in zoom-in-95 my-auto">
355
+ <button onClick={() => setShowProfileModal(false)} className="absolute top-4 right-4 text-gray-500 hover:text-white"><X size={20} /></button>
356
+ <h2 className="text-xl font-bold text-white mb-6 flex items-center gap-2"><User className="text-neon-blue" size={20} /> Comprehensive Patient Profile</h2>
357
+
358
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-8 max-h-[70vh] overflow-y-auto pr-2 custom-scrollbar">
359
+ {/* BASIC */}
360
+ <section className="space-y-4">
361
+ <h3 className="text-xs font-bold text-neon-green uppercase border-b border-white/10 pb-1">Basic Info</h3>
362
+ <div className="grid grid-cols-2 gap-4">
363
+ <div><label className="text-[10px] uppercase text-gray-500 font-bold">Name</label><input value={localProfile.name} onChange={e => setLocalProfile({...localProfile, name: e.target.value})} className="w-full bg-black/40 border border-white/10 rounded p-2 text-white text-sm" /></div>
364
+ <div><label className="text-[10px] uppercase text-gray-500 font-bold">Age</label><input type="number" value={localProfile.age} onChange={e => setLocalProfile({...localProfile, age: parseInt(e.target.value)})} className="w-full bg-black/40 border border-white/10 rounded p-2 text-white text-sm" /></div>
365
+ <div><label className="text-[10px] uppercase text-gray-500 font-bold">Gender</label><select value={localProfile.gender} onChange={e => setLocalProfile({...localProfile, gender: e.target.value})} className="w-full bg-black/40 border border-white/10 rounded p-2 text-white text-sm"><option>Male</option><option>Female</option><option>Other</option><option>Prefer not to say</option></select></div>
366
+ <div><label className="text-[10px] uppercase text-gray-500 font-bold">Blood Group</label><input value={localProfile.bloodGroup} onChange={e => setLocalProfile({...localProfile, bloodGroup: e.target.value})} className="w-full bg-black/40 border border-white/10 rounded p-2 text-white text-sm" /></div>
367
+ </div>
368
+ </section>
369
+
370
+ {/* EMERGENCY */}
371
+ <section className="space-y-4">
372
+ <h3 className="text-xs font-bold text-neon-red uppercase border-b border-white/10 pb-1 flex items-center gap-2"><Phone size={12}/> Emergency Contact</h3>
373
+ <div className="grid grid-cols-2 gap-4">
374
+ <div><label className="text-[10px] uppercase text-gray-500 font-bold">Contact Name</label><input value={localProfile.emergencyContactName} onChange={e => setLocalProfile({...localProfile, emergencyContactName: e.target.value})} className="w-full bg-black/40 border border-white/10 rounded p-2 text-white text-sm" /></div>
375
+ <div><label className="text-[10px] uppercase text-gray-500 font-bold">Relation</label><input value={localProfile.emergencyContactRelation} onChange={e => setLocalProfile({...localProfile, emergencyContactRelation: e.target.value})} className="w-full bg-black/40 border border-white/10 rounded p-2 text-white text-sm" /></div>
376
+ <div className="col-span-2"><label className="text-[10px] uppercase text-gray-500 font-bold">Phone Number</label><input value={localProfile.emergencyContactPhone} onChange={e => setLocalProfile({...localProfile, emergencyContactPhone: e.target.value})} className="w-full bg-black/40 border border-white/10 rounded p-2 text-white text-sm" /></div>
377
+ </div>
378
+ </section>
379
+
380
+ {/* MEDICAL */}
381
+ <section className="space-y-4">
382
+ <h3 className="text-xs font-bold text-neon-blue uppercase border-b border-white/10 pb-1 flex items-center gap-2"><Stethoscope size={12}/> Medical History</h3>
383
+ <div><label className="text-[10px] uppercase text-gray-500 font-bold">Primary Condition</label><input value={localProfile.condition} onChange={e => setLocalProfile({...localProfile, condition: e.target.value})} className="w-full bg-black/40 border border-white/10 rounded p-2 text-white text-sm" /></div>
384
+ <div><label className="text-[10px] uppercase text-gray-500 font-bold">Past Conditions</label><textarea value={localProfile.history} onChange={e => setLocalProfile({...localProfile, history: e.target.value})} className="w-full h-16 bg-black/40 border border-white/10 rounded p-2 text-white text-sm resize-none" /></div>
385
+ <div><label className="text-[10px] uppercase text-gray-500 font-bold">Surgeries/Procedures</label><textarea value={localProfile.surgeries} onChange={e => setLocalProfile({...localProfile, surgeries: e.target.value})} className="w-full h-16 bg-black/40 border border-white/10 rounded p-2 text-white text-sm resize-none" /></div>
386
+ <div><label className="text-[10px] uppercase text-gray-500 font-bold">Allergies</label><textarea value={localProfile.allergies} onChange={e => setLocalProfile({...localProfile, allergies: e.target.value})} className="w-full h-16 bg-black/40 border border-white/10 rounded p-2 text-white text-sm resize-none" /></div>
387
+ </section>
388
+
389
+ {/* LIFESTYLE */}
390
+ <section className="space-y-4">
391
+ <h3 className="text-xs font-bold text-neon-yellow uppercase border-b border-white/10 pb-1 flex items-center gap-2"><Utensils size={12}/> Lifestyle</h3>
392
+ <div className="grid grid-cols-2 gap-4">
393
+ <div><label className="text-[10px] uppercase text-gray-500 font-bold">Diet</label><select value={localProfile.diet} onChange={e => setLocalProfile({...localProfile, diet: e.target.value as any})} className="w-full bg-black/40 border border-white/10 rounded p-2 text-white text-sm"><option>Omnivore</option><option>Vegetarian</option><option>Vegan</option><option>Keto</option><option>Other</option></select></div>
394
+ <div><label className="text-[10px] uppercase text-gray-500 font-bold">Exercise</label><select value={localProfile.exerciseFrequency} onChange={e => setLocalProfile({...localProfile, exerciseFrequency: e.target.value as any})} className="w-full bg-black/40 border border-white/10 rounded p-2 text-white text-sm"><option>Sedentary</option><option>Light</option><option>Moderate</option><option>Active</option></select></div>
395
+ <div><label className="text-[10px] uppercase text-gray-500 font-bold flex items-center gap-1"><Cigarette size={10}/> Smoking</label><select value={localProfile.smokingStatus} onChange={e => setLocalProfile({...localProfile, smokingStatus: e.target.value as any})} className="w-full bg-black/40 border border-white/10 rounded p-2 text-white text-sm"><option>Never</option><option>Former</option><option>Current</option></select></div>
396
+ <div><label className="text-[10px] uppercase text-gray-500 font-bold">Alcohol</label><select value={localProfile.alcoholConsumption} onChange={e => setLocalProfile({...localProfile, alcoholConsumption: e.target.value as any})} className="w-full bg-black/40 border border-white/10 rounded p-2 text-white text-sm"><option>None</option><option>Occasional</option><option>Regular</option></select></div>
397
+ </div>
398
+ <div><label className="text-[10px] uppercase text-gray-500 font-bold">Family History</label><textarea value={localProfile.familyHistory} onChange={e => setLocalProfile({...localProfile, familyHistory: e.target.value})} className="w-full h-16 bg-black/40 border border-white/10 rounded p-2 text-white text-sm resize-none" /></div>
399
+ </section>
400
+ </div>
401
+ <div className="mt-6 flex justify-end">
402
+ <button onClick={saveProfile} className="bg-neon-blue text-black font-bold px-6 py-2 rounded-lg hover:bg-neon-blue/80 flex items-center gap-2"><Check size={16} /> Save Profile</button>
403
+ </div>
404
+ </div>
405
+ </div>
406
+ )}
407
+
408
+ <ReportView profile={profile} vitals={vitals} riskResult={riskResult} chatHistory={chatHistory} chatSummary={chatSummary} medications={medications} />
409
  </div>
410
+ );
 
 
 
411
  };
412
 
413
  export default Dashboard;