feat(ipd): enable autosearch and row-level update on discharge complaint table

This commit is contained in:
DevSoftTech Server 2026-09-22 20:20:11 +05:30
parent 79020e806b
commit 341c09d9c7
2 changed files with 413 additions and 24 deletions

View File

@ -2693,6 +2693,17 @@ class Ipd_discharge extends BaseController
continue;
}
if ($rowRemark !== '' && ($rowRemark[0] === '{' || $rowRemark[0] === '[')) {
$decoded = json_decode($rowRemark, true);
if (is_array($decoded)) {
$parts = [];
if (! empty($decoded['duration'])) $parts[] = (string) $decoded['duration'];
if (! empty($decoded['frequency'])) $parts[] = (string) $decoded['frequency'];
if (! empty($decoded['severity'])) $parts[] = (string) $decoded['severity'];
$rowRemark = implode(', ', $parts);
}
}
if ($report !== '' && $rowRemark !== '') {
$lines[] = esc($report) . ' <i>' . esc($rowRemark) . '</i>';
} elseif ($report !== '') {
@ -2714,6 +2725,38 @@ class Ipd_discharge extends BaseController
return '<p><b>' . esc($title) . '</b> :<br /> ' . implode('<br /> ', $lines) . '</p>';
}
private function getDischargeComplaintRows(int $ipdId): array
{
$rawRows = $this->byIpdRows('ipd_discharge_complaint', ['id', 'comp_report', 'comp_remark'], 'id ASC', $ipdId);
$out = [];
foreach ($rawRows as $row) {
$id = (int) ($row['id'] ?? 0);
$term = (string) ($row['comp_report'] ?? '');
$rawRemark = (string) ($row['comp_remark'] ?? '');
$freq = '';
$sev = '';
$dur = $rawRemark;
if ($rawRemark !== '' && ($rawRemark[0] === '{' || $rawRemark[0] === '[')) {
$decoded = json_decode($rawRemark, true);
if (is_array($decoded)) {
$dur = (string) ($decoded['duration'] ?? '');
$freq = (string) ($decoded['frequency'] ?? '');
$sev = (string) ($decoded['severity'] ?? '');
}
}
$out[] = [
'id' => $id,
'term' => $term,
'frequency' => $freq,
'severity' => $sev,
'duration' => $dur,
'date' => '',
];
}
return $out;
}
private function sanitizeComplaintNarrativeRemark(string $remark): string
{
$remark = trim($remark);
@ -6276,7 +6319,7 @@ class Ipd_discharge extends BaseController
&& ($this->request->isAJAX() || $this->request->getPost('ajax_mode') === 'json')
&& in_array(
(string) ($this->request->getPost('action') ?? ''),
['add_drug', 'remove_drug', 'remove_all_drugs', 'add_course', 'remove_course', 'apply_rx_group'],
['add_drug', 'remove_drug', 'remove_all_drugs', 'add_course', 'remove_course', 'apply_rx_group', 'add_complaint', 'update_complaint', 'remove_complaint'],
true
);
@ -6382,8 +6425,22 @@ class Ipd_discharge extends BaseController
}
if ($action === 'add_complaint') {
$complaintName = trim((string) ($this->request->getPost('new_complaint_name') ?? ''));
$complaintName = trim((string) ($this->request->getPost('complaint_term') ?? $this->request->getPost('new_complaint_name') ?? ''));
$complaintDuration = trim((string) ($this->request->getPost('complaint_duration') ?? ''));
$complaintFreq = trim((string) ($this->request->getPost('complaint_frequency') ?? ''));
$complaintSev = trim((string) ($this->request->getPost('complaint_severity') ?? ''));
$complaintRemarkRow = trim((string) ($this->request->getPost('new_complaint_remark') ?? ''));
if ($complaintFreq !== '' || $complaintSev !== '') {
$complaintRemarkRow = json_encode([
'duration' => $complaintDuration,
'frequency' => $complaintFreq,
'severity' => $complaintSev,
], JSON_UNESCAPED_UNICODE) ?: $complaintDuration;
} elseif ($complaintDuration !== '') {
$complaintRemarkRow = $complaintDuration;
}
$complaintRemarkText = trim((string) ($this->request->getPost('complaint_remark') ?? ''));
$painValue = trim((string) ($this->request->getPost('pain_value') ?? ''));
if (! in_array($painValue, ['0', '1', '2', '3', '4'], true)) {
@ -6404,10 +6461,11 @@ class Ipd_discharge extends BaseController
}
$savedAny = (bool) $this->db->table('ipd_discharge_complaint')->insert($insert);
$ajaxRowId = $savedAny ? (int) ($this->db->insertID() ?? 0) : 0;
$notice = $savedAny ? 'Complaint row added.' : 'Unable to add complaint row.';
$noticeType = $savedAny ? 'success' : 'warning';
if ($this->tableHasColumns('ipd_discharge_complaint_remark', ['ipd_id'])) {
if ($this->tableHasColumns('ipd_discharge_complaint_remark', ['ipd_id']) && $complaintRemarkText !== '') {
$savedAny = $this->upsertByIpd('ipd_discharge_complaint_remark', $ipdId, [
'comp_report' => $this->buildComplaintMetaPayload(['pain_value' => $painValue]),
'comp_remark' => $complaintRemarkText,
@ -6418,6 +6476,41 @@ class Ipd_discharge extends BaseController
$notice = 'Enter complaint name before adding.';
$noticeType = 'warning';
}
} elseif ($action === 'update_complaint') {
$complaintId = (int) ($this->request->getPost('complaint_row_id') ?? 0);
$complaintName = trim((string) ($this->request->getPost('complaint_term') ?? $this->request->getPost('new_complaint_name') ?? ''));
$complaintDuration = trim((string) ($this->request->getPost('complaint_duration') ?? ''));
$complaintFreq = trim((string) ($this->request->getPost('complaint_frequency') ?? ''));
$complaintSev = trim((string) ($this->request->getPost('complaint_severity') ?? ''));
if ($complaintFreq !== '' || $complaintSev !== '') {
$remarkPayload = json_encode([
'duration' => $complaintDuration,
'frequency' => $complaintFreq,
'severity' => $complaintSev,
], JSON_UNESCAPED_UNICODE) ?: $complaintDuration;
} else {
$remarkPayload = $complaintDuration;
}
if ($complaintId > 0 && $complaintName !== '' && $this->tableHasColumns('ipd_discharge_complaint', ['id', 'ipd_id'])) {
$updateData = [
'comp_report' => $complaintName,
'comp_remark' => $remarkPayload,
'update_by' => $userLabel,
];
$this->db->table('ipd_discharge_complaint')
->where('id', $complaintId)
->where('ipd_id', $ipdId)
->update($updateData);
$savedAny = true;
$ajaxRowId = $complaintId;
$notice = 'Complaint updated successfully.';
$noticeType = 'success';
} else {
$notice = 'Unable to update complaint: invalid ID or missing name.';
$noticeType = 'warning';
}
} elseif ($action === 'remove_complaint') {
$removeId = (int) ($this->request->getPost('complaint_remove_id') ?? 0);
$complaintRemarkText = trim((string) ($this->request->getPost('complaint_remark') ?? ''));
@ -6430,10 +6523,11 @@ class Ipd_discharge extends BaseController
->where('id', $removeId)
->where('ipd_id', $ipdId)
->delete();
$ajaxRowId = $removeId;
$notice = $savedAny ? 'Complaint row removed.' : 'Unable to remove complaint row.';
$noticeType = $savedAny ? 'success' : 'warning';
if ($this->tableHasColumns('ipd_discharge_complaint_remark', ['ipd_id'])) {
if ($this->tableHasColumns('ipd_discharge_complaint_remark', ['ipd_id']) && $complaintRemarkText !== '') {
$savedAny = $this->upsertByIpd('ipd_discharge_complaint_remark', $ipdId, [
'comp_report' => $this->buildComplaintMetaPayload(['pain_value' => $painValue]),
'comp_remark' => $complaintRemarkText,
@ -6599,7 +6693,8 @@ class Ipd_discharge extends BaseController
}
}
if (($this->request->isAJAX() || $this->request->getPost('ajax_mode') === 'json') && in_array($action, ['add_surgery', 'remove_surgery', 'add_procedure', 'remove_procedure', 'add_diagnosis', 'remove_diagnosis'], true)) {
if (($this->request->isAJAX() || $this->request->getPost('ajax_mode') === 'json') && in_array($action, ['add_complaint', 'update_complaint', 'remove_complaint', 'add_surgery', 'remove_surgery', 'add_procedure', 'remove_procedure', 'add_diagnosis', 'remove_diagnosis'], true)) {
$complaintRows = $this->getDischargeComplaintRows($ipdId);
$surgeryRows = $this->byIpdRows('ipd_discharge_surgery', ['id', 'surgery_name', 'surgery_date', 'surgery_remark'], 'id ASC', $ipdId);
$procedureRows = $this->byIpdRows('ipd_discharge_procedure', ['id', 'procedure_name', 'procedure_date', 'procedure_remark'], 'id ASC', $ipdId);
$diagnosisRows = $this->byIpdRows('ipd_discharge_diagnosis', ['id', 'comp_report', 'comp_remark'], 'id ASC', $ipdId);
@ -6610,6 +6705,7 @@ class Ipd_discharge extends BaseController
'noticeType' => $noticeType ?? 'info',
'row_id' => $ajaxRowId ?? 0,
'row_source' => $ajaxRowSource ?? 'legacy',
'complaintRows' => $complaintRows,
'surgeryRows' => $surgeryRows,
'procedureRows' => $procedureRows,
'diagnosisRows' => $diagnosisRows,
@ -7187,11 +7283,23 @@ class Ipd_discharge extends BaseController
}
$duration = trim((string) ($row['duration'] ?? ''));
$freq = trim((string) ($row['frequency'] ?? ''));
$sev = trim((string) ($row['severity'] ?? ''));
$rowId = (int) ($row['id'] ?? 0);
if ($freq !== '' || $sev !== '') {
$remarkPayload = json_encode([
'duration' => $duration,
'frequency' => $freq,
'severity' => $sev,
], JSON_UNESCAPED_UNICODE) ?: $duration;
} else {
$remarkPayload = $duration;
}
$commonData = [
'comp_report' => $term,
'comp_remark' => $duration,
'comp_remark' => $remarkPayload,
'update_by' => $userLabel,
];
if ($this->db->fieldExists('order_id', 'ipd_discharge_complaint')) {
@ -7752,6 +7860,7 @@ class Ipd_discharge extends BaseController
'row_id' => $ajaxRowId,
'row_source' => $ajaxRowSource,
'drugRows' => $drugRows,
'complaintRows' => $this->getDischargeComplaintRows($ipdId),
'csrfName' => csrf_token(),
'csrfHash' => csrf_hash(),
]);

View File

@ -630,7 +630,7 @@ $historyFields = [
<th style="width:130px">Frequency</th>
<th style="width:120px">Severity</th>
<th style="width:140px">Duration</th>
<th style="width:28px"></th>
<th style="width:115px;text-align:center;">Action</th>
</tr>
</thead>
<tbody id="discharge_complaint_tbody">
@ -646,7 +646,7 @@ $historyFields = [
style="display:none;position:absolute;left:0;right:0;top:100%;z-index:1060;max-height:260px;overflow-y:auto;"></div>
</td>
<td class="text-center">
<button type="button" class="btn btn-sm btn-outline-primary p-0" id="btn_discharge_add_complaint" title="Add Complaint" style="width:24px;height:24px;line-height:1">+</button>
<button type="button" class="btn btn-sm btn-outline-primary" id="btn_discharge_add_complaint" title="Add Complaint" style="height:26px;line-height:1;padding:0 8px;">+ Add</button>
</td>
</tr>
</tfoot>
@ -665,12 +665,24 @@ $historyFields = [
$complaintSeedRows = [];
if (isset($complaint_rows) && is_array($complaint_rows)) {
foreach ($complaint_rows as $row) {
$rawRemark = (string) ($row['comp_remark'] ?? '');
$freq = '';
$sev = '';
$dur = $rawRemark;
if ($rawRemark !== '' && ($rawRemark[0] === '{' || $rawRemark[0] === '[')) {
$decoded = json_decode($rawRemark, true);
if (is_array($decoded)) {
$dur = (string) ($decoded['duration'] ?? '');
$freq = (string) ($decoded['frequency'] ?? '');
$sev = (string) ($decoded['severity'] ?? '');
}
}
$complaintSeedRows[] = [
'id' => (int) ($row['id'] ?? 0),
'term' => (string) ($row['comp_report'] ?? ''),
'frequency' => '',
'severity' => '',
'duration' => (string) ($row['comp_remark'] ?? ''),
'frequency' => $freq,
'severity' => $sev,
'duration' => $dur,
'date' => '',
];
}
@ -680,6 +692,7 @@ $historyFields = [
<input type="hidden" name="discharge_complaints_json" id="discharge_complaints_json" value="">
<!-- Fixed dropdowns for table cell inputs -->
<div id="discharge_row_complaint_dd" style="display:none;position:fixed;z-index:1095;min-width:240px;background:#fff;border:1px solid #dee2e6;border-radius:.375rem;box-shadow:0 6px 16px rgba(0,0,0,.15);max-height:240px;overflow-y:auto;"></div>
<div id="discharge_freq_dd" style="display:none;position:fixed;z-index:1090;min-width:160px;background:#fff;border:1px solid #dee2e6;border-radius:.375rem;box-shadow:0 4px 12px rgba(0,0,0,.12);max-height:180px;overflow-y:auto;"></div>
<div id="discharge_sev_dd" style="display:none;position:fixed;z-index:1090;min-width:140px;background:#fff;border:1px solid #dee2e6;border-radius:.375rem;box-shadow:0 4px 12px rgba(0,0,0,.12);max-height:180px;overflow-y:auto;"></div>
<div id="discharge_dur_dd" style="display:none;position:fixed;z-index:1090;min-width:160px;background:#fff;border:1px solid #dee2e6;border-radius:.375rem;box-shadow:0 4px 12px rgba(0,0,0,.12);max-height:180px;overflow-y:auto;"></div>
@ -2302,9 +2315,10 @@ $historyFields = [
nameInput.name = 'complaint_term[]';
nameInput.className = 'form-control form-control-sm complaint-name-input';
nameInput.value = item.term || '';
nameInput.placeholder = 'Complaint…';
nameInput.placeholder = 'Complaint (Type to search)…';
nameInput.style.fontSize = '.82rem';
nameInput.setAttribute('data-idx', idx);
nameInput.setAttribute('autocomplete', 'off');
nameInput.addEventListener('input', function() {
selectedDischargeComplaints[idx].term = this.value;
syncDischargeComplaintJsonField();
@ -2323,6 +2337,7 @@ $historyFields = [
freqInput.placeholder = 'daily…';
freqInput.style.fontSize = '.82rem';
freqInput.setAttribute('data-idx', idx);
freqInput.setAttribute('autocomplete', 'off');
freqInput.addEventListener('input', function() {
selectedDischargeComplaints[idx].frequency = this.value;
syncDischargeComplaintJsonField();
@ -2341,6 +2356,7 @@ $historyFields = [
sevInput.placeholder = 'mild…';
sevInput.style.fontSize = '.82rem';
sevInput.setAttribute('data-idx', idx);
sevInput.setAttribute('autocomplete', 'off');
sevInput.addEventListener('input', function() {
selectedDischargeComplaints[idx].severity = this.value;
syncDischargeComplaintJsonField();
@ -2359,6 +2375,7 @@ $historyFields = [
durInput.placeholder = '2 days…';
durInput.style.fontSize = '.82rem';
durInput.setAttribute('data-idx', idx);
durInput.setAttribute('autocomplete', 'off');
durInput.addEventListener('input', function() {
selectedDischargeComplaints[idx].duration = this.value;
syncDischargeComplaintJsonField();
@ -2372,21 +2389,37 @@ $historyFields = [
idInput.value = parseInt(item.id || 0, 10) || 0;
tr.appendChild(idInput);
// Remove button
// Action column with Update and Remove buttons
var td6 = document.createElement('td');
td6.className = 'p-1 text-center';
td6.className = 'p-1 text-center text-nowrap';
var updateBtn = document.createElement('button');
updateBtn.type = 'button';
updateBtn.className = 'btn btn-sm btn-outline-success btn-update-complaint py-0 px-2 me-1';
updateBtn.title = 'Save / Update this complaint';
updateBtn.style.fontSize = '.78rem';
updateBtn.style.height = '26px';
updateBtn.setAttribute('data-idx', idx);
updateBtn.innerHTML = '<i class="fas fa-check me-1"></i>Update';
updateBtn.addEventListener('click', function() {
saveDischargeComplaintRow(idx, $(this));
});
td6.appendChild(updateBtn);
var removeBtn = document.createElement('button');
removeBtn.type = 'button';
removeBtn.className = 'btn btn-sm text-danger p-0';
removeBtn.className = 'btn btn-sm btn-outline-danger btn-remove-complaint p-0';
removeBtn.title = 'Remove this complaint';
removeBtn.style.width = '24px';
removeBtn.style.height = '26px';
removeBtn.style.lineHeight = '1';
removeBtn.style.fontSize = '1rem';
removeBtn.setAttribute('data-idx', idx);
removeBtn.innerHTML = '×';
removeBtn.addEventListener('click', function() {
selectedDischargeComplaints.splice(idx, 1);
renderDischargeComplaintTable();
setComplaintStatus('Complaint removed.', 'success');
removeDischargeComplaintRow(idx, $(this));
});
td6.appendChild(removeBtn);
tr.appendChild(td6);
tbody.appendChild(tr);
});
@ -2394,6 +2427,110 @@ $historyFields = [
syncDischargeComplaintJsonField();
}
function saveDischargeComplaintRow(idx, $btn) {
var item = selectedDischargeComplaints[idx];
if (!item) return;
// Sync latest values from inputs in this row
var $tr = $('#discharge_complaint_tbody tr[data-idx="' + idx + '"]');
if ($tr.length) {
var termVal = ($tr.find('.complaint-name-input').val() || '').trim();
var freqVal = ($tr.find('.complaint-freq-input').val() || '').trim();
var sevVal = ($tr.find('.complaint-sev-input').val() || '').trim();
var durVal = ($tr.find('.complaint-dur-input').val() || '').trim();
item.term = termVal;
item.frequency = freqVal;
item.severity = sevVal;
item.duration = durVal;
}
if (!item.term) {
setComplaintStatus('Please enter or select a complaint name.', 'error');
$tr.find('.complaint-name-input').trigger('focus');
return;
}
var form = document.getElementById('section-complaints')
? document.getElementById('section-complaints').closest('form')
: document.querySelector('form');
var csrf = getCsrfPair(form);
var isUpdate = parseInt(item.id || 0, 10) > 0;
var payload = {
action: isUpdate ? 'update_complaint' : 'add_complaint',
complaint_row_id: parseInt(item.id || 0, 10),
complaint_term: item.term,
complaint_frequency: item.frequency,
complaint_severity: item.severity,
complaint_duration: item.duration,
ajax_mode: 'json'
};
payload[csrf.name] = csrf.value;
var origHtml = $btn ? $btn.html() : '';
if ($btn) $btn.prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i>');
$.post($(form).attr('action') || window.location.href, payload, function(data) {
updateFormCsrf(form, data);
if ($btn) $btn.prop('disabled', false).html(origHtml);
if (data && (data.update === 1 || data.update === true)) {
if (data.row_id && parseInt(data.row_id, 10) > 0) {
selectedDischargeComplaints[idx].id = parseInt(data.row_id, 10);
$tr.find('input[name="complaint_row_id[]"]').val(data.row_id);
}
syncDischargeComplaintJsonField();
setComplaintStatus(data.notice || 'Complaint updated successfully.', 'success');
if ($btn) {
$btn.removeClass('btn-outline-success').addClass('btn-success').html('<i class="fas fa-check me-1"></i>Saved');
setTimeout(function() {
$btn.removeClass('btn-success').addClass('btn-outline-success').html('<i class="fas fa-check me-1"></i>Update');
}, 1200);
}
} else {
setComplaintStatus((data && data.notice) ? data.notice : 'Unable to save complaint.', 'error');
}
}, 'json').fail(function() {
if ($btn) $btn.prop('disabled', false).html(origHtml);
setComplaintStatus('Network error while saving complaint.', 'error');
});
}
function removeDischargeComplaintRow(idx, $btn) {
var item = selectedDischargeComplaints[idx];
if (!item) return;
var rowId = parseInt(item.id || 0, 10);
if (rowId > 0) {
var form = document.getElementById('section-complaints')
? document.getElementById('section-complaints').closest('form')
: document.querySelector('form');
var csrf = getCsrfPair(form);
var payload = {
action: 'remove_complaint',
complaint_remove_id: rowId,
ajax_mode: 'json'
};
payload[csrf.name] = csrf.value;
if ($btn) $btn.prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i>');
$.post($(form).attr('action') || window.location.href, payload, function(data) {
updateFormCsrf(form, data);
selectedDischargeComplaints.splice(idx, 1);
renderDischargeComplaintTable();
setComplaintStatus((data && data.notice) ? data.notice : 'Complaint removed.', 'success');
}, 'json').fail(function() {
selectedDischargeComplaints.splice(idx, 1);
renderDischargeComplaintTable();
setComplaintStatus('Complaint removed from list.', 'success');
});
} else {
selectedDischargeComplaints.splice(idx, 1);
renderDischargeComplaintTable();
setComplaintStatus('Complaint removed.', 'success');
}
}
// Quick Autotext Chips
function renderDischargeRecentChips() {
var $box = $('#discharge_recent_complaint_chips');
@ -2431,17 +2568,144 @@ $historyFields = [
});
}
// ─── Inline Autotext Dropdowns (Frequency, Severity, Duration) ─────────
// ─── Inline Autotext Dropdowns (Complaint Name, Frequency, Severity, Duration) ─────────
var _DISCHARGE_FREQ_OPTIONS = ['daily', 'twice daily', 'weekly', 'intermittent', 'continuous', 'occasional'];
var _DISCHARGE_SEV_OPTIONS = ['mild', 'moderate', 'severe', 'profound'];
var _DISCHARGE_DUR_UNITS = ['hours', 'days', 'weeks', 'months', 'years'];
var _dischargeRowDdIdx = -1;
function _positionDischargeDd($dd, $input) {
if (!$input || !$input.length) return;
var r = $input[0].getBoundingClientRect();
$dd.css({ top: (r.bottom + 2) + 'px', left: r.left + 'px', width: Math.max(r.width, 160) + 'px' });
$dd.css({ top: (r.bottom + 2) + 'px', left: r.left + 'px', width: Math.max(r.width, 180) + 'px' });
}
function renderRowComplaintDropdown(rows, $inp, idx) {
var $dd = $('#discharge_row_complaint_dd').empty();
_dischargeRowDdIdx = -1;
if (!rows || !rows.length) {
$dd.append('<div class="px-3 py-2 text-muted small">No matching complaints found</div>');
_positionDischargeDd($dd, $inp);
$dd.show();
return;
}
rows.forEach(function(row) {
var term = ((row.name || row.term) || '').toString();
var source = (row.source || '').toString();
var hierarchy = (row.hierarchy || '').toString();
var isSnomed = source === 'snomed';
var nameColor = isSnomed ? '#0d6efd' : '#212529';
var $item = $('<div class="px-3 py-2 border-bottom discharge-row-complaint-item" style="cursor:pointer;font-size:.85rem;line-height:1.3;transition:background .1s"></div>');
$item.append($('<div class="fw-semibold text-truncate" style="color:' + nameColor + '">').text(term));
if (isSnomed && hierarchy) {
$item.append($('<div class="text-muted" style="font-size:.72rem">').text(hierarchy));
}
$item.data('term', term);
$item.on('mouseenter', function() { $(this).css('background', '#f0f4ff'); })
.on('mouseleave', function() { $(this).css('background', ''); })
.on('mousedown', function(e) { e.preventDefault(); })
.on('click', function() {
var selectedTerm = $(this).data('term') || term;
$inp.val(selectedTerm);
if (idx >= 0 && idx < selectedDischargeComplaints.length) {
selectedDischargeComplaints[idx].term = selectedTerm;
syncDischargeComplaintJsonField();
}
$dd.hide().empty();
_dischargeRowDdIdx = -1;
$inp.closest('tr').find('.complaint-freq-input').trigger('focus');
});
$dd.append($item);
});
_positionDischargeDd($dd, $inp);
$dd.show();
}
$(document).on('input focus', '.complaint-name-input', function(e) {
var $inp = $(this);
var q = ($inp.val() || '').trim();
var idx = parseInt($inp.attr('data-idx'), 10);
if (idx >= 0 && idx < selectedDischargeComplaints.length) {
selectedDischargeComplaints[idx].term = $inp.val();
syncDischargeComplaintJsonField();
}
if (q.length < 1) {
$('#discharge_row_complaint_dd').hide().empty();
_dischargeRowDdIdx = -1;
return;
}
var cacheKey = q.toUpperCase();
if (_dischargeComplaintSearchCache[cacheKey]) {
renderRowComplaintDropdown(_dischargeComplaintSearchCache[cacheKey], $inp, idx);
return;
}
if (_dischargeComplaintSearchTimer) clearTimeout(_dischargeComplaintSearchTimer);
if (_dischargeComplaintXhr) {
try { _dischargeComplaintXhr.abort(); } catch(e){}
_dischargeComplaintXhr = null;
}
_dischargeComplaintSearchTimer = setTimeout(function() {
if (document.activeElement !== $inp[0]) return;
_dischargeComplaintXhr = $.ajax({
url: '<?= base_url('Opd_prescription/complaints_search') ?>',
data: { q: q },
dataType: 'json',
success: function(data) {
_dischargeComplaintXhr = null;
if (document.activeElement !== $inp[0]) return;
var rows = (data && data.rows) ? data.rows : [];
_dischargeComplaintSearchCache[cacheKey] = rows;
renderRowComplaintDropdown(rows, $inp, idx);
},
error: function() {
_dischargeComplaintXhr = null;
}
});
}, 200);
});
$(document).on('keydown', '.complaint-name-input', function(e) {
var $dd = $('#discharge_row_complaint_dd');
var $items = $dd.find('.discharge-row-complaint-item');
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
if (!$dd.is(':visible') || !$items.length) return;
e.preventDefault();
_dischargeRowDdIdx = e.key === 'ArrowDown'
? Math.min(_dischargeRowDdIdx + 1, $items.length - 1)
: Math.max(_dischargeRowDdIdx - 1, 0);
$items.css('background', '').eq(_dischargeRowDdIdx).css('background', '#f0f4ff');
} else if (e.key === 'Enter') {
if ($dd.is(':visible') && _dischargeRowDdIdx >= 0 && _dischargeRowDdIdx < $items.length) {
e.preventDefault();
$items.eq(_dischargeRowDdIdx).trigger('click');
} else {
$(this).closest('tr').find('.complaint-freq-input').trigger('focus');
}
$dd.hide();
_dischargeRowDdIdx = -1;
} else if (e.key === 'Escape') {
$dd.hide();
_dischargeRowDdIdx = -1;
}
});
$(document).on('blur', '.complaint-name-input', function() {
setTimeout(function() {
$('#discharge_row_complaint_dd').hide();
_dischargeRowDdIdx = -1;
}, 200);
});
function getDischargeDurationSuggestions(input) {
input = (input || '').toString().trim();
var numMatch = input.match(/^(\d+\.?\d*)\s*(.*)/);
@ -2542,6 +2806,9 @@ $historyFields = [
if (!$(e.target).closest('#discharge_complaint_lookup, #discharge_complaint_dropdown').length) {
closeDischargeComplaintDropdown();
}
if (!$(e.target).closest('.complaint-name-input, #discharge_row_complaint_dd').length) {
$('#discharge_row_complaint_dd').hide();
}
if (!$(e.target).closest('.complaint-freq-input, #discharge_freq_dd').length) {
$('#discharge_freq_dd').hide();
}
@ -7750,7 +8017,7 @@ $historyFields = [
var actionValue = submitter && submitter.name === 'action' ? String(submitter.value || '') : '';
var saveMode = submitter && submitter.dataset ? String(submitter.dataset.saveMode || '').toLowerCase() : '';
var statusTargetId = submitter && submitter.dataset ? String(submitter.dataset.statusId || '').trim() : '';
var isComplaintAction = actionValue === 'add_complaint' || actionValue === 'remove_complaint';
var isComplaintAction = actionValue === 'add_complaint' || actionValue === 'remove_complaint' || actionValue === 'update_complaint';
var isMedicineAction = actionValue === 'add_drug' || actionValue === 'remove_drug' || actionValue === 'apply_rx_group';
var targetSectionId = '';
@ -7776,6 +8043,9 @@ $historyFields = [
if (isMedicineAction) {
targetSectionId = 'section-medicine';
}
if (isComplaintAction) {
targetSectionId = 'section-complaints';
}
if (actionValue === 'add_procedure') {
var procedureDateInput = form.querySelector('[name="new_procedure_date"]');
@ -7797,7 +8067,7 @@ $historyFields = [
}).done(function(result) {
// The server always returns JSON for AJAX requests (isAJAX() === true in CI4).
// Because dataType is 'html', jQuery does NOT auto-parse it — result is a raw
// JSON string. Detect this by attempting JSON.parse and handle medicine actions.
// JSON string. Detect this by attempting JSON.parse and handle medicine/complaint actions.
var parsedJson = null;
if (typeof result === 'string' && result.charAt(0) === '{') {
try { parsedJson = JSON.parse(result); } catch (e) { parsedJson = null; }
@ -7819,11 +8089,21 @@ $historyFields = [
noticeLevel = noticeLevel === 'success' ? 'error' : noticeLevel;
}
if (noticeText !== '') {
setSectionStatus('discharge_medicine_status', noticeText, noticeLevel === 'success' ? 'success' : 'error');
var statusIdToUse = statusTargetId !== '' ? statusTargetId : ((targetSectionId === 'section-complaints' || isComplaintAction) ? 'discharge_complaint_status' : 'discharge_medicine_status');
setSectionStatus(statusIdToUse, noticeText, noticeLevel === 'success' ? 'success' : 'error');
if (typeof window.notify === 'function') {
window.notify(noticeLevel === 'success' ? 'success' : 'error', 'Discharge Update', noticeText);
}
}
// Re-render complaint table rows from returned complaintRows array
if ((isComplaintAction || targetSectionId === 'section-complaints') && Array.isArray(parsedJson.complaintRows)) {
if (typeof selectedDischargeComplaints !== 'undefined') {
selectedDischargeComplaints = parsedJson.complaintRows;
if (typeof renderDischargeComplaintTable === 'function') {
renderDischargeComplaintTable();
}
}
}
// Re-render medicine table rows from the returned drugRows array
if (isMedicineAction && Array.isArray(parsedJson.drugRows)) {
var tbody = document.getElementById('discharge_medicine_tbody');