Investment Plans workspace
Open raw ↗
// HAD Digital MVP - Frontend Application
const API = '/api';
let currentUser = null;
let currentView = 'login';

// === API Helpers ===
async function api(method, path, body) {
  const opts = { method, headers: {'Content-Type':'application/json'}, credentials:'same-origin' };
  if (body) opts.body = JSON.stringify(body);
  const r = await fetch(API + path, opts);
  if (r.status === 401) { currentUser = null; render(); return null; }
  const data = await r.json();
  if (!r.ok) throw new Error(data.error || 'Request failed');
  return data;
}

// === Toast ===
function toast(msg, type='info') {
  const t = document.createElement('div');
  t.className = 'toast toast-' + type;
  t.textContent = msg;
  document.getElementById('toast-container').appendChild(t);
  setTimeout(() => t.remove(), 4000);
}

// === Routing ===
function navigate(view, params) {
  currentView = view;
  window._params = params || {};
  render();
}

// === Render ===
function render() {
  const app = document.getElementById('app');
  if (!currentUser) { app.innerHTML = renderLogin(); bindLogin(); return; }
  app.innerHTML = renderShell();
  bindShell();
  const content = document.getElementById('main-content');
  switch(currentView) {
    case 'dashboard': content.innerHTML = renderDashboard(); bindDashboard(); break;
    case 'report': content.innerHTML = renderReportForm(); bindReportForm(); break;
    case 'observation': content.innerHTML = renderObservationForm(); bindObservationForm(); break;
    case 'timeline': content.innerHTML = renderTimeline(); bindTimeline(); break;
    case 'alerts': content.innerHTML = renderAlerts(); bindAlerts(); break;
    case 'treatment': content.innerHTML = renderTreatmentPlan(); bindTreatmentPlan(); break;
    case 'messages': content.innerHTML = renderMessages(); bindMessages(); break;
    case 'export': content.innerHTML = renderExport(); bindExport(); break;
    case 'patients': content.innerHTML = renderPatients(); bindPatients(); break;
    case 'patient-detail': content.innerHTML = renderPatientDetail(); bindPatientDetail(); break;
    default: content.innerHTML = renderDashboard(); bindDashboard();
  }
}

// === Login ===
function renderLogin() {
  return '<div class="login-page"><div class="login-card">'
    + '<div class="login-header"><h1>HAD Digital</h1><p>Oncology Home Care Platform</p></div>'
    + '<form id="login-form">'
    + '<div class="form-group"><label>Username</label><input type="text" id="username" autocomplete="username" required></div>'
    + '<div class="form-group"><label>Password</label><input type="password" id="password" autocomplete="current-password" required></div>'
    + '<button type="submit" class="btn btn-primary btn-block">Sign In</button>'
    + '<div id="login-error" class="error-text"></div>'
    + '</form>'
    + '<div class="login-demo"><strong>Demo Credentials:</strong><br>'
    + 'Admin: admin / admin123<br>'
    + 'Oncologist: dr.martin / demo123<br>'
    + 'HAD Nurse: inf.moret / demo123<br>'
    + 'Patient: patient.durand / demo123</div>'
    + '</div></div>';
}

function bindLogin() {
  document.getElementById('login-form').onsubmit = async (e) => {
    e.preventDefault();
    try {
      const data = await api('POST', '/login', {
        username: document.getElementById('username').value,
        password: document.getElementById('password').value
      });
      if (data && (data.ok || data.user)) { currentUser = data.user || data; navigate('dashboard'); }
      else document.getElementById('login-error').textContent = data.error || 'Login failed';
    } catch(err) { document.getElementById('login-error').textContent = err.message; }
  };
}

// === Shell (Navigation) ===
function renderShell() {
  const role = currentUser?.role || 'patient';
  const isPatient = role === 'patient' || role === 'caregiver';
  const navItems = isPatient
    ? [{v:'dashboard',i:'&#9632;',l:'Home'},{v:'report',i:'&#10010;',l:'Report Symptoms'},{v:'timeline',i:'&#9776;',l:'Timeline'},{v:'treatment',i:'&#9733;',l:'Treatment'},{v:'messages',i:'&#9993;',l:'Messages'}]
    : [{v:'dashboard',i:'&#9632;',l:'Dashboard'},{v:'patients',i:'&#9823;',l:'Patients'},{v:'observation',i:'&#10010;',l:'New Observation'},{v:'alerts',i:'&#9888;',l:'Alerts'},{v:'timeline',i:'&#9776;',l:'Timeline'},{v:'treatment',i:'&#9733;',l:'Treatment'},{v:'messages',i:'&#9993;',l:'Messages'},{v:'export',i:'&#128196;',l:'Export'}];
  let nav = navItems.map(n => '<a href="#" class="nav-item'+(currentView===n.v?' active':'')+'" data-view="'+n.v+'">'+n.i+' <span>'+n.l+'</span></a>').join('');
  return '<div class="app-shell">'
    + '<header class="app-header"><div class="header-brand"><h1>HAD Digital</h1></div>'
    + '<div class="header-user"><span class="user-role">'+role+'</span> <span class="user-name">'+(currentUser.display_name || currentUser.username || currentUser.user || 'User')+'</span>'
    + ' <button class="btn btn-sm btn-ghost" id="logout-btn">Logout</button></div></header>'
    + '<nav class="app-nav">'+nav+'</nav>'
    + '<main class="app-main"><div id="main-content" class="content-area"></div></main></div>';
}

function bindShell() {
  document.querySelectorAll('.nav-item').forEach(a => {
    a.onclick = (e) => { e.preventDefault(); navigate(a.dataset.view); };
  });
  document.getElementById('logout-btn').onclick = async () => {
    await api('POST', '/logout');
    currentUser = null; navigate('login');
  };
}

// === Dashboard ===
function renderDashboard() {
  const role = currentUser?.role || 'patient';
  const isPatient = role === 'patient' || role === 'caregiver';
  if (isPatient) return renderPatientDashboard();
  return renderClinicianDashboard();
}

function renderPatientDashboard() {
  const name = currentUser?.display_name || currentUser?.username || currentUser?.user || 'Patient';
  return '<div class="dashboard">'
    + '<div class="welcome-banner"><h2>Welcome, ' + name + '</h2><p>How are you feeling today?</p></div>'
    + '<div class="quick-actions">'
    + '<button class="action-card action-primary" onclick="navigate(\'report\')"><span class="action-icon">&#10010;</span><span class="action-label">Report Symptoms</span></button>'
    + '<button class="action-card" onclick="navigate(\'timeline\')"><span class="action-icon">&#9776;</span><span class="action-label">View Timeline</span></button>'
    + '<button class="action-card" onclick="navigate(\'treatment\')"><span class="action-icon">&#9733;</span><span class="action-label">Treatment Plan</span></button>'
    + '</div>'
    + '<div class="dashboard-grid"><div class="card"><h3>Recent Reports</h3><div id="recent-reports"><p class="text-muted">Loading...</p></div></div>'
    + '<div class="card"><h3>Active Alerts</h3><div id="active-alerts"><p class="text-muted">Loading...</p></div></div></div></div>';
}

function renderClinicianDashboard() {
  const name = currentUser?.display_name || currentUser?.username || currentUser?.user || 'Clinician';
  return '<div class="dashboard">'
    + '<div class="welcome-banner"><h2>Clinician Dashboard</h2><p>Welcome, ' + name + '</p></div>'
    + '<div class="stats-grid">'
    + '<div class="stat-card"><div class="stat-value" id="stat-patients">-</div><div class="stat-label">Active Patients</div></div>'
    + '<div class="stat-card stat-urgent"><div class="stat-value" id="stat-alerts">-</div><div class="stat-label">Pending Alerts</div></div>'
    + '<div class="stat-card"><div class="stat-value" id="stat-reports">-</div><div class="stat-label">Reports Today</div></div>'
    + '</div>'
    + '<div class="dashboard-grid"><div class="card"><h3>Active Alerts</h3><div id="alert-list"><p class="text-muted">Loading...</p></div></div>'
    + '<div class="card"><h3>Recent Activity</h3><div id="recent-activity"><p class="text-muted">Loading...</p></div></div></div></div>';
}

async function bindDashboard() {
  try {
    const alerts = await api('GET', '/alerts');
    if (alerts && alerts.alerts) {
      const alertDiv = document.getElementById('alert-list') || document.getElementById('active-alerts');
      if (alertDiv) {
        if (alerts.alerts.length === 0) { alertDiv.innerHTML = '<p class="text-muted">No active alerts</p>'; }
        else { alertDiv.innerHTML = alerts.alerts.slice(0,10).map(a => '<div class="alert-item alert-'+a.tier+'"><span class="alert-tier">'+a.tier+'</span><span class="alert-text">'+a.message+'</span><span class="alert-time">'+formatDate(a.created_at)+'</span></div>').join(''); }
      }
      const statAlerts = document.getElementById('stat-alerts');
      if (statAlerts) statAlerts.textContent = alerts.alerts.length;
    }
  } catch(e) { console.error('Dashboard load error:', e); }
}

// === Report Form (Patient Self-Report) ===
function renderReportForm() {
  return '<div class="page-header"><h2>Report Symptoms</h2><p>Report any symptoms you are experiencing</p></div>'
    + '<form id="report-form" class="clinical-form">'
    + '<div class="form-section"><h3>Gastrointestinal</h3>'
    + renderSymptomQuestion('nausea', 'Nausea', ['None','Mild','Moderate','Severe'])
    + renderSymptomQuestion('vomiting', 'Vomiting', ['None','1-2 episodes/day','3-5 episodes/day','>6 episodes/day'])
    + renderSymptomQuestion('diarrhea', 'Diarrhea', ['None','2-3 stools/day','4-6 stools/day','>7 stools/day'])
    + '</div>'
    + '<div class="form-section"><h3>Hematologic</h3>'
    + renderSymptomQuestion('fatigue', 'Fatigue', ['None','Mild fatigue','Moderate fatigue','Severe fatigue'])
    + renderSymptomQuestion('bleeding', 'Bleeding/Bruising', ['None','Minor','Moderate','Severe'])
    + renderSymptomQuestion('infection_signs', 'Signs of Infection', ['None','Mild fever','Fever >38.5C','Fever with chills'])
    + '</div>'
    + '<div class="form-section"><h3>Dermatologic</h3>'
    + renderSymptomQuestion('rash', 'Skin Rash', ['None','Mild rash','Moderate rash','Severe rash'])
    + renderSymptomQuestion('skin_changes', 'Skin Changes', ['None','Dryness','Peeling','Ulceration'])
    + '</div>'
    + '<div class="form-section"><h3>Neurologic</h3>'
    + renderSymptomQuestion('numbness', 'Numbness/Tingling', ['None','Mild','Moderate','Severe'])
    + renderSymptomQuestion('weakness', 'Muscle Weakness', ['None','Mild','Moderate','Severe'])
    + '</div>'
    + '<div class="form-section"><h3>Constitutional</h3>'
    + renderSymptomQuestion('fever', 'Fever', ['None','37.5-38C','38-39C','>39C'])
    + renderSymptomQuestion('weight_loss', 'Weight Loss', ['None','<5%','5-10%','>10%'])
    + '</div>'
    + '<div class="form-group"><label>Additional Notes</label><textarea id="report-notes" rows="3" placeholder="Describe any other symptoms..."></textarea></div>'
    + '<button type="submit" class="btn btn-primary btn-lg">Submit Report</button></form>';
}

function renderSymptomQuestion(id, label, options) {
  let html = '<div class="symptom-question"><label class="symptom-label">' + label + '</label><div class="symptom-options">';
  options.forEach((opt, i) => {
    html += '<label class="radio-card"><input type="radio" name="symptom_'+id+'" value="'+i+'"'+(i===0?' checked':'')+'>'
      + '<span class="radio-card-body">'+opt+'</span></label>';
  });
  html += '</div></div>';
  return html;
}

function bindReportForm() {
  document.getElementById('report-form').onsubmit = async (e) => {
    e.preventDefault();
    const symptoms = {};
    const form = e.target;
    ['nausea','vomiting','diarrhea','fatigue','bleeding','infection_signs','rash','skin_changes','numbness','weakness','fever','weight_loss'].forEach(s => {
      const val = form.querySelector('input[name="symptom_'+s+'"]:checked');
      symptoms[s] = val ? parseInt(val.value) : 0;
    });
    try {
      const result = await api('POST', '/reports', { symptoms, notes: document.getElementById('report-notes').value });
      if (result) {
        showGradingResult(result);
      }
    } catch(err) { toast(err.message, 'error'); }
  };
}

function showGradingResult(result) {
  let gradesHtml = '';
  if (result.grades && result.grades.length > 0) {
    gradesHtml = '<div class="grading-results"><h3>Assessment Results</h3>';
    result.grades.forEach(g => {
      const gradeClass = g.grade >= 3 ? 'grade-high' : g.grade >= 2 ? 'grade-medium' : 'grade-low';
      gradesHtml += '<div class="grade-item '+gradeClass+'"><span class="grade-term">'+g.term+'</span>'
        + '<span class="grade-value">Grade '+g.grade+'</span>'
        + (g.provisional ? '<span class="grade-provisional">Provisional</span>' : '')
        + '</div>';
    });
    gradesHtml += '</div>';
  }
  if (result.guidance) {
    gradesHtml += '<div class="guidance-box"><h3>Guidance</h3><p>'+result.guidance+'</p></div>';
  }
  if (result.alert) {
    gradesHtml += '<div class="alert-box alert-'+result.alert.tier+'"><h3>Alert: '+result.alert.tier.toUpperCase()+'</h3><p>'+result.alert.message+'</p></div>';
  }
  const modal = document.createElement('div');
  modal.className = 'modal-overlay';
  modal.innerHTML = '<div class="modal"><div class="modal-header"><h2>Report Submitted</h2><button class="modal-close">&times;</button></div>'
    + '<div class="modal-body">' + gradesHtml + '</div>'
    + '<div class="modal-footer"><button class="btn btn-primary" onclick="this.closest(\'.modal-overlay\').remove();navigate(\'dashboard\')">Done</button></div></div>';
  document.getElementById('modal-root').appendChild(modal);
  modal.querySelector('.modal-close').onclick = () => modal.remove();
}

// === Observation Form (Clinician) ===
function renderObservationForm() {
  return '<div class="page-header"><h2>Clinician Observation</h2><p>Record clinical findings from home visit</p></div>'
    + '<form id="observation-form" class="clinical-form">'
    + '<div class="form-section"><h3>Patient</h3>'
    + '<div class="form-group"><label>Select Patient</label><select id="obs-patient"><option value="">Loading patients...</option></select></div>'
    + '</div>'
    + '<div class="form-section"><h3>Vital Signs</h3>'
    + '<div class="form-row"><div class="form-group"><label>Temperature (°C)</label><input type="number" id="obs-temp" step="0.1" min="35" max="42" placeholder="36.5"></div>'
    + '<div class="form-group"><label>Blood Pressure</label><input type="text" id="obs-bp" placeholder="120/80"></div>'
    + '<div class="form-group"><label>Heart Rate (bpm)</label><input type="number" id="obs-hr" min="30" max="200" placeholder="72"></div>'
    + '<div class="form-group"><label>SpO2 (%)</label><input type="number" id="obs-spo2" min="50" max="100" placeholder="98"></div></div>'
    + '<div class="form-group"><label>Weight (kg)</label><input type="number" id="obs-weight" step="0.1" min="20" max="200" placeholder="70"></div>'
    + '</div>'
    + '<div class="form-section"><h3>Observed Toxicity Signs</h3>'
    + renderSymptomQuestion('obs_nausea', 'Nausea', ['None','Mild','Moderate','Severe'])
    + renderSymptomQuestion('obs_mucositis', 'Mucositis', ['None','Erythema','Ulceration','Unable to eat'])
    + renderSymptomQuestion('obs_skin', 'Skin Toxicity', ['None','Rash','Desquamation','Necrosis'])
    + '</div>'
    + '<div class="form-group"><label>Clinical Notes</label><textarea id="obs-notes" rows="4" placeholder="Additional clinical observations..."></textarea></div>'
    + '<button type="submit" class="btn btn-primary btn-lg">Submit Observation</button></form>';
}

async function bindObservationForm() {
  try {
    const data = await api('GET', '/patients');
    const sel = document.getElementById('obs-patient');
    if (data && data.patients) {
      sel.innerHTML = '<option value="">Select patient...</option>' + data.patients.map(p => '<option value="'+p.id+'">'+p.name+'</option>').join('');
    }
  } catch(e) { console.error(e); }
  document.getElementById('observation-form').onsubmit = async (e) => {
    e.preventDefault();
    try {
      const symptoms = {};
      ['obs_nausea','obs_mucositis','obs_skin'].forEach(s => {
        const val = e.target.querySelector('input[name="symptom_'+s+'"]:checked');
        symptoms[s.replace('obs_','')] = val ? parseInt(val.value) : 0;
      });
      await api('POST', '/reports', {
        patient_id: document.getElementById('obs-patient').value,
        symptoms,
        vital_signs: {
          temp: document.getElementById('obs-temp').value,
          bp: document.getElementById('obs-bp').value,
          hr: document.getElementById('obs-hr').value,
          spo2: document.getElementById('obs-spo2').value,
          weight: document.getElementById('obs-weight').value
        },
        notes: document.getElementById('obs-notes').value,
        type: 'clinician_observation'
      });
      toast('Observation submitted successfully', 'success');
      navigate('dashboard');
    } catch(err) { toast(err.message, 'error'); }
  };
}

// === Timeline ===
function renderTimeline() {
  return '<div class="page-header"><h2>Care Coordination Timeline</h2></div>'
    + '<div class="filter-bar"><select id="timeline-filter"><option value="">All Events</option><option value="report">Reports</option><option value="observation">Observations</option><option value="alert">Alerts</option><option value="grade">Grades</option><option value="message">Messages</option></select></div>'
    + '<div id="timeline-container"><p class="text-muted">Loading timeline...</p></div>';
}

async function bindTimeline() {
  try {
    const data = await api('GET', '/timeline');
    const container = document.getElementById('timeline-container');
    if (data && data.events && data.events.length > 0) {
      renderTimelineEvents(data.events);
    } else {
      container.innerHTML = '<p class="text-muted">No events found</p>';
    }
    const filterEl = document.getElementById('timeline-filter');
    if (filterEl) {
      filterEl.onchange = async (e) => {
        const filter = e.target.value;
        try {
          const d = await api('GET', '/timeline' + (filter ? '?type=' + filter : ''));
          if (d && d.events) renderTimelineEvents(d.events);
        } catch(err) { toast(err.message, 'error'); }
      };
    }
  } catch(e) { document.getElementById('timeline-container').innerHTML = '<p class="text-danger">Failed to load timeline</p>'; }
}

function renderTimelineEvents(events) {
  const container = document.getElementById('timeline-container');
  if (!events || events.length === 0) { container.innerHTML = '<p class="text-muted">No events found</p>'; return; }
  container.innerHTML = '<div class="timeline">' + events.map(ev => {
    const typeClass = 'event-' + (ev.event_type || ev.type || 'default');
    const titleHtml = ev.title ? '<strong>' + ev.title + '</strong><br>' : '';
    const bodyText = ev.description || ev.message || '';
    return '<div class="timeline-item ' + typeClass + '"><div class="timeline-marker"></div>'
      + '<div class="timeline-content"><div class="timeline-header"><span class="event-type-badge">' + (ev.event_type || ev.type || 'event') + '</span>'
      + '<span class="event-time">' + formatDate(ev.event_date || ev.created_at) + '</span></div>'
      + '<div class="event-body">' + titleHtml + bodyText + '</div></div></div>';
  }).join('') + '</div>';
}

// === Alerts ===
function renderAlerts() {
  return '<div class="page-header"><h2>Alerts</h2></div>'
    + '<div class="filter-bar"><select id="alert-filter"><option value="">All Alerts</option><option value="routine">Routine</option><option value="urgent">Urgent</option><option value="emergency">Emergency</option></select></div>'
    + '<div id="alert-container"><p class="text-muted">Loading alerts...</p></div>';
}

async function bindAlerts() {
  try {
    const data = await api('GET', '/alerts');
    if (data && data.alerts) {
      renderAlertList(data.alerts);
      document.getElementById('alert-filter').onchange = async (e) => {
        const filter = e.target.value;
        try {
          const d = await api('GET', '/alerts' + (filter ? '?tier=' + filter : ''));
          if (d && d.alerts) renderAlertList(d.alerts);
        } catch(err) { toast(err.message, 'error'); }
      };
    }
  } catch(e) { document.getElementById('alert-container').innerHTML = '<p class="text-danger">Failed to load alerts</p>'; }
}

function renderAlertList(alerts) {
  const container = document.getElementById('alert-container');
  if (alerts.length === 0) { container.innerHTML = '<p class="text-muted">No alerts</p>'; return; }
  container.innerHTML = alerts.map(a => {
    return '<div class="alert-card alert-'+a.tier+'"><div class="alert-header"><span class="alert-tier-badge">'+a.tier.toUpperCase()+'</span>'
      + '<span class="alert-time">'+formatDate(a.created_at)+'</span></div>'
      + '<div class="alert-body"><p class="alert-message">'+a.message+'</p>'
      + (a.patient_name ? '<p class="alert-patient">Patient: '+a.patient_name+'</p>' : '') + '</div>'
      + '<div class="alert-actions">'
      + (a.status !== 'acknowledged' ? '<button class="btn btn-sm btn-primary" onclick="acknowledgeAlert('+a.id+')">Acknowledge</button>' : '<span class="badge badge-success">Acknowledged</span>')
      + '</div></div>';
  }).join('');
}

async function acknowledgeAlert(id) {
  try {
    await api('POST', '/alerts/' + id + '/acknowledge');
    toast('Alert acknowledged', 'success');
    navigate('alerts');
  } catch(err) { toast(err.message, 'error'); }
}

// === Treatment Plan ===
function renderTreatmentPlan() {
  return '<div class="page-header"><h2>Treatment Plan</h2></div>'
    + '<div id="treatment-container"><p class="text-muted">Loading treatment plan...</p></div>';
}

async function bindTreatmentPlan() {
  try {
    const data = await api('GET', '/treatment-plan');
    const container = document.getElementById('treatment-container');
    if (data && data.treatment_plan) {
      const tp = data.treatment_plan;
      let drugsHtml = '';
      if (tp.drugs) {
        try {
          const drugs = typeof tp.drugs === 'string' ? JSON.parse(tp.drugs) : tp.drugs;
          if (Array.isArray(drugs)) {
            drugsHtml = '<div class="treatment-drugs" style="margin-top:1rem;"><h4>Prescribed Regimen</h4><ul>'
              + drugs.map(d => '<li><strong>' + (d.name || d) + '</strong>: ' + (d.dose || '') + ' ' + (d.schedule || '') + '</li>').join('')
              + '</ul></div>';
          }
        } catch(e) {}
      }
      container.innerHTML = '<div class="card"><h3>' + (tp.protocol_name || 'Treatment Protocol') + '</h3>'
        + '<p><strong>Cycle:</strong> ' + (tp.current_cycle || 1) + ' of ' + (tp.total_cycles || 'N/A') + '</p>'
        + '<p><strong>Intent:</strong> ' + (tp.intent || 'Curative') + '</p>'
        + drugsHtml + '</div>';
    } else {
      container.innerHTML = '<p class="text-muted">No active treatment plan found</p>';
    }
  } catch(e) {
    document.getElementById('treatment-container').innerHTML = '<p class="text-danger">Failed to load treatment plan</p>';
  }
}

// === Messages ===
function renderMessages() {
  return '<div class="page-header"><h2>Messages</h2></div>'
    + '<div class="messages-layout"><div class="message-list" id="message-list"><p class="text-muted">Loading...</p></div>'
    + '<div class="message-compose"><textarea id="message-text" rows="3" placeholder="Type a message..."></textarea>'
    + '<button class="btn btn-primary" id="send-message-btn">Send</button></div></div>';
}

async function bindMessages() {
  try {
    const data = await api('GET', '/messages');
    const list = document.getElementById('message-list');
    if (data && data.messages && data.messages.length > 0) {
      list.innerHTML = data.messages.map(m => {
        return '<div class="message-item'+(m.sender === currentUser.user ? ' message-self' : '')+'">'
          + '<div class="message-header"><span class="message-sender">'+m.sender+'</span><span class="message-time">'+formatDate(m.created_at)+'</span></div>'
          + '<div class="message-body">'+m.content+'</div></div>';
      }).join('');
    } else {
      list.innerHTML = '<p class="text-muted">No messages yet</p>';
    }
    document.getElementById('send-message-btn').onclick = async () => {
      const text = document.getElementById('message-text').value.trim();
      if (!text) return;
      try {
        await api('POST', '/messages', { content: text });
        document.getElementById('message-text').value = '';
        toast('Message sent', 'success');
        bindMessages();
      } catch(err) { toast(err.message, 'error'); }
    };
  } catch(e) { document.getElementById('message-list').innerHTML = '<p class="text-danger">Failed to load messages</p>'; }
}

// === Export ===
function renderExport() {
  return '<div class="page-header"><h2>Toxicity Summary Export</h2><p>Generate a structured summary for clinical records</p></div>'
    + '<div class="export-controls"><div class="form-row">'
    + '<div class="form-group"><label>Start Date</label><input type="date" id="export-start"></div>'
    + '<div class="form-group"><label>End Date</label><input type="date" id="export-end"></div>'
    + '<div class="form-group"><label>Patient</label><select id="export-patient"><option value="">All Patients</option></select></div>'
    + '</div><button class="btn btn-primary" id="export-btn">Generate Summary</button></div>'
    + '<div id="export-preview" class="export-preview"></div>';
}

async function bindExport() {
  try {
    const data = await api('GET', '/patients');
    const sel = document.getElementById('export-patient');
    if (data && data.patients) {
      sel.innerHTML = '<option value="">All Patients</option>' + data.patients.map(p => '<option value="'+p.id+'">'+p.name+'</option>').join('');
    }
  } catch(e) { console.error(e); }
  const today = new Date().toISOString().split('T')[0];
  const weekAgo = new Date(Date.now() - 7*24*60*60*1000).toISOString().split('T')[0];
  document.getElementById('export-start').value = weekAgo;
  document.getElementById('export-end').value = today;
  document.getElementById('export-btn').onclick = async () => {
    try {
      const params = new URLSearchParams({
        start: document.getElementById('export-start').value,
        end: document.getElementById('export-end').value,
        patient_id: document.getElementById('export-patient').value
      });
      const data = await api('GET', '/export/summary?' + params);
      if (data) {
        document.getElementById('export-preview').innerHTML = '<div class="export-result">'
          + '<h3>Summary Generated</h3><pre>'+JSON.stringify(data, null, 2)+'</pre>'
          + '<button class="btn btn-secondary" onclick="downloadExport()">Download JSON</button></div>';
        window._exportData = data;
      }
    } catch(err) { toast(err.message, 'error'); }
  };
}

function downloadExport() {
  if (!window._exportData) return;
  const blob = new Blob([JSON.stringify(window._exportData, null, 2)], {type: 'application/json'});
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url; a.download = 'toxicity_summary.json'; a.click();
  URL.revokeObjectURL(url);
}

// === Patients (Clinician) ===
function renderPatients() {
  return '<div class="page-header"><h2>Patients</h2></div>'
    + '<div id="patient-list"><p class="text-muted">Loading patients...</p></div>';
}

async function bindPatients() {
  try {
    const data = await api('GET', '/patients');
    const container = document.getElementById('patient-list');
    if (data && data.patients && data.patients.length > 0) {
      container.innerHTML = '<div class="patient-grid">' + data.patients.map(p => {
        return '<div class="patient-card" onclick="navigate(\'patient-detail\', {id:'+p.id+'})">'
          + '<h3>'+p.name+'</h3>'
          + '<p class="patient-protocol">'+(p.treatment_protocol||'N/A')+'</p>'
          + '<p class="patient-cycle">'+(p.current_cycle||'')+'</p>'
          + '</div>';
      }).join('') + '</div>';
    } else {
      container.innerHTML = '<p class="text-muted">No patients found</p>';
    }
  } catch(e) { document.getElementById('patient-list').innerHTML = '<p class="text-danger">Failed to load patients</p>'; }
}

// === Patient Detail ===
function renderPatientDetail() {
  return '<div class="page-header"><button class="btn btn-ghost" onclick="navigate(\'patients\')">&larr; Back</button><h2 id="patient-name">Loading...</h2></div>'
    + '<div id="patient-detail-content"><p class="text-muted">Loading patient details...</p></div>';
}

async function bindPatientDetail() {
  const id = window._params?.id;
  if (!id) { navigate('patients'); return; }
  try {
    const data = await api('GET', '/patients/' + id);
    if (data) {
      document.getElementById('patient-name').textContent = data.name || 'Patient';
      const c = document.getElementById('patient-detail-content');
      c.innerHTML = '<div class="patient-detail-grid">'
        + '<div class="card"><h3>Treatment</h3><p><strong>Protocol:</strong> '+(data.treatment_protocol||'N/A')+'</p>'
        + '<p><strong>Cycle:</strong> '+(data.current_cycle||'N/A')+'</p></div>'
        + '<div class="card"><h3>Recent Grades</h3><div id="patient-grades"><p class="text-muted">Loading...</p></div></div>'
        + '<div class="card"><h3>Alerts</h3><div id="patient-alerts"><p class="text-muted">Loading...</p></div></div>'
        + '</div>';
      // Load grades and alerts
      try {
        const grades = await api('GET', '/grades?patient_id=' + id);
        if (grades && grades.grades) {
          document.getElementById('patient-grades').innerHTML = grades.grades.slice(0,5).map(g =>
            '<div class="grade-item grade-'+(g.grade>=3?'high':g.grade>=2?'medium':'low')+'"><span>'+g.term+'</span><span>Grade '+g.grade+'</span></div>'
          ).join('') || '<p class="text-muted">No grades</p>';
        }
      } catch(e) {}
      try {
        const alerts = await api('GET', '/alerts?patient_id=' + id);
        if (alerts && alerts.alerts) {
          document.getElementById('patient-alerts').innerHTML = alerts.alerts.slice(0,5).map(a =>
            '<div class="alert-item alert-'+a.tier+'"><span class="alert-tier">'+a.tier+'</span><span>'+a.message+'</span></div>'
          ).join('') || '<p class="text-muted">No alerts</p>';
        }
      } catch(e) {}
    }
  } catch(e) { toast('Failed to load patient', 'error'); }
}

// === Utility ===
function formatDate(d) {
  if (!d) return '';
  try { const dt = new Date(d); return dt.toLocaleDateString() + ' ' + dt.toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'}); }
  catch(e) { return d; }
}

// === Init ===
async function init() {
  try {
    const data = await api('GET', '/whoami');
    if (data && data.authenticated && data.user) {
      currentUser = data.user;
      navigate('dashboard');
    } else {
      navigate('login');
    }
  } catch(e) { navigate('login'); }
}

init();