This commit is contained in:
gsinghpal
2026-03-09 23:45:00 -04:00
parent acd3fc455e
commit f81e0cd918
85 changed files with 6085 additions and 126 deletions

View File

@@ -199,6 +199,204 @@
word-wrap: break-word;
}
/* ── Compact option cards (seating tabs) ── */
.wc-option-compact {
padding: 0.45rem 0.6rem !important;
border-radius: 0.375rem !important;
border: 1px solid #e0e0e0 !important;
background: #fff;
transition: border-color 0.15s, background-color 0.15s;
}
.wc-option-compact:hover {
border-color: #adb5bd !important;
background: #f8f9fa;
}
.wc-option-compact .form-check-input:checked ~ .form-check-label {
color: #0d6efd;
}
.wc-option-card.wc-option-compact .form-check-input:checked {
/* parent card highlight when checked */
}
.wc-options-grid .col-md-4 {
padding-bottom: 0.15rem;
}
.wc-option-compact .form-check {
min-height: auto;
}
.wc-option-compact .form-check-input {
width: 0.9em;
height: 0.9em;
margin-top: 0.2em;
}
.wc-option-compact .wc-opt-label {
font-size: 0.78rem;
font-weight: 500;
line-height: 1.3;
display: block;
}
.wc-option-compact .wc-opt-meta {
font-size: 0.7rem;
line-height: 1.2;
margin-left: 1.35em;
margin-top: 0.15rem;
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.wc-option-compact .wc-opt-adp {
font-size: 0.7rem;
}
/* Checked card highlight */
.wc-option-compact:has(.form-check-input:checked) {
border-color: #198754 !important;
background: #f0faf4;
}
/* ── Collapsible section groups (accordion within tab) ── */
.wc-section-groups {
display: flex;
flex-direction: column;
gap: 0;
}
.wc-section-group {
border: 1px solid #dee2e6;
border-radius: 0.5rem;
margin-bottom: 0.5rem;
overflow: hidden;
}
.wc-group-header {
display: flex;
align-items: center;
gap: 0.5rem;
width: 100%;
padding: 0.65rem 0.85rem;
border: none;
background: #f0f0f0;
color: #333;
font-size: 0.85rem;
font-weight: 600;
text-align: left;
cursor: pointer;
transition: background-color 0.15s;
}
.wc-group-header:hover {
background: #e2e2e2;
}
.wc-section-group.open > .wc-group-header {
background: #2c3e50;
color: #fff;
}
.wc-section-group.open > .wc-group-header:hover {
background: #34495e;
}
.wc-group-icon {
width: 18px;
text-align: center;
font-size: 0.9rem;
flex-shrink: 0;
}
.wc-group-title {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.wc-group-count {
display: none;
font-size: 0.65rem;
font-weight: 700;
background: #fff;
color: #2c3e50;
border-radius: 999px;
min-width: 20px;
height: 20px;
line-height: 20px;
text-align: center;
padding: 0 6px;
flex-shrink: 0;
}
.wc-section-group.has-selection > .wc-group-header .wc-group-count {
display: inline-block;
}
/* When closed + has selections: green accent */
.wc-section-group.has-selection:not(.open) > .wc-group-header {
background: #d1e7dd;
color: #0f5132;
}
.wc-section-group.has-selection:not(.open) > .wc-group-header .wc-group-count {
background: #198754;
color: #fff;
}
.wc-group-chevron {
font-size: 0.7rem;
transition: transform 0.2s;
flex-shrink: 0;
}
.wc-section-group.open > .wc-group-header .wc-group-chevron {
transform: rotate(180deg);
}
.wc-group-body {
display: none;
padding: 0.75rem;
}
.wc-section-group.open > .wc-group-body {
display: block;
}
/* ── Real-time filter ── */
.wc-option-filter-wrap {
max-width: 300px;
}
.wc-option-filter-wrap .input-group-text {
background: transparent;
border-right: none;
color: var(--bs-secondary-color);
padding: 0.25rem 0.5rem;
}
.wc-option-filter-wrap .wc-option-filter {
border-left: none;
font-size: 0.8rem;
padding: 0.25rem 0.5rem;
}
.wc-option-filter-wrap .wc-option-filter:focus {
box-shadow: none;
border-color: var(--bs-primary);
}
.wc-option-filter-wrap .wc-option-filter:focus + .input-group-text,
.wc-option-filter-wrap .input-group:focus-within .input-group-text {
border-color: var(--bs-primary);
}
/* -----------------------------------------------------------------
Search Containers — z-index must beat ALL sibling content below.
position:relative creates a stacking context so the absolute
@@ -321,37 +519,189 @@
}
/* -----------------------------------------------------------------
Accordion Sections (Step 4 seating)
Seating Side Tabs (responsive vertical tabs)
Desktop: side nav left + content right
Mobile: horizontal scrollable tabs on top + content below
----------------------------------------------------------------- */
.wc-assessment-form .accordion-button:not(.collapsed) {
background-color: #e8f0fe;
color: #0d6efd;
}
.wc-assessment-form .accordion-item {
border-radius: 0.5rem !important;
margin-bottom: 0.75rem;
border: 1px solid #dee2e6 !important;
/* ── Container ── */
.wc-seating-tabs {
border: 1px solid var(--bs-border-color, #dee2e6);
border-radius: 0.75rem;
overflow: hidden;
background: var(--bs-body-bg);
}
.wc-assessment-form .accordion-item:last-child {
margin-bottom: 0;
.wc-seating-tabs > .row {
min-height: 400px;
}
.wc-assessment-form .accordion-button {
border-radius: 0.5rem !important;
/* ── Tab Navigation Column ── */
.wc-tab-nav-col {
background: var(--bs-tertiary-bg, #f8f9fa);
border-right: 1px solid var(--bs-border-color, #dee2e6);
}
.wc-tab-nav {
padding: 0.5rem 0;
gap: 2px;
}
/* ── Individual Tab Button ── */
.wc-tab-btn {
display: flex;
align-items: center;
gap: 0.5rem;
width: 100%;
padding: 0.625rem 0.875rem;
border: none;
background: transparent;
color: var(--bs-body-color);
font-size: 0.82rem;
font-weight: 500;
text-align: left;
cursor: pointer;
transition: background-color 0.15s, color 0.15s;
border-left: 3px solid transparent;
white-space: nowrap;
}
.wc-tab-btn:hover {
background: rgba(var(--bs-primary-rgb), 0.06);
}
.wc-tab-btn.active {
background: var(--bs-body-bg, #fff);
color: var(--bs-primary);
font-weight: 600;
border-left-color: var(--bs-primary);
box-shadow: 1px 0 0 var(--bs-body-bg, #fff);
}
.wc-assessment-form .accordion-button:not(.collapsed) {
border-radius: 0.5rem 0.5rem 0 0 !important;
.wc-tab-icon {
width: 18px;
text-align: center;
font-size: 0.9rem;
flex-shrink: 0;
}
.wc-assessment-form .accordion-body {
.wc-tab-label {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
/* Badge showing selected items count — hidden when 0 */
.wc-tab-badge {
display: none;
font-size: 0.68rem;
font-weight: 700;
background: var(--bs-primary);
color: #fff;
border-radius: 999px;
min-width: 18px;
height: 18px;
line-height: 18px;
text-align: center;
padding: 0 5px;
flex-shrink: 0;
}
.wc-tab-badge.has-items {
display: inline-block;
}
/* ── Tab Content Column ── */
.wc-tab-content-col {
min-height: 100%;
}
.wc-tab-content {
padding: 1.25rem;
position: relative;
}
/* ── Tab Panels ── */
.wc-tab-panel {
display: none;
}
.wc-tab-panel.active {
display: block;
}
.wc-tab-panel-title {
font-size: 1rem;
font-weight: 600;
margin-bottom: 1rem;
padding-bottom: 0.625rem;
border-bottom: 1px solid var(--bs-border-color, #dee2e6);
color: var(--bs-body-color);
}
/* ── MOBILE: horizontal scrollable tabs ── */
@media (max-width: 767.98px) {
.wc-seating-tabs > .row {
min-height: auto;
flex-direction: column;
}
.wc-tab-nav-col {
border-right: none;
border-bottom: 1px solid var(--bs-border-color, #dee2e6);
width: 100%;
max-width: 100%;
flex: 0 0 auto;
}
.wc-tab-nav {
flex-direction: row !important;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
padding: 0;
gap: 0;
}
.wc-tab-nav::-webkit-scrollbar {
display: none;
}
.wc-tab-btn {
flex-direction: column;
align-items: center;
gap: 0.25rem;
padding: 0.625rem 0.75rem;
min-width: 72px;
border-left: none;
border-bottom: 3px solid transparent;
font-size: 0.7rem;
text-align: center;
white-space: nowrap;
}
.wc-tab-btn.active {
border-left-color: transparent;
border-bottom-color: var(--bs-primary);
box-shadow: none;
}
.wc-tab-icon {
font-size: 1.1rem;
}
.wc-tab-content-col {
width: 100%;
max-width: 100%;
flex: 1 1 auto;
}
.wc-tab-content {
padding: 1rem 0.75rem;
}
}
/* -----------------------------------------------------------------
Review Table (Step 6)
----------------------------------------------------------------- */

View File

@@ -16,6 +16,9 @@ publicWidget.registry.WCAssessmentForm = publicWidget.Widget.extend({
'input .wc-measurement-input': '_onMeasurementChange',
'change .wc-unit-select': '_onMeasurementChange',
// equipment_type is now a hidden field — no change event needed
'click .wc-tab-btn': '_onSeatingTabClick',
'click .wc-group-header': '_onGroupHeaderClick',
'input .wc-option-filter': '_onOptionFilter',
'change .wc-radio-btn input[type="radio"]': '_onRadioBtnChange',
'click #btnGenerate': '_onClickGenerate',
'submit #wcForm': '_onFormSubmit',
@@ -52,6 +55,33 @@ publicWidget.registry.WCAssessmentForm = publicWidget.Widget.extend({
return '/my/quotation/api/' + endpoint;
},
/**
* Convert a string to Title Case.
* "ROHO HIGH-PROFILE CUSHION" → "Roho High-Profile Cushion"
*/
_toTitleCase: function (str) {
if (!str) return '';
return str.toLowerCase().replace(/(?:^|\s|[-/])\S/g, function (c) {
return c.toUpperCase();
});
},
/**
* Strip leading bracketed codes like [SESND1045] or (SESND1045) from a
* product name AND strip trailing parenthesised ADP codes like (SE0001692).
* Returns the cleaned display name in Title Case.
*/
_cleanProductName: function (name) {
if (!name) return '';
// Remove leading [CODE] or (CODE)
var cleaned = name.replace(/^\s*[\[(][A-Z0-9]+[\])]\s*/i, '');
// Remove trailing (CODE) that matches an ADP-style pattern
cleaned = cleaned.replace(/\s*\([A-Z]{2,}[A-Z0-9]*\)\s*$/i, '');
// Trim any leftover dashes/spaces
cleaned = cleaned.replace(/^\s*[-–—]\s*/, '').trim();
return this._toTitleCase(cleaned);
},
// =====================================================================
// INITIALIZATION
// =====================================================================
@@ -315,8 +345,9 @@ publicWidget.registry.WCAssessmentForm = publicWidget.Widget.extend({
item.href = '#';
item.className = 'list-group-item list-group-item-action';
item.style.cssText = 'background:var(--bs-body-bg,#fff);cursor:pointer;';
var displayName = self._cleanProductName(r.name);
var adpInfo = '';
if (r.adp_device_code) adpInfo += 'ADP: ' + r.adp_device_code;
if (r.adp_device_code) adpInfo += 'Code: ' + r.adp_device_code;
if (r.adp_price) adpInfo += ' \u2014 $' + r.adp_price.toFixed(2);
var variantBadge = '';
if (r.has_configurable_attributes) {
@@ -324,7 +355,7 @@ publicWidget.registry.WCAssessmentForm = publicWidget.Widget.extend({
r.variant_count + ' options</span>';
}
item.innerHTML = '<div class="d-flex justify-content-between align-items-center">' +
'<div><strong>' + r.name + '</strong>' + variantBadge +
'<div><strong>' + displayName + '</strong>' + variantBadge +
(adpInfo ? '<div class="small text-muted">' + adpInfo + '</div>' : '') +
'</div></div>';
item.addEventListener('click', function (e) {
@@ -343,8 +374,8 @@ publicWidget.registry.WCAssessmentForm = publicWidget.Widget.extend({
// Store template ID
this.el.querySelector('#frameProductTmplId').value = template.id;
// Display selected template info
this.el.querySelector('#selectedFrameName').textContent = template.name;
// Display selected template info (Title Case, cleaned)
this.el.querySelector('#selectedFrameName').textContent = this._cleanProductName(template.name);
this.el.querySelector('#selectedFrameCode').textContent = template.adp_device_code || 'N/A';
this.el.querySelector('#selectedFramePrice').textContent = (template.adp_price || 0).toFixed(2);
this.el.querySelector('#selectedFrame').classList.remove('d-none');
@@ -610,8 +641,95 @@ publicWidget.registry.WCAssessmentForm = publicWidget.Widget.extend({
},
/**
* Show/hide seating accordion sections based on their equipment_type
* data attribute. Filtering rules:
* Handle seating tab click — switch active tab and panel.
*/
_onSeatingTabClick: function (ev) {
var clickedBtn = ev.currentTarget;
if (clickedBtn.classList.contains('active')) return;
var container = clickedBtn.closest('.wc-seating-tabs');
if (!container) return;
// Deactivate all tabs and panels
container.querySelectorAll('.wc-tab-btn.active').forEach(function (btn) {
btn.classList.remove('active');
});
container.querySelectorAll('.wc-tab-panel.active').forEach(function (panel) {
panel.classList.remove('active');
});
// Activate clicked tab
clickedBtn.classList.add('active');
// Activate corresponding panel
var targetId = clickedBtn.dataset.bsTarget;
if (targetId) {
var panel = container.querySelector(targetId);
if (panel) panel.classList.add('active');
}
},
/**
* Real-time filter for product option cards within a seating tab.
* Filters by product name and ADP code as the user types.
*/
_onOptionFilter: function (ev) {
var input = ev.currentTarget;
var query = input.value.toLowerCase().trim();
var sectionContainer = input.closest('.wc-section-options');
if (!sectionContainer) return;
var cards = sectionContainer.querySelectorAll('.wc-options-grid > div');
cards.forEach(function (col) {
var card = col.querySelector('.wc-option-card');
if (!card) { col.style.display = ''; return; }
var searchText = card.dataset.searchText || '';
col.style.display = (!query || searchText.indexOf(query) !== -1) ? '' : 'none';
});
},
/**
* Toggle collapsible section groups within a seating tab.
* Accordion behaviour: opening one group closes the others.
*/
_onGroupHeaderClick: function (ev) {
var group = ev.currentTarget.closest('.wc-section-group');
if (!group) return;
var container = group.closest('.wc-section-groups');
if (!container) return;
if (group.classList.contains('open')) {
// Close it
group.classList.remove('open');
} else {
// Close all siblings, open this one
container.querySelectorAll('.wc-section-group.open').forEach(function (g) {
g.classList.remove('open');
});
group.classList.add('open');
}
},
/**
* Update the selection count badge on each section group header.
* Called after any checkbox change via _syncSelectedLines.
*/
_updateGroupCounts: function () {
var groups = this.el.querySelectorAll('.wc-section-group');
groups.forEach(function (group) {
var count = group.querySelectorAll('.wc-option-check:checked').length;
var badge = group.querySelector('.wc-group-count');
if (badge) {
badge.textContent = count;
}
group.classList.toggle('has-selection', count > 0);
});
},
/**
* Show/hide seating tab buttons and panels based on their equipment_type
* data attribute. Also ensures the first visible tab is active.
* Filtering rules:
* - 'both' → always visible (all equipment types)
* - 'wheelchair' → visible for manual_wheelchair and power_wheelchair
* - exact match → visible only for that specific equipment type
@@ -619,9 +737,16 @@ publicWidget.registry.WCAssessmentForm = publicWidget.Widget.extend({
*/
_filterSeatingSections: function (equipType) {
var wheelchairTypes = ['manual_wheelchair', 'power_wheelchair'];
var sectionItems = this.el.querySelectorAll('.wc-equipment-section');
sectionItems.forEach(function (item) {
var sectionEquipType = item.dataset.equipmentType;
var container = this.el.querySelector('.wc-seating-tabs');
if (!container) return;
var firstVisibleBtn = null;
var hasActive = false;
// Filter tab buttons
var tabBtns = container.querySelectorAll('.wc-tab-btn');
tabBtns.forEach(function (btn) {
var sectionEquipType = btn.dataset.equipmentType;
var show = false;
if (!sectionEquipType || sectionEquipType === 'both') {
show = true;
@@ -630,8 +755,40 @@ publicWidget.registry.WCAssessmentForm = publicWidget.Widget.extend({
} else {
show = sectionEquipType === equipType;
}
item.style.display = show ? '' : 'none';
btn.style.display = show ? '' : 'none';
if (show && !firstVisibleBtn) firstVisibleBtn = btn;
if (show && btn.classList.contains('active')) hasActive = true;
});
// Filter tab panels
var tabPanels = container.querySelectorAll('.wc-tab-panel');
tabPanels.forEach(function (panel) {
var sectionEquipType = panel.dataset.equipmentType;
var show = false;
if (!sectionEquipType || sectionEquipType === 'both') {
show = true;
} else if (sectionEquipType === 'wheelchair') {
show = wheelchairTypes.indexOf(equipType) !== -1;
} else {
show = sectionEquipType === equipType;
}
if (!show) {
panel.style.display = 'none';
panel.classList.remove('active');
} else {
panel.style.display = '';
}
});
// If no tab is active, activate the first visible one
if (!hasActive && firstVisibleBtn) {
firstVisibleBtn.classList.add('active');
var targetId = firstVisibleBtn.dataset.bsTarget;
if (targetId) {
var panel = container.querySelector(targetId);
if (panel) panel.classList.add('active');
}
}
},
_reloadEquipmentOptions: function () {
@@ -671,6 +828,49 @@ publicWidget.registry.WCAssessmentForm = publicWidget.Widget.extend({
var btn = radio.closest('.wc-radio-btn');
if (btn) self._applyRadioBtnStyle(btn, radio.checked);
});
// If this is a build-type radio, filter products in the matching section
if (ev.target.classList.contains('wc-section-build-type')) {
var sectionCode = ev.target.dataset.section;
var selectedBuildType = ev.target.value;
this._filterByBuildType(sectionCode, selectedBuildType);
}
},
/**
* Filter product cards by build type.
* Finds the tab panel containing this section and filters ALL option cards
* in the entire panel (main section + children share the same build type).
*/
_filterByBuildType: function (sectionCode, buildType) {
// Find the build-type radio, then walk up to the tab panel
var radio = this.el.querySelector(
'.wc-section-build-type[data-section="' + sectionCode + '"]'
);
var panel = radio ? radio.closest('.wc-tab-panel') : null;
// Fallback: search the whole form
var scope = panel || this.el;
var cards = scope.querySelectorAll('.wc-option-card[data-build-types]');
cards.forEach(function (card) {
var cardBuild = card.dataset.buildTypes || 'both';
var col = card.closest('[class*="col-"]');
if (!col) col = card.parentElement;
if (cardBuild === 'both' || cardBuild === buildType) {
col.style.display = '';
} else {
col.style.display = 'none';
// Uncheck hidden cards so they don't get submitted
var cb = card.querySelector('.wc-option-check');
if (cb && cb.checked) {
cb.checked = false;
}
}
});
this._syncSelectedLines();
this._updateGroupCounts();
},
_syncRadioActiveState: function () {
@@ -708,23 +908,30 @@ publicWidget.registry.WCAssessmentForm = publicWidget.Widget.extend({
options.forEach(function (opt) {
var col = document.createElement('div');
col.className = 'col-md-4';
col.className = 'col-md-4 col-6';
var displayName = self._cleanProductName(opt.product_name);
var priceHtml = opt.adp_price ? '<span class="text-success wc-opt-price">$' + opt.adp_price.toFixed(2) + '</span>' : '';
var adpHtml = opt.adp_device_code ? '<span class="text-muted wc-opt-adp">Code: ' + opt.adp_device_code + '</span>' : '';
var metaParts = [];
if (adpHtml) metaParts.push(adpHtml);
if (priceHtml) metaParts.push(priceHtml);
col.innerHTML =
'<div class="wc-option-card h-100 p-2" data-product-tmpl-id="' + opt.product_tmpl_id + '"' +
'<div class="wc-option-card wc-option-compact h-100" data-product-tmpl-id="' + opt.product_tmpl_id + '"' +
' data-section-id="' + (container.dataset.sectionId || '') + '"' +
' data-section-code="' + (container.dataset.section || '') + '"' +
' data-adp-code="' + (opt.adp_device_code || '') + '"' +
' data-price="' + (opt.list_price || 0) + '"' +
' data-build-types="' + (opt.available_build_types || 'both') + '"' +
' data-search-text="' + (opt.product_name + ' ' + (opt.adp_device_code || '')).toLowerCase().replace(/"/g, '') + '"' +
' data-rationale="' + (opt.requires_clinical_rationale ? '1' : '0') + '">' +
' <div class="form-check">' +
' <div class="form-check mb-0">' +
' <input class="form-check-input wc-option-check" type="checkbox"' +
' id="opt_' + opt.id + '" value="' + opt.product_tmpl_id + '"/>' +
' <label class="form-check-label" for="opt_' + opt.id + '">' +
' <strong>' + opt.product_name + '</strong>' +
(opt.adp_device_code ? '<br/><small class="text-muted">ADP: ' + opt.adp_device_code + '</small>' : '') +
(opt.adp_price ? '<br/><small class="text-success">$' + opt.adp_price.toFixed(2) + '</small>' : '') +
' <label class="form-check-label wc-opt-label" for="opt_' + opt.id + '">' +
displayName +
' </label>' +
' </div>' +
(metaParts.length ? ' <div class="wc-opt-meta">' + metaParts.join(' ') + '</div>' : '') +
(opt.requires_clinical_rationale ?
'<div class="wc-rationale-field d-none mt-1">' +
'<input type="text" class="form-control form-control-sm" placeholder="Clinical rationale *"/>' +
@@ -744,6 +951,25 @@ publicWidget.registry.WCAssessmentForm = publicWidget.Widget.extend({
grid.appendChild(col);
});
// Apply build-type filter for newly rendered options
// The build-type radio might be for this section or a parent section
var sectionCode = container.dataset.section;
if (sectionCode) {
var buildRadio = this.el.querySelector(
'.wc-section-build-type[data-section="' + sectionCode + '"]:checked'
);
if (!buildRadio) {
// Child section: find the build-type radio in the same tab panel
var panel = container.closest('.wc-tab-panel');
if (panel) {
buildRadio = panel.querySelector('.wc-section-build-type:checked');
}
}
if (buildRadio) {
this._filterByBuildType(buildRadio.dataset.section, buildRadio.value);
}
}
},
_renderAdpOptions: function (options) {
@@ -771,17 +997,34 @@ publicWidget.registry.WCAssessmentForm = publicWidget.Widget.extend({
cb.value = opt.product_tmpl_id;
cb.style.cssText = 'width:18px;height:18px;min-width:18px;margin-top:2px;cursor:pointer;accent-color:var(--bs-primary);';
var displayName = self._cleanProductName(opt.product_name);
var content = document.createElement('div');
content.style.cssText = 'flex:1;min-width:0;';
var lbl = document.createElement('label');
lbl.className = 'wc-option-label';
lbl.setAttribute('for', cb.id);
lbl.style.cssText = 'font-size:0.82rem;line-height:1.35;cursor:pointer;';
lbl.textContent = opt.product_name;
lbl.style.cssText = 'font-size:0.82rem;line-height:1.35;cursor:pointer;display:block;';
lbl.textContent = displayName;
if (opt.requires_clinical_rationale) {
lbl.innerHTML = lbl.textContent + ' <span class="text-danger fw-bold">*</span>';
}
content.appendChild(lbl);
// Meta line: Code + price
var metaParts = [];
if (opt.adp_device_code) metaParts.push('<span class="text-muted">Code: ' + opt.adp_device_code + '</span>');
if (opt.adp_price) metaParts.push('<span class="text-success">$' + opt.adp_price.toFixed(2) + '</span>');
if (metaParts.length) {
var meta = document.createElement('div');
meta.className = 'wc-opt-meta';
meta.style.cssText = 'font-size:0.7rem;line-height:1.2;';
meta.innerHTML = metaParts.join(' ');
content.appendChild(meta);
}
card.appendChild(cb);
card.appendChild(lbl);
card.appendChild(content);
if (opt.requires_clinical_rationale) {
var rf = document.createElement('div');
@@ -833,18 +1076,31 @@ publicWidget.registry.WCAssessmentForm = publicWidget.Widget.extend({
cb.value = opt.product_tmpl_id;
cb.style.cssText = 'width:18px;height:18px;min-width:18px;margin-top:2px;cursor:pointer;accent-color:var(--bs-primary);';
var displayName = self._cleanProductName(opt.product_name);
var content = document.createElement('div');
content.style.cssText = 'flex:1;min-width:0;';
var lbl = document.createElement('label');
lbl.className = 'wc-option-label';
lbl.setAttribute('for', cb.id);
lbl.style.cssText = 'font-size:0.82rem;line-height:1.35;cursor:pointer;';
var labelText = opt.product_name;
if (opt.adp_price) {
labelText += ' <small class="text-success">($' + opt.adp_price.toFixed(2) + ')</small>';
lbl.style.cssText = 'font-size:0.82rem;line-height:1.35;cursor:pointer;display:block;';
lbl.textContent = displayName;
content.appendChild(lbl);
// Meta line: Code + price
var metaParts = [];
if (opt.adp_device_code) metaParts.push('<span class="text-muted">Code: ' + opt.adp_device_code + '</span>');
if (opt.adp_price) metaParts.push('<span class="text-success">$' + opt.adp_price.toFixed(2) + '</span>');
if (metaParts.length) {
var meta = document.createElement('div');
meta.className = 'wc-opt-meta';
meta.style.cssText = 'font-size:0.7rem;line-height:1.2;';
meta.innerHTML = metaParts.join(' ');
content.appendChild(meta);
}
lbl.innerHTML = labelText;
card.appendChild(cb);
card.appendChild(lbl);
card.appendChild(content);
// Click anywhere on card toggles checkbox
card.addEventListener('click', function (e) {
@@ -909,6 +1165,9 @@ publicWidget.registry.WCAssessmentForm = publicWidget.Widget.extend({
rationale: rationaleInput ? rationaleInput.value : '',
});
});
// Update collapsible group count badges
this._updateGroupCounts();
},
_populateHiddenLineFields: function () {