Add OPD keyword manager and caret-aligned autocomplete

This commit is contained in:
DevSoftTech Server 2026-04-09 06:25:03 +05:30
parent 418fb3e5e7
commit 7275d13500
3 changed files with 621 additions and 8 deletions

View File

@ -535,6 +535,9 @@ $routes->get('Opd_prescription/get_complaints', 'Opd_prescription::get_complaint
$routes->get('Opd_prescription/get_disease', 'Opd_prescription::get_disease');
$routes->get('Opd_prescription/get_finding_exam', 'Opd_prescription::get_finding_exam');
$routes->post('Opd_prescription/autotype_keyword_save', 'Opd_prescription::autotype_keyword_save');
$routes->get('Opd_prescription/autotype_keyword_list', 'Opd_prescription::autotype_keyword_list');
$routes->post('Opd_prescription/autotype_keyword_update', 'Opd_prescription::autotype_keyword_update');
$routes->post('Opd_prescription/autotype_keyword_remove', 'Opd_prescription::autotype_keyword_remove');
$routes->post('Opd_prescription/complaints_parse', 'Opd_prescription::complaints_parse');
$routes->post('Opd_prescription/complaints_ai_draft', 'Opd_prescription::complaints_ai_draft');
$routes->post('Opd_prescription/clinical_autotype', 'Opd_prescription::clinical_autotype');

View File

@ -4111,6 +4111,195 @@ class Opd_prescription extends BaseController
]);
}
public function autotype_keyword_list()
{
if (! $this->canAccessPrescription()) {
return $this->response->setStatusCode(403)->setJSON(['rows' => []]);
}
$section = trim((string) $this->request->getGet('section'));
$scope = strtolower(trim((string) $this->request->getGet('scope')));
$q = trim((string) $this->request->getGet('q'));
$docId = max(0, (int) ($this->getCurrentUserId() ?? 0));
$canMaster = $this->canManageMasterKeyword();
if ($scope === '') {
$scope = 'all';
}
if (! $this->ensureAutotypeKeywordTable()) {
return $this->response->setJSON(['rows' => []]);
}
$builder = $this->db->table('opd_autotype_keyword_master')
->select('id,doc_id,section_key,keyword,updated_at')
->where('is_active', 1);
if ($this->isAutotypeKeywordSectionAllowed($section)) {
$builder->where('section_key', $section);
}
if ($scope === 'master') {
$builder->where('doc_id', 0);
} elseif ($scope === 'doctor') {
$builder->where('doc_id', $docId);
} else {
$builder->groupStart()
->where('doc_id', 0)
->orWhere('doc_id', $docId)
->groupEnd();
}
if ($q !== '') {
$builder->like('keyword', $q);
}
$rows = $builder
->orderBy('doc_id', 'DESC')
->orderBy('updated_at', 'DESC')
->orderBy('keyword', 'ASC')
->limit(200)
->get()
->getResultArray();
$out = [];
foreach ($rows as $row) {
$id = (int) ($row['id'] ?? 0);
$rowDocId = (int) ($row['doc_id'] ?? 0);
$isMaster = $rowDocId === 0;
$canEdit = $isMaster ? $canMaster : ($rowDocId === $docId);
$out[] = [
'id' => $id,
'doc_id' => $rowDocId,
'section_key' => (string) ($row['section_key'] ?? ''),
'keyword' => (string) ($row['keyword'] ?? ''),
'scope' => $isMaster ? 'master' : 'doctor',
'scope_label' => $isMaster ? 'Master' : 'My',
'updated_at' => (string) ($row['updated_at'] ?? ''),
'can_edit' => $canEdit ? 1 : 0,
'can_delete' => $canEdit ? 1 : 0,
];
}
return $this->response->setJSON(['rows' => $out]);
}
public function autotype_keyword_update()
{
if (! $this->request->isAJAX()) {
return $this->response->setStatusCode(400)->setJSON(['update' => 0, 'error_text' => 'Invalid request']);
}
if (! $this->canAccessPrescription()) {
return $this->response->setStatusCode(403)->setJSON(['update' => 0, 'error_text' => 'Access denied']);
}
$id = (int) $this->request->getPost('id');
$keyword = trim((string) $this->request->getPost('keyword'));
$keyword = preg_replace('/\s+/', ' ', $keyword) ?? $keyword;
if ($id <= 0 || mb_strlen($keyword) < 2) {
return $this->response->setJSON(['update' => 0, 'error_text' => 'Valid keyword is required']);
}
if (! $this->ensureAutotypeKeywordTable()) {
return $this->response->setJSON(['update' => 0, 'error_text' => 'Keyword storage unavailable']);
}
$table = $this->db->table('opd_autotype_keyword_master');
$current = $table->where('id', $id)->where('is_active', 1)->get(1)->getRowArray();
if (empty($current)) {
return $this->response->setJSON(['update' => 0, 'error_text' => 'Keyword not found']);
}
$docId = max(0, (int) ($this->getCurrentUserId() ?? 0));
$rowDocId = (int) ($current['doc_id'] ?? 0);
if ($rowDocId === 0) {
if (! $this->canManageMasterKeyword()) {
return $this->response->setJSON(['update' => 0, 'error_text' => 'No permission to edit master keyword']);
}
} elseif ($rowDocId !== $docId) {
return $this->response->setJSON(['update' => 0, 'error_text' => 'You can edit only your own keyword']);
}
$section = (string) ($current['section_key'] ?? '');
if (! $this->isAutotypeKeywordSectionAllowed($section)) {
return $this->response->setJSON(['update' => 0, 'error_text' => 'Unsupported section']);
}
$dup = $table
->where('doc_id', $rowDocId)
->where('section_key', $section)
->where('keyword', $keyword)
->where('id <>', $id)
->where('is_active', 1)
->get(1)
->getRowArray();
if (! empty($dup)) {
return $this->response->setJSON(['update' => 0, 'error_text' => 'Keyword already exists in this scope']);
}
$table->where('id', $id)->update([
'keyword' => $keyword,
'updated_at' => date('Y-m-d H:i:s'),
]);
return $this->response->setJSON([
'update' => 1,
'error_text' => 'Keyword updated',
'csrfName' => csrf_token(),
'csrfHash' => csrf_hash(),
]);
}
public function autotype_keyword_remove()
{
if (! $this->request->isAJAX()) {
return $this->response->setStatusCode(400)->setJSON(['update' => 0, 'error_text' => 'Invalid request']);
}
if (! $this->canAccessPrescription()) {
return $this->response->setStatusCode(403)->setJSON(['update' => 0, 'error_text' => 'Access denied']);
}
$id = (int) $this->request->getPost('id');
if ($id <= 0) {
return $this->response->setJSON(['update' => 0, 'error_text' => 'Keyword id is required']);
}
if (! $this->ensureAutotypeKeywordTable()) {
return $this->response->setJSON(['update' => 0, 'error_text' => 'Keyword storage unavailable']);
}
$table = $this->db->table('opd_autotype_keyword_master');
$current = $table->where('id', $id)->where('is_active', 1)->get(1)->getRowArray();
if (empty($current)) {
return $this->response->setJSON(['update' => 0, 'error_text' => 'Keyword not found']);
}
$docId = max(0, (int) ($this->getCurrentUserId() ?? 0));
$rowDocId = (int) ($current['doc_id'] ?? 0);
if ($rowDocId === 0) {
if (! $this->canManageMasterKeyword()) {
return $this->response->setJSON(['update' => 0, 'error_text' => 'No permission to remove master keyword']);
}
} elseif ($rowDocId !== $docId) {
return $this->response->setJSON(['update' => 0, 'error_text' => 'You can remove only your own keyword']);
}
$table->where('id', $id)->update([
'is_active' => 0,
'updated_at' => date('Y-m-d H:i:s'),
]);
return $this->response->setJSON([
'update' => 1,
'error_text' => 'Keyword removed',
'csrfName' => csrf_token(),
'csrfHash' => csrf_hash(),
]);
}
// Legacy-compatible wrappers used by older autocomplete clients.
public function get_complaints()
{
@ -6322,13 +6511,30 @@ class Opd_prescription extends BaseController
is_active TINYINT NOT NULL DEFAULT 1,
created_at DATETIME NULL,
updated_at DATETIME NULL,
UNIQUE KEY uniq_doc_section_keyword (doc_id, section_key, keyword),
INDEX idx_section_keyword (section_key, keyword),
UNIQUE KEY uniq_doc_section_keyword (doc_id, section_key, keyword(120)),
INDEX idx_section_keyword (section_key, keyword(120)),
INDEX idx_doc_active (doc_id, is_active)
)";
$this->db->query($sql);
} catch (\Throwable $e) {
return false;
// Fallback for stricter MySQL/InnoDB index length limits.
try {
$fallbackSql = "CREATE TABLE IF NOT EXISTS opd_autotype_keyword_master (
id INT AUTO_INCREMENT PRIMARY KEY,
doc_id INT NOT NULL DEFAULT 0,
section_key VARCHAR(60) NOT NULL,
keyword VARCHAR(180) NOT NULL,
is_active TINYINT NOT NULL DEFAULT 1,
created_at DATETIME NULL,
updated_at DATETIME NULL,
UNIQUE KEY uniq_doc_section_keyword (doc_id, section_key, keyword),
INDEX idx_section_keyword (section_key, keyword),
INDEX idx_doc_active (doc_id, is_active)
)";
$this->db->query($fallbackSql);
} catch (\Throwable $e2) {
return false;
}
}
return $this->db->tableExists('opd_autotype_keyword_master');
@ -6389,6 +6595,16 @@ class Opd_prescription extends BaseController
return $out;
}
private function canManageMasterKeyword(): bool
{
$user = auth()->user();
if (! $user || ! method_exists($user, 'can')) {
return false;
}
return $user->can('doctor_work.template_workspace.access') || $user->can('doctor_work.access');
}
private function isTemplateSectionAllowed(string $section): bool
{
return in_array($section, ['finding_examinations', 'diagnosis', 'provisional_diagnosis', 'prescriber_remarks', 'advice'], true);

View File

@ -467,6 +467,7 @@
<div class="d-flex align-items-center gap-2">
<span class="badge bg-light text-dark border">Doctor View</span>
<button type="button" class="btn btn-outline-primary btn-sm" id="btn_toggle_consult_view_settings">Customize Sections</button>
<button type="button" class="btn btn-outline-secondary btn-sm" id="btn_manage_autotype_keywords">Manage Keywords</button>
</div>
</div>
@ -949,6 +950,91 @@
</div>
</form>
<div class="modal fade" id="autotypeKeywordModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Manage Autotype Keywords</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="border rounded p-2 mb-3 bg-light">
<div class="row g-2 align-items-end">
<div class="col-md-3">
<label class="form-label mb-1">Add In Section</label>
<select class="form-select form-select-sm" id="autotype_keyword_add_section">
<option value="complaints">Complaints</option>
<option value="finding_examinations">Finding/Examinations</option>
<option value="provisional_diagnosis">Provisional Diagnosis</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label mb-1">Save Scope</label>
<select class="form-select form-select-sm" id="autotype_keyword_add_scope">
<option value="doctor">My</option>
<option value="master">Master</option>
</select>
</div>
<div class="col-md-5">
<label class="form-label mb-1">Keyword Text</label>
<input type="text" class="form-control form-control-sm" id="autotype_keyword_add_text" placeholder="Type full keyword/text...">
</div>
<div class="col-md-2 d-grid">
<button type="button" class="btn btn-primary btn-sm" id="btn_autotype_keyword_add">Add Keyword</button>
</div>
</div>
</div>
<div class="row g-2 mb-2">
<div class="col-md-4">
<label class="form-label mb-1">Section</label>
<select class="form-select form-select-sm" id="autotype_keyword_filter_section">
<option value="">All sections</option>
<option value="complaints">Complaints</option>
<option value="finding_examinations">Finding/Examinations</option>
<option value="provisional_diagnosis">Provisional Diagnosis</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label mb-1">Scope</label>
<select class="form-select form-select-sm" id="autotype_keyword_filter_scope">
<option value="all">All</option>
<option value="doctor">My Keywords</option>
<option value="master">Master Keywords</option>
</select>
</div>
<div class="col-md-5">
<label class="form-label mb-1">Search</label>
<div class="input-group input-group-sm">
<input type="text" class="form-control" id="autotype_keyword_filter_search" placeholder="Search keyword...">
<button type="button" class="btn btn-outline-secondary" id="btn_autotype_keyword_filter_reload">Reload</button>
</div>
</div>
</div>
<div class="small text-muted mb-2" id="autotype_keyword_manager_note">Showing latest keywords.</div>
<div class="table-responsive border rounded">
<table class="table table-sm table-striped mb-0">
<thead>
<tr>
<th width="140">Section</th>
<th>Keyword</th>
<th width="90">Scope</th>
<th width="110">Action</th>
</tr>
</thead>
<tbody id="autotype_keyword_manager_rows">
<tr><td colspan="4" class="text-muted px-2 py-2">No keyword found.</td></tr>
</tbody>
</table>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="rxTemplateModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-md">
<div class="modal-content">
@ -1200,6 +1286,12 @@
var autotypeSuggestCacheMaxEntries = 240;
var autotypeSuggestCacheLocalKey = 'opd_autotype_suggest_cache_v1';
var autotypeSuggestCacheLocalReady = false;
var autotypeKeywordModalLoading = false;
var autotypeKeywordSectionLabels = {
complaints: 'Complaints',
finding_examinations: 'Finding/Examinations',
provisional_diagnosis: 'Provisional Diagnosis'
};
var medInputTimer = null;
var crgcMedInputTimer = null;
var medicineDoseMasterCache = { dose: [], when: [], freq: [], where: [] };
@ -1586,6 +1678,72 @@
return (val || '').toString().split(/,\s*/);
}
function getTextareaCaretCoordinates(el, caretPos) {
var doc = document;
var div = doc.createElement('div');
var style = window.getComputedStyle(el);
var propList = [
'boxSizing', 'width', 'height', 'overflowX', 'overflowY',
'borderTopWidth', 'borderRightWidth', 'borderBottomWidth', 'borderLeftWidth',
'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft',
'fontStyle', 'fontVariant', 'fontWeight', 'fontStretch', 'fontSize',
'fontSizeAdjust', 'lineHeight', 'fontFamily', 'textAlign', 'textTransform',
'textIndent', 'textDecoration', 'letterSpacing', 'wordSpacing'
];
div.style.position = 'absolute';
div.style.visibility = 'hidden';
div.style.whiteSpace = 'pre-wrap';
div.style.wordWrap = 'break-word';
div.style.left = '-9999px';
propList.forEach(function(prop) {
div.style[prop] = style[prop];
});
div.textContent = el.value.substring(0, caretPos);
var span = doc.createElement('span');
span.textContent = el.value.substring(caretPos) || '.';
div.appendChild(span);
doc.body.appendChild(div);
var top = span.offsetTop - el.scrollTop;
var left = span.offsetLeft - el.scrollLeft;
doc.body.removeChild(div);
return { top: top, left: left };
}
function positionAutocompleteNearCaret($field) {
var ac = $field.autocomplete('instance');
if (!ac || !ac.menu || !ac.menu.element || !$field.length) {
return;
}
var el = $field.get(0);
if (!el || el.tagName !== 'TEXTAREA') {
return;
}
var offset = $field.offset();
if (!offset) {
return;
}
var caretPos = Number(el.selectionStart || 0);
var caret = getTextareaCaretCoordinates(el, caretPos);
var lineHeight = parseFloat(window.getComputedStyle(el).lineHeight || '20');
var menuLeft = Math.max(8, offset.left + caret.left);
var menuTop = Math.max(8, offset.top + caret.top + lineHeight + 6);
ac.menu.element.css({
position: 'absolute',
left: menuLeft + 'px',
top: menuTop + 'px',
minWidth: Math.max(260, Math.round($field.innerWidth() * 0.65)) + 'px',
zIndex: 2000
});
}
function extractLastCommaTerm(term) {
return splitCommaTerms(term).pop() || '';
}
@ -1668,11 +1826,14 @@
return token;
}
function saveAutotypeKeyword(section, keyword, done) {
var scope = 'doctor';
var useMaster = window.confirm('Save keyword to Master list for all doctors?\nChoose Cancel to save only for current doctor.');
if (useMaster) {
scope = 'master';
function saveAutotypeKeyword(section, keyword, done, scopeOverride) {
var scope = (scopeOverride || '').toString().trim().toLowerCase();
if (scope !== 'doctor' && scope !== 'master') {
scope = 'doctor';
var useMaster = window.confirm('Save keyword to Master list for all doctors?\nChoose Cancel to save only for current doctor.');
if (useMaster) {
scope = 'master';
}
}
apiPost('<?= base_url('Opd_prescription/autotype_keyword_save') ?>', {
@ -1690,6 +1851,137 @@
});
}
function autotypeKeywordApiList(params, done) {
$.getJSON('<?= base_url('Opd_prescription/autotype_keyword_list') ?>', params || {}, function(data) {
if (typeof done === 'function') {
done(data || {});
}
}).fail(function() {
if (typeof done === 'function') {
done({ rows: [], error_text: 'Unable to fetch keyword list.' });
}
});
}
function autotypeKeywordApiUpdate(id, keyword, done) {
apiPost('<?= base_url('Opd_prescription/autotype_keyword_update') ?>', {
id: id,
keyword: keyword
}, function(data) {
if (typeof done === 'function') {
done(data || {});
}
});
}
function autotypeKeywordApiRemove(id, done) {
apiPost('<?= base_url('Opd_prescription/autotype_keyword_remove') ?>', {
id: id
}, function(data) {
if (typeof done === 'function') {
done(data || {});
}
});
}
function renderAutotypeKeywordManagerRows(rows) {
var $tbody = $('#autotype_keyword_manager_rows');
$tbody.empty();
var list = Array.isArray(rows) ? rows : [];
if (!list.length) {
$tbody.append('<tr><td colspan="4" class="text-muted px-2 py-2">No keyword found.</td></tr>');
return;
}
list.forEach(function(row) {
var id = Number(row.id || 0);
var keyword = (row.keyword || '').toString();
var section = (row.section_key || '').toString();
var scopeLabel = (row.scope_label || row.scope || '').toString();
var canEdit = Number(row.can_edit || 0) === 1;
var canDelete = Number(row.can_delete || 0) === 1;
var $tr = $('<tr></tr>');
$('<td></td>').text(autotypeKeywordSectionLabels[section] || section || '-').appendTo($tr);
$('<td></td>').text(keyword).appendTo($tr);
$('<td></td>').text(scopeLabel).appendTo($tr);
var $action = $('<td></td>');
var $editBtn = $('<button type="button" class="btn btn-outline-primary btn-sm me-1">Edit</button>');
var $delBtn = $('<button type="button" class="btn btn-outline-danger btn-sm">Delete</button>');
$editBtn.attr('data-id', id).attr('data-keyword', keyword).addClass('btn-autotype-keyword-edit');
$delBtn.attr('data-id', id).attr('data-keyword', keyword).addClass('btn-autotype-keyword-delete');
if (!canEdit) {
$editBtn.prop('disabled', true);
}
if (!canDelete) {
$delBtn.prop('disabled', true);
}
$action.append($editBtn).append($delBtn).appendTo($tr);
$tbody.append($tr);
});
}
function getAutotypeKeywordFilters() {
return {
section: ($('#autotype_keyword_filter_section').val() || '').toString(),
scope: ($('#autotype_keyword_filter_scope').val() || 'all').toString(),
q: ($('#autotype_keyword_filter_search').val() || '').toString().trim()
};
}
function submitModalAutotypeKeyword() {
var section = ($('#autotype_keyword_add_section').val() || '').toString();
var scope = ($('#autotype_keyword_add_scope').val() || 'doctor').toString().toLowerCase();
var keyword = ($('#autotype_keyword_add_text').val() || '').toString().trim();
if (!section) {
$('.jsError').removeClass('text-success text-muted').addClass('text-danger').text('Please select section for keyword.');
return;
}
if (!keyword || keyword.length < 2) {
$('.jsError').removeClass('text-success text-muted').addClass('text-danger').text('Please type at least 2 characters in keyword text.');
return;
}
saveAutotypeKeyword(section, keyword, function(data) {
if (Number(data.update || 0) === 1) {
clearAutotypeSuggestionCaches();
$('#autotype_keyword_add_text').val('');
$('.jsError').removeClass('text-danger text-muted').addClass('text-success').text((data.error_text || 'Keyword saved') + ': ' + keyword);
loadAutotypeKeywordManagerRows();
} else {
$('.jsError').removeClass('text-success text-muted').addClass('text-danger').text(data.error_text || 'Unable to save keyword.');
}
}, scope);
}
function loadAutotypeKeywordManagerRows() {
if (autotypeKeywordModalLoading) {
return;
}
autotypeKeywordModalLoading = true;
$('#autotype_keyword_manager_note').text('Loading keywords...');
autotypeKeywordApiList(getAutotypeKeywordFilters(), function(data) {
autotypeKeywordModalLoading = false;
var rows = (data && data.rows) ? data.rows : [];
renderAutotypeKeywordManagerRows(rows);
$('#autotype_keyword_manager_note').text('Showing ' + rows.length + ' keyword(s).');
if (data && data.error_text) {
$('.jsError').removeClass('text-success text-muted').addClass('text-danger').text(data.error_text);
}
});
}
function clearAutotypeSuggestionCaches() {
autotypeSuggestCache = {};
persistAutotypeSuggestCache();
}
function bindLegacyCommaAutocomplete(selector, endpointUrl, extractValue) {
var $field = $(selector);
if (!$field.length || !($.ui && $.ui.autocomplete)) {
@ -1744,6 +2036,9 @@
focus: function() {
return false;
},
open: function() {
positionAutocompleteNearCaret($(this));
},
select: function(event, ui) {
var terms = splitCommaTerms(this.value);
terms.pop();
@ -1756,6 +2051,14 @@
return false;
}
});
$field.on('keyup click focus', function() {
var $self = $(this);
var ac = $self.autocomplete('instance');
if (ac && ac.menu && ac.menu.element && ac.menu.element.is(':visible')) {
positionAutocompleteNearCaret($self);
}
});
}
function getCsrfPair() {
@ -5963,12 +6266,103 @@
saveAutotypeKeyword(section, keyword, function(data) {
if (Number(data.update || 0) === 1) {
$('.jsError').removeClass('text-danger text-muted').addClass('text-success').text((data.error_text || 'Keyword saved') + ': ' + keyword);
loadAutotypeKeywordManagerRows();
} else {
$('.jsError').removeClass('text-success text-muted').addClass('text-danger').text(data.error_text || 'Unable to save keyword.');
}
});
});
$('#btn_manage_autotype_keywords').on('click', function() {
var firstSection = ($('#autotype_keyword_filter_section').val() || 'complaints').toString();
if (!firstSection) {
firstSection = 'complaints';
}
$('#autotype_keyword_add_section').val(firstSection);
loadAutotypeKeywordManagerRows();
if (window.bootstrap && window.bootstrap.Modal) {
window.bootstrap.Modal.getOrCreateInstance(document.getElementById('autotypeKeywordModal')).show();
} else {
$('#autotypeKeywordModal').show();
}
});
$('#btn_autotype_keyword_add').on('click', function() {
submitModalAutotypeKeyword();
});
$('#autotype_keyword_add_text').on('keydown', function(e) {
if ((e.key || '').toLowerCase() === 'enter') {
e.preventDefault();
submitModalAutotypeKeyword();
}
});
$('#btn_autotype_keyword_filter_reload').on('click', function() {
loadAutotypeKeywordManagerRows();
});
$('#autotype_keyword_filter_section, #autotype_keyword_filter_scope').on('change', function() {
loadAutotypeKeywordManagerRows();
});
$('#autotype_keyword_filter_search').on('keydown', function(e) {
if ((e.key || '').toLowerCase() === 'enter') {
e.preventDefault();
loadAutotypeKeywordManagerRows();
}
});
$(document).on('click', '.btn-autotype-keyword-edit', function() {
var id = Number($(this).data('id') || 0);
var oldKeyword = ($(this).data('keyword') || '').toString();
if (id <= 0) {
return;
}
var next = window.prompt('Edit keyword', oldKeyword);
if (next === null) {
return;
}
next = next.toString().trim();
if (next.length < 2) {
$('.jsError').removeClass('text-success text-muted').addClass('text-danger').text('Keyword must be at least 2 characters.');
return;
}
autotypeKeywordApiUpdate(id, next, function(data) {
if (Number(data.update || 0) === 1) {
clearAutotypeSuggestionCaches();
$('.jsError').removeClass('text-danger text-muted').addClass('text-success').text(data.error_text || 'Keyword updated.');
loadAutotypeKeywordManagerRows();
} else {
$('.jsError').removeClass('text-success text-muted').addClass('text-danger').text(data.error_text || 'Unable to update keyword.');
}
});
});
$(document).on('click', '.btn-autotype-keyword-delete', function() {
var id = Number($(this).data('id') || 0);
var keyword = ($(this).data('keyword') || '').toString();
if (id <= 0) {
return;
}
if (!window.confirm('Delete keyword: ' + keyword + ' ?')) {
return;
}
autotypeKeywordApiRemove(id, function(data) {
if (Number(data.update || 0) === 1) {
clearAutotypeSuggestionCaches();
$('.jsError').removeClass('text-danger text-muted').addClass('text-success').text(data.error_text || 'Keyword removed.');
loadAutotypeKeywordManagerRows();
} else {
$('.jsError').removeClass('text-success text-muted').addClass('text-danger').text(data.error_text || 'Unable to remove keyword.');
}
});
});
initConsultSectionDesigner();
bindLegacyCommaAutocomplete('#complaints', '<?= base_url('Opd_prescription/complaints_search') ?>', function(row) {