From b3e8a1a6926eefff682316df78be7648bc773180 Mon Sep 17 00:00:00 2001 From: DevSoftTech Server Date: Fri, 5 Jun 2026 03:34:08 +0530 Subject: [PATCH] Refine OPD template builder UI and preview behavior --- app/Views/Setting/Template/index.php | 12 +- app/Views/billing/opd_template_builder.php | 663 +++++++++++++----- .../billing/opd_template_builder_list.php | 19 +- 3 files changed, 518 insertions(+), 176 deletions(-) diff --git a/app/Views/Setting/Template/index.php b/app/Views/Setting/Template/index.php index e5e925c..8ae4154 100644 --- a/app/Views/Setting/Template/index.php +++ b/app/Views/Setting/Template/index.php @@ -202,7 +202,7 @@ $hasTemplateAccess = $canPathology || $canUltrasound || $canXray || $canCt || $c
- + OPD Print Template @@ -211,7 +211,7 @@ $hasTemplateAccess = $canPathology || $canUltrasound || $canXray || $canCt || $c
- + IPD Discharge Template @@ -220,7 +220,7 @@ $hasTemplateAccess = $canPathology || $canUltrasound || $canXray || $canCt || $c
- + IPD Document Master @@ -230,7 +230,7 @@ $hasTemplateAccess = $canPathology || $canUltrasound || $canXray || $canCt || $c
- + Pathology Print Template @@ -238,7 +238,7 @@ $hasTemplateAccess = $canPathology || $canUltrasound || $canXray || $canCt || $c
- + Diagnosis Print Template @@ -247,7 +247,7 @@ $hasTemplateAccess = $canPathology || $canUltrasound || $canXray || $canCt || $c
- + Document Print Template diff --git a/app/Views/billing/opd_template_builder.php b/app/Views/billing/opd_template_builder.php index 7133ee9..1a4e31f 100644 --- a/app/Views/billing/opd_template_builder.php +++ b/app/Views/billing/opd_template_builder.php @@ -5,9 +5,8 @@
@@ -18,61 +17,167 @@ $paperSettings = $paper_settings ?? []; $paperPageSize = strtoupper((string) ($paperSettings['page_size'] ?? 'A4')); $paperCustomWidth = (string) ($paperSettings['custom_width_mm'] ?? '210'); $paperCustomHeight = (string) ($paperSettings['custom_height_mm'] ?? '297'); +$selectedTemplateName = (string) ($selected_name ?? 'default'); + +$placeholderGroups = [ + 'hospital' => [], + 'patient' => [], + 'opd_master' => [], + 'opd_consult' => [], + 'other' => [], +]; + +$hospitalSet = [ + 'hospital_name', 'hospital_address', 'hospital_phone', 'hospital_email', + 'h_name', 'h_address_1', 'h_address_2', 'h_phone_no', 'h_email', 'h_logo', + 'hospital_section', +]; + +$patientSet = [ + 'pname', 'prelative', 'age_sex', 'phoneno', 'p_address', 'uhid_no', + 'patient_section', +]; + +$opdMasterSet = [ + 'opd_sr_no', 'opd_no', 'opd_date', 'exp_date', 'opd_fee_desc', + 'total_no_visit', 'last_opdvisit_date', 'str_opd_book_date', + 'doctor_name', 'doctor_full_name', 'doctor_title', + 'doctor_reg_no', 'doctor_registration', + 'doctor_phone', 'doctor_email', 'doctor_address', + 'doctor_specialization', 'specname', 'short_description', + 'doctor_short_description', 'doctor_sign_html', + 'print_time', 'current_date', + 'margintop', 'marginbottom', 'marginleft', 'marginright', 'marginheader', 'marginfooter', +]; + +$opdConsultSet = [ + 'bp', 'bp_diastolic', 'pulse', 'temp', 'spo2', 'rr_min', 'height', 'weight', 'waist', + 'pallor', 'icterus', 'cyanosis', 'clubbing', 'edema', + 'vital_content', 'vital_content_extra', 'vital_content_full', 'general_examination', + 'pain_value', 'pain_label', 'pain_scale', + 'complication', 'addiction', + 'drug_allergy_status', 'drug_allergy_details', 'adr_history', 'current_medications', + 'drug_allergy_block', 'adr_history_block', 'current_medications_block', + 'women_lmp', 'women_last_baby', 'women_pregnancy_related', 'women_related_problems', 'women_block', + 'obstetric_history', 'menstrual_history', 'medical_surgical_history', + 'family_history', 'allergic_history', 'vaccination_history', 'patient_history_block', + 'morbidities', 'morbidities_block', + 'painscale_img', + 'complaint', 'diagnosis', 'provisional_diagnosis', 'finding_examinations', + 'medical', 'investigation', 'prescriber_remarks', 'advice', 'next_visit', 'refer_to', + 'rx', 'rxtable', 'rxfullblock', + 'vitalsblock', 'complaintblock', 'diagnosisblock', 'investigationblock', + 'remarksblock', 'adviceblock', 'nextvisitblock', + 'content_section', 'content', +]; + +foreach (($placeholders ?? []) as $ph) { + $key = strtolower((string) $ph); + if (in_array($key, $hospitalSet, true)) { + $placeholderGroups['hospital'][] = $ph; + } elseif (in_array($key, $patientSet, true)) { + $placeholderGroups['patient'][] = $ph; + } elseif (in_array($key, $opdMasterSet, true)) { + $placeholderGroups['opd_master'][] = $ph; + } elseif (in_array($key, $opdConsultSet, true)) { + $placeholderGroups['opd_consult'][] = $ph; + } else { + $placeholderGroups['other'][] = $ph; + } +} + +foreach ($placeholderGroups as $groupName => $groupValues) { + $groupValues = array_values(array_unique($groupValues)); + natcasesort($groupValues); + $placeholderGroups[$groupName] = array_values($groupValues); +} ?> -
-
-
-
-
-

Edit Template Content

-
- -
-
-
- -
-
- - - -
-
- - - -
-
-
- - -
-
-
-
-
-
-
-
-

Placeholders

-
-
- - {{}} - -
-
-
- Old-style (recommended): save a name (example: opd_print_parcha_chamunda_hospital_ksp), then set that name in doctor master fields:
- opd_print_format, opd_blank_print, rx_pre_print_letter_head_format, rx_blank_letter_head, rx_plain_paper.

- Optional composed mode: use compose_default with type-specific parts. -
-
-
+ + +
+
+

OPD Paper Print Settings

@@ -92,7 +197,7 @@ $paperCustomHeight = (string) ($paperSettings['custom_height_mm'] ?? '297');
-
@@ -204,19 +401,29 @@ $paperCustomHeight = (string) ($paperSettings['custom_height_mm'] ?? '297'); return value.replace(/^[_-]+|[_-]+$/g, ''); } - document.getElementById('btn_load_part').addEventListener('click', function () { - var name = normalizeTemplateId(document.getElementById('tmpl_name').value || 'default'); - if (!name) { name = 'default'; } - document.getElementById('tmpl_name').value = name; - load_form_div('?mode=edit&name=' + encodeURIComponent(name), 'maindiv', 'OPD Template Edit'); - }); + var placeholderSearch = document.getElementById('placeholder_search'); + if (placeholderSearch) { + placeholderSearch.addEventListener('input', function () { + var query = (placeholderSearch.value || '').toLowerCase().trim(); + var groups = document.querySelectorAll('.placeholder-group'); - document.getElementById('btn_new_part').addEventListener('click', function () { - document.getElementById('tmpl_name').value = ''; - document.getElementById('tmpl_content').value = ''; - document.getElementById('tmpl_original_name').value = ''; - setMsg('Enter new template name, then write HTML and click Save.', true); - }); + groups.forEach(function (groupEl) { + var chips = groupEl.querySelectorAll('.placeholder-chip'); + var visibleCount = 0; + + chips.forEach(function (chipEl) { + var key = (chipEl.getAttribute('data-placeholder') || '').toLowerCase(); + var isVisible = !query || key.indexOf(query) !== -1; + chipEl.style.display = isVisible ? 'inline-block' : 'none'; + if (isVisible) { + visibleCount += 1; + } + }); + + groupEl.style.display = visibleCount > 0 ? 'block' : 'none'; + }); + }); + } function toggleCustomSize() { var pageSize = document.getElementById('opd_page_size'); @@ -237,6 +444,7 @@ $paperCustomHeight = (string) ($paperSettings['custom_height_mm'] ?? '297'); var header = document.getElementById('opd_header_html'); var footer = document.getElementById('opd_footer_html'); var body = document.getElementById('opd_paper_html_content'); + var customCss = document.getElementById('opd_custom_style_css'); var pageBlock = '\n\n' : ''; + + return pageBlock + headerBlock + footerBlock + customStyleBlock + bodyText; + } + + function splitCustomCssAndBody(content) { + var source = (content || '').trim(); + var cssList = []; + + source = source.replace(/]*>([\s\S]*?)<\/style>/gi, function (full, inner) { + var cssText = (inner || '').trim(); + if (!cssText) { + return ''; + } + + if (/@page\b/i.test(cssText) + || /header\s*:\s*html_myHeader/i.test(cssText) + || /footer\s*:\s*html_myFooter/i.test(cssText)) { + return ''; + } + + cssList.push(cssText); + return ''; + }); + + return { + bodyHtml: source.trim(), + customCss: cssList.join('\n\n').trim() + }; + } + + function hydrateCustomCssFromBody() { + var bodyEl = document.getElementById('opd_paper_html_content'); + var cssEl = document.getElementById('opd_custom_style_css'); + if (!bodyEl || !cssEl) { + return; + } + + var parsed = splitCustomCssAndBody(bodyEl.value || ''); + bodyEl.value = parsed.bodyHtml || ''; + cssEl.value = parsed.customCss || ''; + } + + function stripMpdfBlocks(html) { + var source = (html || ''); + var headerHtml = ''; + var footerHtml = ''; + var customCss = []; + + // Extract CSS first from the whole source so styles inside header/footer + // are preserved in preview (mPDF applies CSS globally). + source = source.replace(/]*>([\s\S]*?)<\/style>/gi, function (_, inner) { + var cssText = (inner || '').trim(); + if (!cssText) { + return ''; + } + + if (/@page\b/i.test(cssText) + || /header\s*:\s*html_myHeader/i.test(cssText) + || /footer\s*:\s*html_myFooter/i.test(cssText)) { + return ''; + } + + customCss.push(cssText); + return ''; + }); + + source = source.replace(/]*name\s*=\s*["']myHeader["'][^>]*>([\s\S]*?)<\/htmlpageheader>/i, function (_, inner) { + headerHtml = inner || ''; + return ''; + }); + + source = source.replace(/]*name\s*=\s*["']myFooter["'][^>]*>([\s\S]*?)<\/htmlpagefooter>/i, function (_, inner) { + footerHtml = inner || ''; + return ''; + }); + + source = source.replace(/header\s*:\s*html_myHeader\s*;?/gi, ''); + source = source.replace(/footer\s*:\s*html_myFooter\s*;?/gi, ''); + + return { + bodyHtml: source.trim(), + headerHtml: headerHtml.trim(), + footerHtml: footerHtml.trim(), + customCss: customCss.join('\n\n').trim() + }; + } + + function buildMpdfLikePreview(html) { + var parsed = stripMpdfBlocks(html); + + var top = (document.getElementById('opd_margin_top') || {}).value || '6.1'; + var bottom = (document.getElementById('opd_margin_bottom') || {}).value || '2.5'; + var left = (document.getElementById('opd_margin_left') || {}).value || '0.7'; + var right = (document.getElementById('opd_margin_right') || {}).value || '0.7'; + + var topCm = parseFloat(top) || 6.1; + var bottomCm = parseFloat(bottom) || 2.5; + var leftCm = parseFloat(left) || 0.7; + var rightCm = parseFloat(right) || 0.7; + var headerCm = parseFloat((document.getElementById('opd_margin_header') || {}).value || '0.5') || 0.5; + var footerCm = parseFloat((document.getElementById('opd_margin_footer') || {}).value || '1.5') || 1.5; + + var pageWidth = '210mm'; + var pageHeight = '297mm'; + + var pageSize = ((document.getElementById('opd_page_size') || {}).value || 'A4').toUpperCase(); + if (pageSize === 'A4-L') { + pageWidth = '297mm'; + pageHeight = '210mm'; + } else if (pageSize === 'A5') { + pageWidth = '148mm'; + pageHeight = '210mm'; + } else if (pageSize === 'A6') { + pageWidth = '105mm'; + pageHeight = '148mm'; + } else if (pageSize === 'LETTER') { + pageWidth = '216mm'; + pageHeight = '279mm'; + } else if (pageSize === 'LEGAL') { + pageWidth = '216mm'; + pageHeight = '356mm'; + } else if (pageSize === 'CUSTOM') { + var cw = ((document.getElementById('opd_custom_width') || {}).value || '210'); + var ch = ((document.getElementById('opd_custom_height') || {}).value || '297'); + pageWidth = cw + 'mm'; + pageHeight = ch + 'mm'; + } + + var liveHeader = ((document.getElementById('opd_header_html') || {}).value || '').trim(); + var liveFooter = ((document.getElementById('opd_footer_html') || {}).value || '').trim(); + var finalHeader = liveHeader !== '' ? liveHeader : (parsed.headerHtml || '').trim(); + var finalFooter = liveFooter !== '' ? liveFooter : (parsed.footerHtml || '').trim(); + + var liveCustomCss = ((document.getElementById('opd_custom_style_css') || {}).value || '').trim(); + var finalCustomCss = liveCustomCss !== '' ? liveCustomCss : (parsed.customCss || '').trim(); + + var hasHeader = finalHeader !== ''; + var hasFooter = finalFooter !== ''; + + // Collapse extra white space when header/footer blocks are empty. + var contentTopCm = hasHeader ? topCm : Math.max(0.4, topCm * 0.35); + var contentBottomCm = hasFooter ? bottomCm : Math.max(0.4, bottomCm * 0.35); + + // Keep footer visually near page bottom like mPDF page footer behavior. + // We reserve footer space in content padding so body text does not overlap footer. + var footerReserveCm = hasFooter ? Math.max(contentBottomCm, footerCm + 0.6) : contentBottomCm; + + var headerBlock = hasHeader + ? '
' + finalHeader + '
' + : ''; + + var footerBlock = hasFooter + ? '' + : ''; + + return '' + + '' + + '
' + + headerBlock + + '
' + (parsed.bodyHtml || '') + '
' + + footerBlock + + '
'; + } + + function renderTemplatePreview() { + var frame = document.getElementById('tmpl_preview_frame'); + if (!frame) { + return; + } + + // Preview must always reflect current control values on this page. + var html = buildTemplateFromPaperFields(); + frame.srcdoc = buildMpdfLikePreview(html); } var pageSizeSelect = document.getElementById('opd_page_size'); @@ -278,81 +668,26 @@ $paperCustomHeight = (string) ($paperSettings['custom_height_mm'] ?? '297'); pageSizeSelect.addEventListener('change', toggleCustomSize); } toggleCustomSize(); + hydrateCustomCssFromBody(); - document.getElementById('btn_save_part').addEventListener('click', function () { - var name = normalizeTemplateId(document.getElementById('tmpl_name').value || ''); - var originalName = normalizeTemplateId(document.getElementById('tmpl_original_name').value || ''); - var content = document.getElementById('tmpl_content').value || ''; - document.getElementById('tmpl_name').value = name; - if (!content.trim()) { - content = buildTemplateFromPaperFields(); - var tmplContentEl = document.getElementById('tmpl_content'); - if (tmplContentEl && content.trim()) { - tmplContentEl.value = content; - } - } - if (!name) { - setMsg('Template name required', false); + var previewInputIds = [ + 'opd_page_size', 'opd_custom_width', 'opd_custom_height', + 'opd_margin_top', 'opd_margin_bottom', 'opd_margin_left', 'opd_margin_right', + 'opd_margin_header', 'opd_margin_footer', + 'opd_header_html', 'opd_footer_html', 'opd_paper_html_content', 'opd_custom_style_css' + ]; + + previewInputIds.forEach(function (id) { + var el = document.getElementById(id); + if (!el) { return; } - var csrf = getCsrfPair(); - var payload = { - section: 'full', - name: name, - content: content - }; - payload[csrf.name] = csrf.value; - - var saveTemplate = function () { - $.post('', payload, function (res) { - if (res && res.csrfName && res.csrfHash) { - var tokenInput = document.querySelector('input[name="' + res.csrfName + '"]'); - if (tokenInput) tokenInput.value = res.csrfHash; - } - if (!res || Number(res.update || 0) !== 1) { - setMsg((res && res.error_text) ? res.error_text : 'Unable to save template', false); - return; - } - document.getElementById('tmpl_original_name').value = (res.name || name); - setMsg('Saved: ' + (res.section || 'full') + '/' + (res.name || name), true); - setTimeout(function () { - load_form_div('?mode=edit&name=' + encodeURIComponent(res.name || name), 'maindiv', 'OPD Template Edit'); - }, 250); - }, 'json').fail(function () { - setMsg('Unable to save template', false); - }); - }; - - if (originalName && originalName !== name) { - var renamePayload = { - old_name: originalName, - new_name: name - }; - var renameCsrf = getCsrfPair(); - renamePayload[renameCsrf.name] = renameCsrf.value; - - $.post('', renamePayload, function (renameRes) { - if (renameRes && renameRes.csrfName && renameRes.csrfHash) { - var renameTokenInput = document.querySelector('input[name="' + renameRes.csrfName + '"]'); - if (renameTokenInput) renameTokenInput.value = renameRes.csrfHash; - } - - if (!renameRes || Number(renameRes.update || 0) !== 1) { - setMsg((renameRes && renameRes.error_text) ? renameRes.error_text : 'Unable to rename template', false); - return; - } - - document.getElementById('tmpl_original_name').value = name; - saveTemplate(); - }, 'json').fail(function () { - setMsg('Unable to rename template', false); - }); - return; - } - - saveTemplate(); + el.addEventListener('input', renderTemplatePreview); + el.addEventListener('change', renderTemplatePreview); }); + renderTemplatePreview(); + var savePaperBtn = document.getElementById('btn_save_opd_paper'); if (savePaperBtn) { savePaperBtn.addEventListener('click', function () { @@ -371,7 +706,15 @@ $paperCustomHeight = (string) ($paperSettings['custom_height_mm'] ?? '297'); margin_footer_cm: document.getElementById('opd_margin_footer').value || '1.5', header_html: document.getElementById('opd_header_html').value || '', footer_html: document.getElementById('opd_footer_html').value || '', - paper_html_content: document.getElementById('opd_paper_html_content').value || '' + paper_html_content: (function () { + var bodyVal = document.getElementById('opd_paper_html_content').value || ''; + var cssVal = document.getElementById('opd_custom_style_css').value || ''; + cssVal = cssVal.trim(); + if (!cssVal) { + return bodyVal; + } + return '\n\n' + bodyVal; + })() }; payload[csrf.name] = csrf.value; @@ -426,6 +769,8 @@ $paperCustomHeight = (string) ($paperSettings['custom_height_mm'] ?? '297'); } else { setPaperMsg((res.notice || 'OPD print settings saved.'), true); } + + renderTemplatePreview(); }).fail(function () { setPaperMsg('Unable to save OPD print settings', false); }).always(function () { @@ -480,6 +825,8 @@ $paperCustomHeight = (string) ($paperSettings['custom_height_mm'] ?? '297'); document.getElementById('opd_footer_html').value = settings.footer_html || ''; document.getElementById('opd_paper_html_content').value = settings.paper_html_content || ''; toggleCustomSize(); + hydrateCustomCssFromBody(); + renderTemplatePreview(); setPaperMsg((res.notice || 'OPD print settings reset.'), true); }).fail(function () { diff --git a/app/Views/billing/opd_template_builder_list.php b/app/Views/billing/opd_template_builder_list.php index e13804e..e78f0a8 100644 --- a/app/Views/billing/opd_template_builder_list.php +++ b/app/Views/billing/opd_template_builder_list.php @@ -4,16 +4,11 @@

OPD Print Template Builder

- -
- - Edit Template -
+
@@ -22,7 +17,7 @@

Templates

- +
@@ -48,7 +43,7 @@ - Edit + Edit @@ -102,7 +97,7 @@ return; } - load_form_div('?mode=edit&name=' + encodeURIComponent(name), 'maindiv', 'OPD Template Edit'); + load_form('?mode=edit&name=' + encodeURIComponent(name), 'maindiv', 'OPD Template Edit'); }); var renameButtons = document.querySelectorAll('.btn_rename_template'); @@ -148,7 +143,7 @@ } setListMsg('Template renamed: ' + oldName + ' -> ' + (res.new_name || newName), true); - load_form_div('?mode=list', 'maindiv', 'OPD Template List'); + load_form('?mode=list', 'maindiv', 'OPD Template List'); }, 'json').fail(function () { setListMsg('Unable to rename template', false); }); @@ -187,7 +182,7 @@ } setListMsg('Template deleted: ' + templateName, true); - load_form_div('?mode=list', 'maindiv', 'OPD Template List'); + load_form('?mode=list', 'maindiv', 'OPD Template List'); }, 'json').fail(function () { setListMsg('Unable to delete template', false); });