thors1's picture
Initial DeepSite commit
8fb2fe8 verified
class HealeoApp {
constructor() {
this.currentPatient = null;
this.currentTab = 'overview';
this.currentFilter = 'all';
this.recommendations = [];
this.approvalHistory = [];
this.patients = [
{
id: 1,
name: "John Doe",
age: 58,
gender: "Male",
avatar: "JD",
conditions: ["Type 2 Diabetes", "Hypertension"],
lastVisit: "2024-01-15",
nextFollowup: "2024-02-01",
adherence: 87,
risk: "Moderate",
status: "active",
carePlan: {
activeConditions: [
{ condition: "Type 2 Diabetes", severity: "moderate", status: "managed" },
{ condition: "Hypertension", severity: "mild", status: "improving" }
],
medications: [
{ name: "Metformin", dosage: "1000mg", frequency: "Twice daily", adherence: 95 },
{ name: "Lisinopril", dosage: "10mg", frequency: "Once daily", adherence: 82 }
],
goals: [
{ text: "Reduce HbA1c to <7%", target: "6 months", progress: 65 },
{ text: "Blood pressure <130/80", target: "3 months", progress: 45 }
],
lifestyle: [
"Mediterranean diet - strict adherence",
"30min walking 5x/week",
"Sleep hygiene protocol"
]
},
labs: [
{ date: "2024-01-15", type: "HbA1c", value: "7.2", unit: "%", trend: "down" },
{ date: "2024-01-15", type: "Blood Pressure", value: "138/85", unit: "mmHg", trend: "stable" }
]
},
{
id: 2,
name: "Sarah Chen",
age: 34,
gender: "Female",
avatar: "SC",
conditions: ["Asthma", "Anxiety Disorder"],
lastVisit: "2024-01-10",
nextFollowup: "2024-01-25",
adherence: 92,
risk: "Low",
status: "stable",
carePlan: {
activeConditions: [
{ condition: "Asthma", severity: "moderate", status: "controlled" },
{ condition: "Anxiety Disorder", severity: "mild", status: "managed" }
],
medications: [
{ name: "Albuterol", dosage: "90mcg", frequency: "As needed", adherence: 88 },
{ name: "Sertraline", dosage: "50mg", frequency: "Once daily", adherence: 98 }
],
goals: [
{ text: "Reduce anxiety attacks to <2/month", target: "Ongoing", progress: 80 },
{ text: "Peak flow >350 L/min", target: "Maintenance", progress: 90 }
],
lifestyle: [
"Daily meditation practice",
"Yoga 3x/week",
"Air quality monitoring"
]
},
labs: [
{ date: "2024-01-10", type: "Spirometry", value: "85%", unit: "predicted", trend: "up" }
]
},
{
id: 3,
name: "Robert Martinez",
age: 67,
gender: "Male",
avatar: "RM",
conditions: ["COPD", "Heart Failure"],
lastVisit: "2024-01-08",
nextFollowup: "2024-01-22",
adherence: 73,
risk: "High",
status: "critical",
carePlan: {
activeConditions: [
{ condition: "COPD", severity: "severe", status: "worsening" },
{ condition: "Heart Failure", severity: "moderate", status: "stable" }
],
medications: [
{ name: "Furosemide", dosage: "40mg", frequency: "Once daily", adherence: 65 },
{ name: "Spironolactone", dosage: "25mg", frequency: "Once daily", adherence: 70 }
],
goals: [
{ text: "Reduce ED visits", target: "6 months", progress: 30 },
{ text: "Weight stability ±2lbs", target: "Monthly", progress: 60 }
],
lifestyle: [
"Sodium restriction <2g/day",
"Daily weight monitoring",
"Pulmonary rehab 2x/week"
]
},
labs: [
{ date: "2024-01-08", type: "BNP", value: "450", unit: "pg/mL", trend: "up" },
{ date: "2024-01-08", type: "eGFR", value: "45", unit: "mL/min", trend: "down" }
]
},
{
id: 4,
name: "Emily Watson",
age: 45,
gender: "Female",
avatar: "EW",
conditions: ["Rheumatoid Arthritis"],
lastVisit: "2024-01-12",
nextFollowup: "2024-02-12",
adherence: 96,
risk: "Low",
status: "review",
carePlan: {
activeConditions: [
{ condition: "Rheumatoid Arthritis", severity: "moderate", status: "improving" }
],
medications: [
{ name: "Methotrexate", dosage: "15mg", frequency: "Weekly", adherence: 96 },
{ name: "Folic Acid", dosage: "1mg", frequency: "Daily", adherence: 100 }
],
goals: [
{ text: "DAS28 <3.2", target: "3 months", progress: 75 },
{ text: "Pain score <4/10", target: "Ongoing", progress: 60 }
],
lifestyle: [
"Joint protection techniques",
"Aquatic therapy",
"Anti-inflammatory diet"
]
},
labs: [
{ date: "2024-01-12", type: "CRP", value: "8.5", unit: "mg/L", trend: "down" }
]
}
];
this.aiRecommendations = [
{
id: 1,
patientId: 1,
type: "medication",
title: "Dosage Adjustment Suggested",
description: "Increase Metformin to 1500mg based on current HbA1c trending and BMI.",
rationale: "Current HbA1c of 7.2% suggests suboptimal glycemic control. Patient BMI 32 indicates insulin resistance.",
confidence: 92,
action: "Adjust prescription",
timestamp: "10 mins ago"
},
{
id: 2,
patientId: 1,
type: "followup",
title: "Follow-up Timeline Update",
description: "Move next visit from 2/1 to 1/28 for earlier intervention.",
rationale: "Recent lab trends show glucose variability. Earlier assessment recommended.",
confidence: 87,
action: "Reschedule visit",
timestamp: "15 mins ago"
},
{
id: 3,
patientId: 1,
type: "monitoring",
title: "Add Continuous Glucose Monitor",
description: "Recommend CGM for 2 weeks to assess glycemic patterns.",
rationale: "Identifying fasting vs post-prandial spikes will optimize medication timing.",
confidence: 95,
action: "Order device",
timestamp: "20 mins ago"
}
];
this.timeline = [
{ date: "2024-01-15", type: "visit", title: "Routine Visit", description: "HbA1c discussed, adherence reviewed", icon: "stethoscope" },
{ date: "2024-01-10", type: "medication", title: "Medication Refill", description: "Metformin 90-day supply issued", icon: "pill" },
{ date: "2024-01-05", type: "lab", title: "Lab Results", description: "HbA1c: 7.2% (down from 7.8%)", icon: "flask-conical", highlight: true },
{ date: "2023-12-20", type: "therapy", title: "Nutritionist Consult", description: "Mediterranean diet counseling", icon: "apple" },
{ date: "2023-12-01", type: "visit", title: "Emergency Visit", description: "Hypoglycemic episode resolved", icon: "alert-circle", highlight: true }
];
this.init();
}
init() {
this.renderPatientList();
this.selectPatient(this.patients[0]);
lucide.createIcons();
}
filterPatients(query) {
const filtered = this.patients.filter(p =>
p.name.toLowerCase().includes(query.toLowerCase()) ||
p.conditions.some(c => c.toLowerCase().includes(query.toLowerCase()))
);
this.renderPatientList(filtered);
}
setFilter(filter) {
this.currentFilter = filter;
document.querySelectorAll('.filter-btn').forEach(btn => {
if(btn.dataset.filter === filter) {
btn.classList.remove('bg-gray-100', 'text-gray-600', 'border-gray-200');
btn.classList.add('bg-blue-100', 'text-blue-700', 'border-blue-200');
} else {
btn.classList.add('bg-gray-100', 'text-gray-600', 'border-gray-200');
btn.classList.remove('bg-blue-100', 'text-blue-700', 'border-blue-200');
}
});
let filtered = this.patients;
if(filter === 'critical') filtered = this.patients.filter(p => p.status === 'critical');
if(filter === 'review') filtered = this.patients.filter(p => p.status === 'review');
this.renderPatientList(filtered);
}
renderPatientList(patients = this.patients) {
const container = document.getElementById('patientList');
container.innerHTML = '';
patients.forEach(patient => {
const card = document.createElement('div');
card.className = `p-3 rounded-lg border cursor-pointer transition-all hover:shadow-md ${this.currentPatient?.id === patient.id ? 'bg-blue-50 border-blue-300 shadow-sm' : 'bg-white border-gray-200 hover:border-blue-300'}`;
card.onclick = () => this.selectPatient(patient);
const statusColor = patient.status === 'critical' ? 'bg-red-100 text-red-700' : patient.status === 'review' ? 'bg-amber-100 text-amber-700' : 'bg-green-100 text-green-700';
card.innerHTML = `
<div class="flex items-start gap-3">
<div class="w-10 h-10 rounded-full bg-gradient-to-br from-blue-400 to-blue-600 flex items-center justify-center text-white font-bold text-sm shrink-0">
${patient.avatar}
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<h4 class="font-semibold text-gray-900 text-sm truncate">${patient.name}</h4>
<span class="px-2 py-0.5 text-xs font-medium rounded-full ${statusColor}">${patient.status}</span>
</div>
<p class="text-xs text-gray-500 mt-0.5">${patient.age}y • ${patient.gender}</p>
<div class="flex flex-wrap gap-1 mt-2">
${patient.conditions.slice(0, 2).map(c => `<span class="px-1.5 py-0.5 bg-gray-100 text-gray-600 text-xs rounded">${c}</span>`).join('')}
${patient.conditions.length > 2 ? `<span class="px-1.5 py-0.5 bg-gray-100 text-gray-600 text-xs rounded">+${patient.conditions.length - 2}</span>` : ''}
</div>
<div class="flex items-center gap-3 mt-2 text-xs text-gray-500">
<span class="flex items-center gap-1">
<i data-lucide="activity" class="w-3 h-3"></i>
${patient.adherence}% adherence
</span>
</div>
</div>
</div>
`;
container.appendChild(card);
});
document.getElementById('patientCount').textContent = patients.length;
lucide.createIcons();
}
selectPatient(patient) {
this.currentPatient = patient;
this.renderPatientList();
this.updatePatientHeader();
this.renderTabContent();
this.renderRecommendations();
this.showToast(`Loaded care plan for ${patient.name}`, 'success');
}
updatePatientHeader() {
const p = this.currentPatient;
document.getElementById('patientAvatar').textContent = p.avatar;
document.getElementById('patientName').textContent = p.name;
document.getElementById('patientMeta').textContent = `${p.age} years old • ${p.gender} • MRN: 00${p.id}2345`;
document.getElementById('lastVisit').textContent = p.lastVisit;
document.getElementById('nextFollowup').textContent = p.nextFollowup;
document.getElementById('adherenceScore').textContent = p.adherence + '%';
const riskColors = { 'Low': 'text-green-600', 'Moderate': 'text-yellow-600', 'High': 'text-red-600' };
const riskEl = document.getElementById('riskLevel');
riskEl.textContent = p.risk;
riskEl.className = `text-sm font-semibold ${riskColors[p.risk] || 'text-gray-600'}`;
const tagsContainer = document.getElementById('patientTags');
tagsContainer.innerHTML = p.conditions.map(c =>
`<span class="px-2 py-1 bg-blue-50 text-blue-700 text-xs font-medium rounded-md border border-blue-200">${c}</span>`
).join('');
}
setTab(tab) {
this.currentTab = tab;
document.querySelectorAll('.tab-btn').forEach(btn => {
if(btn.dataset.tab === tab) {
btn.classList.add('border-blue-600', 'text-blue-600', 'active');
btn.classList.remove('border-transparent', 'text-gray-600');
} else {
btn.classList.remove('border-blue-600', 'text-blue-600', 'active');
btn.classList.add('border-transparent', 'text-gray-600');
}
});
this.renderTabContent();
}
renderTabContent() {
const container = document.getElementById('tabContent');
container.innerHTML = '';
if(this.currentTab === 'overview') this.renderOverview(container);
else if(this.currentTab === 'timeline') this.renderTimeline(container);
else if(this.currentTab === 'history') this.renderHistory(container);
else if(this.currentTab === 'labs') this.renderLabs(container);
lucide.createIcons();
}
renderOverview(container) {
const plan = this.currentPatient.carePlan;
const sections = [
{ id: 'conditions', title: 'Active Conditions', icon: 'activity', content: this.renderConditions(plan.activeConditions) },
{ id: 'medications', title: 'Medication Plan', icon: 'pill', content: this.renderMedications(plan.medications) },
{ id: 'goals', title: 'Treatment Goals', icon: 'target', content: this.renderGoals(plan.goals) },
{ id: 'lifestyle', title: 'Lifestyle Recommendations', icon: 'heart', content: this.renderLifestyle(plan.lifestyle) }
];
sections.forEach(section => {
const card = document.createElement('div');
card.className = 'bg-white rounded-xl border border-gray-200 overflow-hidden';
card.innerHTML = `
<div class="px-4 py-3 border-b border-gray-100 bg-gray-50 flex items-center justify-between">
<div class="flex items-center gap-2">
<i data-lucide="${section.icon}" class="w-4 h-4 text-blue-600"></i>
<h3 class="font-semibold text-gray-900">${section.title}</h3>
</div>
<button onclick="this.closest('.bg-white').querySelector('.section-content').classList.toggle('hidden')" class="p-1 hover:bg-gray-200 rounded">
<i data-lucide="chevron-down" class="w-4 h-4 text-gray-500"></i>
</button>
</div>
<div class="section-content p-4">
${section.content}
</div>
`;
container.appendChild(card);
});
}
renderConditions(conditions) {
return `<div class="space-y-3">
${conditions.map(c => `
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg border border-gray-100 group hover:border-blue-200 transition-colors">
<div>
<h4 class="font-medium text-gray-900">${c.condition}</h4>
<p class="text-xs text-gray-500 mt-0.5">Severity: ${c.severity} • Status: ${c.status}</p>
</div>
<div class="flex items-center gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
<button class="p-1.5 text-blue-600 hover:bg-blue-50 rounded" title="Edit">
<i data-lucide="edit-2" class="w-4 h-4"></i>
</button>
</div>
</div>
`).join('')}
</div>`;
}
renderMedications(medications) {
return `<div class="space-y-3">
${medications.map(m => `
<div class="p-3 border border-gray-200 rounded-lg hover:border-blue-300 transition-colors">
<div class="flex items-center justify-between mb-2">
<h4 class="font-semibold text-gray-900">${m.name}</h4>
<span class="px-2 py-0.5 bg-green-50 text-green-700 text-xs font-medium rounded">${m.adherence}% adherence</span>
</div>
<div class="grid grid-cols-2 gap-2 text-sm text-gray-600 mb-2">
<span contenteditable="true" class="editable-field cursor-text border-b border-transparent hover:border-gray-300">${m.dosage}</span>
<span contenteditable="true" class="editable-field cursor-text border-b border-transparent hover:border-gray-300">${m.frequency}</span>
</div>
<div class="flex gap-2 mt-3">
<button class="px-3 py-1.5 bg-blue-50 text-blue-700 text-xs font-medium rounded hover:bg-blue-100 transition-colors">Adjust Dose</button>
<button class="px-3 py-1.5 bg-gray-50 text-gray-600 text-xs font-medium rounded hover:bg-gray-100 transition-colors">Refill</button>
</div>
</div>
`).join('')}
</div>`;
}
renderGoals(goals) {
return `<div class="space-y-4">
${goals.map(g => `
<div class="p-3 bg-gradient-to-r from-blue-50 to-indigo-50 rounded-lg border border-blue-100">
<div class="flex items-center justify-between mb-2">
<h4 class="font-medium text-gray-900" contenteditable="true" class="editable-field">${g.text}</h4>
<span class="text-xs text-gray-500 bg-white px-2 py-1 rounded border border-gray-200">${g.target}</span>
</div>
<div class="flex items-center gap-3">
<div class="flex-1 bg-white rounded-full h-2 border border-gray-200">
<div class="bg-gradient-to-r from-blue-500 to-indigo-600 h-2 rounded-full transition-all" style="width: ${g.progress}%"></div>
</div>
<span class="text-xs font-semibold text-blue-600 min-w-fit">${g.progress}%</span>
</div>
</div>
`).join('')}
</div>`;
}
renderLifestyle(items) {
return `<div class="space-y-2">
${items.map((item, idx) => `
<div class="flex items-start gap-3 p-3 bg-green-50 rounded-lg border border-green-100">
<i data-lucide="check-circle-2" class="w-5 h-5 text-green-600 shrink-0 mt-0.5"></i>
<div class="flex-1">
<p contenteditable="true" class="editable-field text-sm text-gray-800 leading-relaxed">${item}</p>
<div class="flex gap-2 mt-2">
<button class="text-xs text-green-700 hover:underline font-medium">Mark Complete</button>
<button class="text-xs text-gray-500 hover:underline">Modify</button>
</div>
</div>
</div>
`).join('')}
</div>`;
}
renderTimeline(container) {
const timelineHtml = `
<div class="relative pl-8 space-y-6">
<div class="timeline-line"></div>
${this.timeline.map((event, idx) => `
<div class="relative slide-in" style="animation-delay: ${idx * 0.1}s">
<div class="timeline-dot" style="top: 6px;"></div>
<div class="bg-white p-4 rounded-lg border border-gray-200 shadow-sm ml-4 hover:shadow-md transition-shadow">
<div class="flex items-start justify-between mb-2">
<div class="flex items-center gap-2">
<div class="p-1.5 ${event.highlight ? 'bg-blue-50 text-blue-600' : 'bg-gray-50 text-gray-600'} rounded-md">
<i data-lucide="${event.icon}" class="w-4 h-4"></i>
</div>
<div>
<h4 class="font-semibold text-gray-900 text-sm">${event.title}</h4>
<p class="text-xs text-gray-500">${event.date}</p>
</div>
</div>
${event.highlight ? '<span class="px-2 py-0.5 bg-blue-100 text-blue-700 text-xs font-medium rounded">Key Event</span>' : ''}
</div>
<p class="text-sm text-gray-600 pl-9">${event.description}</p>
<div class="flex gap-2 mt-3 pl-9">
<button class="text-xs text-blue-600 hover:text-blue-700 font-medium">View Details</button>
<button class="text-xs text-gray-400 hover:text-gray-600">• Add Note</button>
</div>
</div>
</div>
`).join('')}
</div>
<button onclick="app.addTimelineEvent()" class="w-full mt-6 py-3 border-2 border-dashed border-gray-300 rounded-lg text-gray-500 hover:border-blue-400 hover:text-blue-600 transition-colors text-sm font-medium flex items-center justify-center gap-2">
<i data-lucide="plus" class="w-4 h-4"></i>
Add Timeline Event
</button>
`;
container.innerHTML = timelineHtml;
}
renderHistory(container) {
container.innerHTML = `
<div class="space-y-4">
<div class="bg-white p-4 rounded-lg border border-gray-200">
<h3 class="font-semibold text-gray-900 mb-3">Care Plan Revisions</h3>
<div class="space-y-3">
<div class="flex items-start gap-3 pb-3 border-b border-gray-100">
<div class="w-8 h-8 bg-blue-50 rounded-full flex items-center justify-center shrink-0">
<i data-lucide="git-commit" class="w-4 h-4 text-blue-600"></i>
</div>
<div>
<p class="text-sm font-medium text-gray-900">Medication dosage updated</p>
<p class="text-xs text-gray-500 mt-1">By Dr. Smith • Jan 15, 2024 • 10:30 AM</p>
<div class="mt-2 p-2 bg-gray-50 rounded text-xs text-gray-600 font-mono">
Metformin: 500mg → 1000mg
</div>
</div>
</div>
<div class="flex items-start gap-3">
<div class="w-8 h-8 bg-green-50 rounded-full flex items-center justify-center shrink-0">
<i data-lucide="check" class="w-4 h-4 text-green-600"></i>
</div>
<div>
<p class="text-sm font-medium text-gray-900">AI Recommendation Approved</p>
<p class="text-xs text-gray-500 mt-1">By Dr. Smith • Jan 14, 2024 • 3:45 PM</p>
<p class="text-xs text-gray-600 mt-1">Added CGM monitoring for 14 days</p>
</div>
</div>
</div>
</div>
</div>
`;
}
renderLabs(container) {
const labs = this.currentPatient.labs || [];
container.innerHTML = `
<div class="grid gap-4">
${labs.map(lab => `
<div class="bg-white p-4 rounded-lg border border-gray-200 flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="p-2 bg-purple-50 rounded-lg">
<i data-lucide="flask-conical" class="w-5 h-5 text-purple-600"></i>
</div>
<div>
<h4 class="font-semibold text-gray-900">${lab.type}</h4>
<p class="text-xs text-gray-500">${lab.date}</p>
</div>
</div>
<div class="text-right">
<p class="text-2xl font-bold text-gray-900">${lab.value} <span class="text-sm font-normal text-gray-500">${lab.unit}</span></p>
<p class="text-xs ${lab.trend === 'up' ? 'text-red-500' : lab.trend === 'down' ? 'text-green-500' : 'text-gray-500'} flex items-center justify-end gap-1">
<i data-lucide="${lab.trend === 'up' ? 'trending-up' : lab.trend === 'down' ? 'trending-down' : 'minus'}" class="w-3 h-3"></i>
${lab.trend === 'up' ? 'Increased' : lab.trend === 'down' ? 'Decreased' : 'Stable'}
</p>
</div>
</div>
`).join('')}
<button onclick="app.requestLab()" class="w-full py-3 border-2 border-dashed border-gray-300 rounded-lg text-gray-500 hover:border-purple-400 hover:text-purple-600 transition-colors text-sm font-medium flex items-center justify-center gap-2">
<i data-lucide="plus" class="w-4 h-4"></i>
Request New Lab
</button>
</div>
`;
}
renderRecommendations() {
const container = document.getElementById('recommendationList');
const recs = this.aiRecommendations.filter(r => r.patientId === this.currentPatient?.id);
document.getElementById('pendingCount').textContent = recs.length;
if(recs.length === 0) {
container.innerHTML = `
<div class="p-4 text-center text-gray-500 bg-gray-50 rounded-lg border border-gray-200">
<i data-lucide="check-circle" class="w-8 h-8 mx-auto mb-2 text-green-500"></i>
<p class="text-sm font-medium">All recommendations reviewed</p>
<p class="text-xs mt-1">No pending AI suggestions</p>
</div>
`;
lucide.createIcons();
return;
}
container.innerHTML = recs.map(rec => `
<div class="bg-white rounded-lg border border-blue-200 overflow-hidden shadow-sm hover:shadow-md transition-all slide-in" id="rec-${rec.id}">
<div class="p-3 border-b border-blue-50 bg-gradient-to-r from-blue-50 to-indigo-50 flex items-center justify-between">
<div class="flex items-center gap-2">
<i data-lucide="${rec.type === 'medication' ? 'pill' : rec.type === 'followup' ? 'calendar-clock' : 'activity'}" class="w-4 h-4 text-blue-600"></i>
<span class="text-xs font-semibold text-blue-800 uppercase tracking-wide">${rec.type}</span>
</div>
<div class="flex items-center gap-1">
<div class="w-6 h-6 rounded-full bg-white border-2 border-blue-500 flex items-center justify-center text-xs font-bold text-blue-600">
${rec.confidence}
</div>
</div>
</div>
<div class="p-3">
<h4 class="font-semibold text-gray-900 text-sm mb-1">${rec.title}</h4>
<p class="text-xs text-gray-600 leading-relaxed mb-3">${rec.description}</p>
<div class="bg-gray-50 p-2 rounded border border-gray-200 mb-3">
<p class="text-xs text-gray-500 mb-1 font-medium">AI Rationale:</p>
<p class="text-xs text-gray-700 italic">${rec.rationale}</p>
</div>
<div class="flex gap-2">
<button onclick="app.approveRecommendation(${rec.id})" class="flex-1 py-1.5 bg-blue-600 text-white text-xs font-medium rounded hover:bg-blue-700 transition-colors flex items-center justify-center gap-1">
<i data-lucide="check" class="w-3 h-3"></i>
Approve
</button>
<button onclick="app.rejectRecommendation(${rec.id})" class="flex-1 py-1.5 bg-white text-gray-700 border border-gray-300 text-xs font-medium rounded hover:bg-gray-50 transition-colors flex items-center justify-center gap-1">
<i data-lucide="x" class="w-3 h-3"></i>
Reject
</button>
</div>
</div>
</div>
`).join('');
// Update confidence based on pending recommendations
const avgConfidence = recs.reduce((acc, r) => acc + r.confidence, 0) / recs.length;
this.updateConfidence(avgConfidence);
lucide.createIcons();
}
approveRecommendation(id) {
const rec = this.aiRecommendations.find(r => r.id === id);
if(!rec) return;
// Add to history
this.approvalHistory.unshift({
...rec,
status: 'approved',
timestamp: new Date().toLocaleTimeString()
});
// Remove from pending
this.aiRecommendations = this.aiRecommendations.filter(r => r.id !== id);
// Update UI
const card = document.getElementById(`rec-${id}`);
if(card) {
card.style.transform = 'translateX(100%)';
card.style.opacity = '0';
setTimeout(() => this.renderRecommendations(), 300);
}
this.showToast('Recommendation approved and applied to care plan', 'success');
this.updateApprovalHistory();
}
rejectRecommendation(id) {
const rec = this.aiRecommendations.find(r => r.id === id);
if(!rec) return;
this.approvalHistory.unshift({
...rec,
status: 'rejected',
timestamp: new Date().toLocaleTimeString()
});
this.aiRecommendations = this.aiRecommendations.filter(r => r.id !== id);
const card = document.getElementById(`rec-${id}`);
if(card) {
card.style.transform = 'translateX(100%)';
card.style.opacity = '0';
setTimeout(() => this.renderRecommendations(), 300);
}
this.showToast('Recommendation rejected', 'info');
this.updateApprovalHistory();
}
updateApprovalHistory() {
const container = document.getElementById('approvalHistory');
container.innerHTML = this.approvalHistory.slice(0, 3).map(h => `
<div class="p-2 bg-gray-50 rounded border border-gray-200 text-xs">
<div class="flex items-center justify-between mb-1">
<span class="font-medium text-gray-900">${h.title}</span>
<span class="px-1.5 py-0.5 rounded ${h.status === 'approved' ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'} text-xs font-bold uppercase">${h.status}</span>
</div>
<p class="text-gray-500">${h.timestamp}</p>
</div>
`).join('');
}
updateConfidence(score) {
document.getElementById('confidenceValue').textContent = Math.round(score) + '%';
document.getElementById('confidenceBar').style.width = score + '%';
document.getElementById('lastUpdated').textContent = 'Just now';
}
regenerateAI() {
const btn = document.getElementById('regenerateBtn');
const originalContent = btn.innerHTML;
btn.innerHTML = '<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i><span>Analyzing...</span>';
btn.disabled = true;
lucide.createIcons();
// Simulate AI processing
setTimeout(() => {
// Add new random recommendation
const newRec = {
id: Date.now(),
patientId: this.currentPatient.id,
type: 'monitoring',
title: 'New Symptom Pattern Detected',
description: 'AI detected irregular glucose patterns during sleep hours.',
rationale: 'Continuous monitoring data analysis reveals dawn phenomenon requiring adjustment.',
confidence: Math.floor(Math.random() * 15) + 85,
action: 'Review data',
timestamp: 'Just now'
};
this.aiRecommendations.push(newRec);
this.renderRecommendations();
btn.innerHTML = originalContent;
btn.disabled = false;
lucide.createIcons();
this.showToast('AI analysis complete. New recommendations available.', 'success');
}, 2000);
}
saveCarePlan() {
this.showToast('Care plan saved successfully', 'success');
}
addTimelineEvent() {
const today = new Date().toISOString().split('T')[0];
this.timeline.unshift({
date: today,
type: 'note',
title: 'New Clinical Note',
description: 'Added via care plan engine',
icon: 'file-text'
});
this.renderTabContent();
this.showToast('Timeline event added', 'success');
}
requestLab() {
this.showToast('Lab order submitted to LIS', 'info');
}
openAIChat() {
this.showToast('AI Assistant coming soon', 'info');
}
showToast(message, type = 'info') {
const container = document.getElementById('toastContainer');
const toast = document.createElement('div');
const colors = {
success: 'bg-green-500',
error: 'bg-red-500',
info: 'bg-blue-500',
warning: 'bg-amber-500'
};
const icons = {
success: 'check-circle',
error: 'x-circle',
info: 'info',
warning: 'alert-triangle'
};
toast.className = `${colors[type]} text-white px-4 py-3 rounded-lg shadow-lg flex items-center gap-3 min-w-[300px] toast-enter`;
toast.innerHTML = `
<i data-lucide="${icons[type]}" class="w-5 h-5"></i>
<div class="flex-1">
<p class="text-sm font-medium">${message}</p>
</div>
<button onclick="this.parentElement.remove()" class="hover:opacity-75">
<i data-lucide="x" class="w-4 h-4"></i>
</button>
`;
container.appendChild(toast);
lucide.createIcons();
setTimeout(() => toast.remove(), 5000);
}
}
// Initialize app
const app = new HealeoApp();