diff --git a/public/components/cms.html b/public/components/cms.html index 39ad990..2c366ce 100644 --- a/public/components/cms.html +++ b/public/components/cms.html @@ -45,7 +45,11 @@
- +
+ + + +
- +
- - + +
+ + + + + + + + + + + + + + + +
+
diff --git a/public/components/settings.html b/public/components/settings.html index 4c69fe7..29e2066 100644 --- a/public/components/settings.html +++ b/public/components/settings.html @@ -78,20 +78,19 @@
- +
-

HIPAA & Privacy

+

Compliance & Usage

-

Current Status: This tool processes data through third-party AI APIs.

+

AWS Bedrock is available with a Business Associate Agreement (BAA) for HIPAA-eligible workloads.

-

Recommendation: Do not enter real PHI until your organization has executed BAAs with all AI providers.

+

Important: Check with your institution's guidelines and policies before use. This tool is not intended for production clinical use without proper organizational authorization and provider BAAs in place. Use with caution.

diff --git a/public/css/styles.css b/public/css/styles.css index 822e04c..cf0672f 100644 --- a/public/css/styles.css +++ b/public/css/styles.css @@ -439,12 +439,16 @@ body{font-family:'Inter',system-ui,sans-serif;background:var(--g50);color:var(-- .shadess-flag-btn{font-size:11px;padding:2px 6px;} /* Settings page */ -.settings-page{display:flex;flex-direction:column;gap:16px;max-width:800px;} -.settings-page .settings-section{padding:20px;} -.settings-page .settings-section h3{font-size:16px;font-weight:700;color:var(--g800);margin-bottom:12px;display:flex;align-items:center;gap:8px;} -.settings-page .settings-section p{font-size:13px;color:var(--g600);margin-bottom:12px;} +.settings-page{display:flex;flex-direction:column;gap:0;max-width:800px;} +.settings-page .settings-section{padding:20px;margin-bottom:12px;border-radius:var(--radius);} +.settings-page .settings-section h3{font-size:16px;font-weight:700;color:var(--g800);margin-bottom:14px;padding-bottom:10px;border-bottom:1px solid var(--g100);display:flex;align-items:center;gap:8px;} +.settings-page .settings-section p{font-size:13px;color:var(--g600);margin-bottom:12px;line-height:1.6;} .settings-page .hipaa-info ul{margin:10px 0 10px 20px;font-size:13px;} -.settings-page .hipaa-info li{margin-bottom:4px;} +.settings-page .hipaa-info li{margin-bottom:6px;line-height:1.5;} +.settings-page .form-group{margin-bottom:14px;} +.settings-page .form-group label{font-size:13px;font-weight:600;color:var(--g600);margin-bottom:4px;display:block;} +.settings-page .form-group input{width:100%;padding:8px 12px;border:1.5px solid var(--g300);border-radius:6px;font-size:13px;box-sizing:border-box;} +.settings-page .form-group input:focus{border-color:var(--blue);outline:none;box-shadow:0 0 0 2px var(--blue-light);} /* Inline load popover — compact dropdown, right-aligned under Load button */ .enc-load-popover{position:absolute;right:0;top:calc(100% + 4px);width:360px;max-width:calc(100vw - 24px);background:white;border:1px solid var(--g300);border-radius:10px;box-shadow:0 8px 24px rgba(0,0,0,0.15);z-index:600;display:flex;flex-direction:column;} @@ -531,20 +535,23 @@ body{font-family:'Inter',system-ui,sans-serif;background:var(--g50);color:var(-- .lh-content-body th,.lh-content-body td{padding:8px 12px;border:1px solid var(--g200);font-size:13px;} .lh-content-body th{background:var(--g50);font-weight:600;} -/* Quiz */ -.lh-quiz-q{margin-bottom:20px;padding-bottom:16px;border-bottom:1px solid var(--g100);} -.lh-quiz-q:last-child{border-bottom:none;margin-bottom:0;} -.lh-quiz-q-header{display:flex;gap:8px;align-items:center;margin-bottom:6px;} -.lh-quiz-q-num{font-size:12px;font-weight:700;color:var(--blue);background:var(--blue-light);padding:2px 8px;border-radius:6px;} -.lh-quiz-q-type{font-size:11px;color:var(--g400);} -.lh-quiz-q-text{font-size:14px;font-weight:500;margin-bottom:10px;color:var(--g800);} -.lh-quiz-options{display:flex;flex-direction:column;gap:6px;} -.lh-quiz-option{display:flex;align-items:center;gap:10px;padding:10px 14px;border:1.5px solid var(--g200);border-radius:8px;cursor:pointer;transition:all 0.15s;font-size:13px;} -.lh-quiz-option:hover{border-color:var(--blue-light);background:var(--g50);} -.lh-quiz-option input[type="radio"]{accent-color:var(--blue);width:16px;height:16px;} +/* Quiz — large, visual question cards */ +.lh-quiz-q{margin-bottom:24px;padding:20px;background:white;border:1.5px solid var(--g200);border-radius:12px;box-shadow:0 1px 4px rgba(0,0,0,0.04);} +.lh-quiz-q:last-child{margin-bottom:0;} +.lh-quiz-q-header{display:flex;gap:10px;align-items:center;margin-bottom:10px;} +.lh-quiz-q-num{font-size:13px;font-weight:700;color:white;background:linear-gradient(135deg,var(--blue),var(--purple));padding:4px 12px;border-radius:8px;min-width:32px;text-align:center;} +.lh-quiz-q-type{font-size:11px;color:var(--g400);background:var(--g100);padding:2px 8px;border-radius:4px;} +.lh-quiz-q-text{font-size:16px;font-weight:600;margin-bottom:14px;color:var(--g800);line-height:1.5;} +.lh-quiz-options{display:flex;flex-direction:column;gap:8px;} +.lh-quiz-option{display:flex;align-items:center;gap:12px;padding:14px 18px;border:2px solid var(--g200);border-radius:10px;cursor:pointer;transition:all 0.2s;font-size:14px;line-height:1.4;background:white;} +.lh-quiz-option:hover{border-color:var(--blue);background:var(--blue-light);transform:translateY(-1px);box-shadow:0 2px 8px rgba(37,99,235,0.1);} +.lh-quiz-option input[type="radio"]{accent-color:var(--blue);width:18px;height:18px;flex-shrink:0;} .lh-quiz-option span{flex:1;} -.lh-opt-correct{border-color:var(--green) !important;background:var(--green-light) !important;} -.lh-opt-wrong{border-color:var(--red) !important;background:var(--red-light) !important;} +.lh-opt-correct{border-color:var(--green) !important;background:var(--green-light) !important;box-shadow:0 0 0 2px rgba(16,185,129,0.2) !important;} +.lh-opt-correct::after{content:'\\2713';position:absolute;right:14px;color:var(--green);font-weight:700;font-size:18px;} +.lh-quiz-option.lh-opt-correct,.lh-quiz-option.lh-opt-wrong{position:relative;} +.lh-opt-wrong{border-color:var(--red) !important;background:var(--red-light) !important;box-shadow:0 0 0 2px rgba(239,68,68,0.2) !important;} +.lh-opt-wrong::after{content:'\\2717';position:absolute;right:14px;color:var(--red);font-weight:700;font-size:18px;} /* Quiz results */ .lh-result-item{margin-bottom:14px;padding-bottom:14px;border-bottom:1px solid var(--g100);} @@ -556,7 +563,7 @@ body{font-family:'Inter',system-ui,sans-serif;background:var(--g50);color:var(-- .lh-expl-general{background:var(--blue-light);color:#1e40af;} /* CMS question builder */ -.lh-question-block{padding:12px;border:1px solid var(--g200);border-radius:8px;margin-bottom:8px;background:var(--g50);} +.lh-question-block{padding:16px;border:1.5px solid var(--g200);border-radius:10px;margin-bottom:12px;background:white;box-shadow:0 1px 3px rgba(0,0,0,0.04);transition:outline 0.2s;} /* ============================================================ CONTENT MANAGER (WordPress-like CMS) @@ -628,6 +635,13 @@ body{font-family:'Inter',system-ui,sans-serif;background:var(--g50);color:var(-- .cms-quiz-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;} .cms-quiz-header h3{margin:0;font-size:15px;color:var(--g700);} +/* Editor toolbar */ +.cms-editor-toolbar{display:flex;gap:2px;padding:6px 8px;background:var(--g50);border:1px solid var(--g300);border-bottom:none;border-radius:8px 8px 0 0;flex-wrap:wrap;} +.cms-tb-btn{width:32px;height:28px;border:none;background:transparent;cursor:pointer;border-radius:4px;font-size:12px;color:var(--g600);display:flex;align-items:center;justify-content:center;font-family:inherit;font-weight:600;} +.cms-tb-btn:hover{background:var(--g200);color:var(--g800);} +.cms-tb-sep{width:1px;background:var(--g300);margin:2px 4px;align-self:stretch;} +.cms-body-section .cms-body-editor{border-radius:0 0 8px 8px;} + /* Mobile responsive */ @media(max-width:768px){ .cms-layout{grid-template-columns:1fr;} @@ -640,7 +654,7 @@ body{font-family:'Inter',system-ui,sans-serif;background:var(--g50);color:var(-- .cms-stat{padding:8px 10px;} .cms-stat-value{font-size:18px;} } -.lh-option-row{display:flex;align-items:center;gap:6px;margin-bottom:4px;} +.lh-option-row{display:flex;align-items:center;gap:8px;margin-bottom:6px;padding:6px 8px;border-radius:6px;background:var(--g50);} @media(max-width:640px){ .lh-feed-item-header{flex-direction:column;gap:6px;} diff --git a/public/js/app.js b/public/js/app.js index 12a2a19..9265564 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -74,12 +74,15 @@ document.addEventListener('DOMContentLoaded', function() { var tabEl = document.getElementById(tabName + '-tab'); if (tabEl) { tabEl.classList.add('active'); - // Lazy-load component HTML if not yet loaded - loadComponent(tabEl); + // Lazy-load component HTML, then fire tabChanged after DOM is ready + loadComponent(tabEl).then(function() { + document.dispatchEvent(new CustomEvent('tabChanged', { detail: { tab: tabName } })); + }); + } else { + document.dispatchEvent(new CustomEvent('tabChanged', { detail: { tab: tabName } })); } // Persist last tab in localStorage (restored after login in auth.js) try { localStorage.setItem('ped_last_tab', tabName); } catch(e) {} - document.dispatchEvent(new CustomEvent('tabChanged', { detail: { tab: tabName } })); // Close sidebar on mobile after tab click var sidebar = document.getElementById('sidebar'); if (sidebar && window.innerWidth <= 768) { diff --git a/public/js/learningHub.js b/public/js/learningHub.js index 983f9d6..615ac72 100644 --- a/public/js/learningHub.js +++ b/public/js/learningHub.js @@ -56,12 +56,18 @@ // ── CMS events ───────────────────────────── if (e.target.closest('#btn-lh-add-cat')) { addCategory(); return; } - if (e.target.closest('#btn-lh-new-content')) { openEditor(null); return; } + if (e.target.closest('#btn-lh-new-content')) { openEditor(null, 'article'); return; } + if (e.target.closest('#btn-lh-new-quiz')) { openEditor(null, 'quiz'); return; } + if (e.target.closest('#btn-lh-new-pearl')) { openEditor(null, 'pearl'); return; } if (e.target.closest('#btn-lh-close-editor')) { closeEditor(); return; } if (e.target.closest('#btn-lh-save-content')) { saveContent(); return; } if (e.target.closest('#btn-lh-delete-content')) { deleteContent(); return; } if (e.target.closest('#btn-lh-add-question')) { addQuestionBlock(); return; } + // Editor toolbar buttons + var tbBtn = e.target.closest('.cms-tb-btn'); + if (tbBtn && tbBtn.dataset.insert) { insertTag(tbBtn.dataset.insert); return; } + // CMS content item edit var cmsItem = e.target.closest('.lh-cms-content-item'); if (cmsItem && cmsItem.dataset.id) { openEditor(cmsItem.dataset.id); return; } @@ -554,7 +560,33 @@ renderCmsContentList(filtered); } - function openEditor(contentId) { + // Editor toolbar — insert HTML tags at cursor in textarea + function insertTag(tag) { + var ta = document.getElementById('lh-cms-edit-body'); + if (!ta) return; + var start = ta.selectionStart, end = ta.selectionEnd; + var sel = ta.value.substring(start, end); + var insert = ''; + switch(tag) { + case 'b': case 'i': case 'u': case 'code': case 'h2': case 'h3': + insert = '<' + tag + '>' + (sel || 'text') + ''; break; + case 'a': + var url = prompt('Enter URL:'); + if (!url) return; + insert = '' + (sel || 'link text') + ''; break; + case 'ul': insert = ''; break; + case 'ol': insert = '
    \n
  1. ' + (sel || 'item') + '
  2. \n
  3. \n
'; break; + case 'blockquote': insert = '
' + (sel || 'quote') + '
'; break; + case 'table': insert = '\n\n\n
Header 1Header 2
' + (sel || 'data') + '
'; break; + case 'hr': insert = '
'; break; + default: return; + } + ta.value = ta.value.substring(0, start) + insert + ta.value.substring(end); + ta.focus(); + ta.selectionStart = ta.selectionEnd = start + insert.length; + } + + function openEditor(contentId, contentType) { var editor = document.getElementById('lh-cms-editor'); var listView = document.getElementById('cms-list-view'); if (!editor) return; @@ -567,11 +599,16 @@ document.getElementById('lh-cms-edit-subject').value = ''; document.getElementById('lh-cms-edit-body').value = ''; document.getElementById('lh-cms-edit-category').value = ''; - document.getElementById('lh-cms-edit-type').value = 'article'; + document.getElementById('lh-cms-edit-type').value = contentType || 'article'; document.getElementById('lh-cms-edit-published').value = 'false'; document.getElementById('lh-cms-questions').innerHTML = ''; document.getElementById('btn-lh-delete-content').classList.add('hidden'); + // For quiz type, add a starter question + if (contentType === 'quiz' && !contentId) { + addQuestionBlock(); + } + if (!contentId) return; // Load existing content @@ -618,11 +655,14 @@ if (!title) { showToast('Title required', 'error'); return; } - // Gather questions + // Gather questions with validation var questions = []; - document.querySelectorAll('#lh-cms-questions .lh-question-block').forEach(function(qBlock) { + var validationError = false; + var qBlocks = document.querySelectorAll('#lh-cms-questions .lh-question-block'); + for (var qi = 0; qi < qBlocks.length; qi++) { + var qBlock = qBlocks[qi]; var qText = qBlock.querySelector('.lh-q-text').value.trim(); - if (!qText) return; + if (!qText) continue; var qType = qBlock.querySelector('.lh-q-type').value; var qExpl = qBlock.querySelector('.lh-q-explanation').value.trim(); var qId = qBlock.dataset.questionId || null; @@ -638,6 +678,17 @@ }); }); + // Validate: must have a correct answer marked + var hasCorrect = options.some(function(o) { return o.is_correct; }); + if (options.length > 0 && !hasCorrect) { + showToast('Question "' + qText.substring(0, 50) + '..." needs a correct answer marked', 'error'); + qBlock.style.outline = '2px solid var(--red)'; + qBlock.scrollIntoView({ behavior: 'smooth', block: 'center' }); + validationError = true; + break; + } + qBlock.style.outline = ''; + questions.push({ id: qId, question_text: qText, @@ -645,7 +696,8 @@ explanation: qExpl, options: options }); - }); + } + if (validationError) return; var payload = { title: title, body: body, category_id: category_id, subject: subject, content_type: content_type, published: published }; @@ -733,21 +785,28 @@ block.className = 'lh-question-block'; if (existingQ && existingQ.id) block.dataset.questionId = existingQ.id; + var qNum = container.querySelectorAll('.lh-question-block').length + 1; block.innerHTML = - '
' + - 'Question' + - '' + + '
' + + '
' + + 'Q' + qNum + '' + + '' + + '
' + + '' + '
' + - '' + - '
' + - '' + - '' + + '' + + '
' + + '' + '
' + '
' + - ''; + '' + + '
' + + '' + + '' + + '
'; container.appendChild(block); @@ -768,13 +827,12 @@ var row = document.createElement('div'); row.className = 'lh-option-row'; row.innerHTML = - '