Add cached consult autotype suggestions and doctor/master keyword save
This commit is contained in:
parent
b1d329e462
commit
418fb3e5e7
|
|
@ -534,6 +534,7 @@ $routes->get('Opd_prescription/finding_exam_search', 'Opd_prescription::finding_
|
||||||
$routes->get('Opd_prescription/get_complaints', 'Opd_prescription::get_complaints');
|
$routes->get('Opd_prescription/get_complaints', 'Opd_prescription::get_complaints');
|
||||||
$routes->get('Opd_prescription/get_disease', 'Opd_prescription::get_disease');
|
$routes->get('Opd_prescription/get_disease', 'Opd_prescription::get_disease');
|
||||||
$routes->get('Opd_prescription/get_finding_exam', 'Opd_prescription::get_finding_exam');
|
$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->post('Opd_prescription/complaints_parse', 'Opd_prescription::complaints_parse');
|
$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/complaints_ai_draft', 'Opd_prescription::complaints_ai_draft');
|
||||||
$routes->post('Opd_prescription/clinical_autotype', 'Opd_prescription::clinical_autotype');
|
$routes->post('Opd_prescription/clinical_autotype', 'Opd_prescription::clinical_autotype');
|
||||||
|
|
|
||||||
|
|
@ -3862,25 +3862,65 @@ class Opd_prescription extends BaseController
|
||||||
public function complaints_search()
|
public function complaints_search()
|
||||||
{
|
{
|
||||||
$q = trim((string) $this->request->getGet('q'));
|
$q = trim((string) $this->request->getGet('q'));
|
||||||
if ($q === '' || ! $this->db->tableExists('complaints_master')) {
|
if ($q === '') {
|
||||||
return $this->response->setJSON(['rows' => []]);
|
return $this->response->setJSON(['rows' => []]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$rows = $this->db->table('complaints_master')
|
$rows = [];
|
||||||
->select('Code as code, Name as name, name_hinglish, show_in_short, ai_hint')
|
if ($this->db->tableExists('complaints_master')) {
|
||||||
->where('is_active', 1)
|
$rows = $this->db->table('complaints_master')
|
||||||
->groupStart()
|
->select('Code as code, Name as name, name_hinglish, show_in_short, ai_hint')
|
||||||
->like('Name', $q)
|
->where('is_active', 1)
|
||||||
->orLike('name_hinglish', $q)
|
->groupStart()
|
||||||
->orLike('keywords', $q)
|
->like('Name', $q)
|
||||||
->groupEnd()
|
->orLike('name_hinglish', $q)
|
||||||
->orderBy('show_in_short', 'DESC')
|
->orLike('keywords', $q)
|
||||||
->orderBy('Name', 'ASC')
|
->groupEnd()
|
||||||
->limit(20)
|
->orderBy('show_in_short', 'DESC')
|
||||||
->get()
|
->orderBy('Name', 'ASC')
|
||||||
->getResultArray();
|
->limit(20)
|
||||||
|
->get()
|
||||||
|
->getResultArray();
|
||||||
|
}
|
||||||
|
|
||||||
return $this->response->setJSON(['rows' => $rows]);
|
$keywordRows = $this->fetchAutotypeKeywords(
|
||||||
|
'complaints',
|
||||||
|
$q,
|
||||||
|
max(0, (int) ($this->getCurrentUserId() ?? 0)),
|
||||||
|
20
|
||||||
|
);
|
||||||
|
|
||||||
|
$out = [];
|
||||||
|
$seen = [];
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$name = trim((string) ($row['name'] ?? ''));
|
||||||
|
if ($name === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$k = strtoupper($name);
|
||||||
|
if (isset($seen[$k])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$seen[$k] = true;
|
||||||
|
$out[] = $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($keywordRows as $keyword) {
|
||||||
|
$k = strtoupper($keyword);
|
||||||
|
if ($keyword === '' || isset($seen[$k])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$seen[$k] = true;
|
||||||
|
$out[] = [
|
||||||
|
'code' => '',
|
||||||
|
'name' => $keyword,
|
||||||
|
'name_hinglish' => '',
|
||||||
|
'show_in_short' => 0,
|
||||||
|
'ai_hint' => '',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->response->setJSON(['rows' => array_slice($out, 0, 20)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function provisional_diagnosis_search()
|
public function provisional_diagnosis_search()
|
||||||
|
|
@ -3908,7 +3948,37 @@ class Opd_prescription extends BaseController
|
||||||
->get()
|
->get()
|
||||||
->getResultArray();
|
->getResultArray();
|
||||||
|
|
||||||
return $this->response->setJSON(['rows' => $rows]);
|
$keywordRows = $this->fetchAutotypeKeywords(
|
||||||
|
'provisional_diagnosis',
|
||||||
|
$q,
|
||||||
|
max(0, (int) ($this->getCurrentUserId() ?? 0)),
|
||||||
|
20
|
||||||
|
);
|
||||||
|
|
||||||
|
$out = [];
|
||||||
|
$seen = [];
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$name = trim((string) ($row['name'] ?? ''));
|
||||||
|
if ($name === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$k = strtoupper($name);
|
||||||
|
if (isset($seen[$k])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$seen[$k] = true;
|
||||||
|
$out[] = ['name' => $name];
|
||||||
|
}
|
||||||
|
foreach ($keywordRows as $keyword) {
|
||||||
|
$k = strtoupper($keyword);
|
||||||
|
if ($keyword === '' || isset($seen[$k])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$seen[$k] = true;
|
||||||
|
$out[] = ['name' => $keyword];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->response->setJSON(['rows' => array_slice($out, 0, 20)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function finding_exam_search()
|
public function finding_exam_search()
|
||||||
|
|
@ -3933,7 +4003,112 @@ class Opd_prescription extends BaseController
|
||||||
->get()
|
->get()
|
||||||
->getResultArray();
|
->getResultArray();
|
||||||
|
|
||||||
return $this->response->setJSON(['rows' => $rows]);
|
$keywordRows = $this->fetchAutotypeKeywords(
|
||||||
|
'finding_examinations',
|
||||||
|
$q,
|
||||||
|
max(0, (int) ($this->getCurrentUserId() ?? 0)),
|
||||||
|
20
|
||||||
|
);
|
||||||
|
|
||||||
|
$out = [];
|
||||||
|
$seen = [];
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$text = trim((string) ($row['finding_examinations'] ?? ''));
|
||||||
|
if ($text === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$k = strtoupper($text);
|
||||||
|
if (isset($seen[$k])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$seen[$k] = true;
|
||||||
|
$out[] = ['finding_examinations' => $text];
|
||||||
|
}
|
||||||
|
foreach ($keywordRows as $keyword) {
|
||||||
|
$k = strtoupper($keyword);
|
||||||
|
if ($keyword === '' || isset($seen[$k])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$seen[$k] = true;
|
||||||
|
$out[] = ['finding_examinations' => $keyword];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->response->setJSON(['rows' => array_slice($out, 0, 20)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function autotype_keyword_save()
|
||||||
|
{
|
||||||
|
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']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$section = trim((string) $this->request->getPost('section'));
|
||||||
|
$keyword = trim((string) $this->request->getPost('keyword'));
|
||||||
|
$scope = strtolower(trim((string) $this->request->getPost('scope')));
|
||||||
|
if ($scope === '') {
|
||||||
|
$scope = 'doctor';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $this->isAutotypeKeywordSectionAllowed($section)) {
|
||||||
|
return $this->response->setJSON(['update' => 0, 'error_text' => 'Unsupported section']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$keyword = preg_replace('/\s+/', ' ', $keyword) ?? $keyword;
|
||||||
|
if (mb_strlen($keyword) < 2) {
|
||||||
|
return $this->response->setJSON(['update' => 0, 'error_text' => 'Keyword must be at least 2 characters']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $this->ensureAutotypeKeywordTable()) {
|
||||||
|
return $this->response->setJSON(['update' => 0, 'error_text' => 'Keyword storage unavailable']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$docId = max(0, (int) ($this->getCurrentUserId() ?? 0));
|
||||||
|
if ($scope === 'master') {
|
||||||
|
$user = auth()->user();
|
||||||
|
$canMaster = $user && method_exists($user, 'can') && ($user->can('doctor_work.template_workspace.access') || $user->can('doctor_work.access'));
|
||||||
|
if (! $canMaster) {
|
||||||
|
return $this->response->setJSON(['update' => 0, 'error_text' => 'No permission to save master keyword']);
|
||||||
|
}
|
||||||
|
$docId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$table = $this->db->table('opd_autotype_keyword_master');
|
||||||
|
$existing = $table
|
||||||
|
->where('doc_id', $docId)
|
||||||
|
->where('section_key', $section)
|
||||||
|
->where('keyword', $keyword)
|
||||||
|
->get(1)
|
||||||
|
->getRowArray();
|
||||||
|
|
||||||
|
if (! empty($existing)) {
|
||||||
|
$table->where('id', (int) ($existing['id'] ?? 0))->update([
|
||||||
|
'is_active' => 1,
|
||||||
|
'updated_at' => date('Y-m-d H:i:s'),
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$table->insert([
|
||||||
|
'doc_id' => $docId,
|
||||||
|
'section_key' => $section,
|
||||||
|
'keyword' => $keyword,
|
||||||
|
'is_active' => 1,
|
||||||
|
'created_at' => date('Y-m-d H:i:s'),
|
||||||
|
'updated_at' => date('Y-m-d H:i:s'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->response->setJSON([
|
||||||
|
'update' => 1,
|
||||||
|
'error_text' => $docId === 0 ? 'Keyword saved in master list' : 'Keyword saved for current doctor',
|
||||||
|
'keyword' => $keyword,
|
||||||
|
'section' => $section,
|
||||||
|
'scope' => $docId === 0 ? 'master' : 'doctor',
|
||||||
|
'csrfName' => csrf_token(),
|
||||||
|
'csrfHash' => csrf_hash(),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Legacy-compatible wrappers used by older autocomplete clients.
|
// Legacy-compatible wrappers used by older autocomplete clients.
|
||||||
|
|
@ -6132,6 +6307,88 @@ class Opd_prescription extends BaseController
|
||||||
return $this->db->tableExists('opd_clinical_template_usage');
|
return $this->db->tableExists('opd_clinical_template_usage');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function ensureAutotypeKeywordTable(): bool
|
||||||
|
{
|
||||||
|
if ($this->db->tableExists('opd_autotype_keyword_master')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$sql = "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(80) NOT NULL,
|
||||||
|
keyword VARCHAR(255) 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($sql);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->db->tableExists('opd_autotype_keyword_master');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isAutotypeKeywordSectionAllowed(string $section): bool
|
||||||
|
{
|
||||||
|
return in_array($section, ['complaints', 'provisional_diagnosis', 'finding_examinations'], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<int, string>
|
||||||
|
*/
|
||||||
|
private function fetchAutotypeKeywords(string $section, string $q, int $docId, int $limit = 20): array
|
||||||
|
{
|
||||||
|
if ($q === '' || ! $this->isAutotypeKeywordSectionAllowed($section) || ! $this->ensureAutotypeKeywordTable()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$builder = $this->db->table('opd_autotype_keyword_master')
|
||||||
|
->select('keyword,doc_id')
|
||||||
|
->where('is_active', 1)
|
||||||
|
->where('section_key', $section)
|
||||||
|
->like('keyword', $q)
|
||||||
|
->orderBy('doc_id', 'DESC')
|
||||||
|
->orderBy('updated_at', 'DESC')
|
||||||
|
->limit(max(1, $limit));
|
||||||
|
|
||||||
|
if ($docId > 0) {
|
||||||
|
$builder->groupStart()
|
||||||
|
->where('doc_id', 0)
|
||||||
|
->orWhere('doc_id', $docId)
|
||||||
|
->groupEnd();
|
||||||
|
} else {
|
||||||
|
$builder->where('doc_id', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows = $builder->get()->getResultArray();
|
||||||
|
if (empty($rows)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$out = [];
|
||||||
|
$seen = [];
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$keyword = trim((string) ($row['keyword'] ?? ''));
|
||||||
|
if ($keyword === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$k = strtoupper($keyword);
|
||||||
|
if (isset($seen[$k])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$seen[$k] = true;
|
||||||
|
$out[] = $keyword;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
private function isTemplateSectionAllowed(string $section): bool
|
private function isTemplateSectionAllowed(string $section): bool
|
||||||
{
|
{
|
||||||
return in_array($section, ['finding_examinations', 'diagnosis', 'provisional_diagnosis', 'prescriber_remarks', 'advice'], true);
|
return in_array($section, ['finding_examinations', 'diagnosis', 'provisional_diagnosis', 'prescriber_remarks', 'advice'], true);
|
||||||
|
|
|
||||||
|
|
@ -656,6 +656,7 @@
|
||||||
<label class="form-label">Complaints
|
<label class="form-label">Complaints
|
||||||
<button type="button" class="btn btn-outline-primary btn-sm rx-ai-btn btn-ai-rewrite btn-complaints-rewrite" data-target="complaints" data-mode="hinglish_to_english" title="Convert Hinglish text to English">↔ Hinglish → English</button>
|
<button type="button" class="btn btn-outline-primary btn-sm rx-ai-btn btn-ai-rewrite btn-complaints-rewrite" data-target="complaints" data-mode="hinglish_to_english" title="Convert Hinglish text to English">↔ Hinglish → English</button>
|
||||||
<button type="button" class="btn btn-outline-success btn-sm rx-ai-btn" id="btn_complaints_mic" title="Speech to text for complaints">Mic</button>
|
<button type="button" class="btn btn-outline-success btn-sm rx-ai-btn" id="btn_complaints_mic" title="Speech to text for complaints">Mic</button>
|
||||||
|
<button type="button" class="btn btn-outline-secondary btn-sm rx-ai-btn btn-save-autotype-keyword" data-section="complaints" data-target="complaints" title="Save keyword for autocomplete">Save keyword</button>
|
||||||
</label>
|
</label>
|
||||||
<textarea class="form-control rx-field" id="complaints" rows="4" maxlength="4000"><?= esc($opd_prescription[0]->complaints ?? '') ?></textarea>
|
<textarea class="form-control rx-field" id="complaints" rows="4" maxlength="4000"><?= esc($opd_prescription[0]->complaints ?? '') ?></textarea>
|
||||||
<div id="complaints_interim_preview" style="display:none;font-size:.8rem;color:#6c757d;padding:2px 4px;font-style:italic;"></div>
|
<div id="complaints_interim_preview" style="display:none;font-size:.8rem;color:#6c757d;padding:2px 4px;font-style:italic;"></div>
|
||||||
|
|
@ -670,6 +671,7 @@
|
||||||
<button type="button" class="btn btn-outline-secondary btn-sm btn-field-past" data-section="finding_examinations" data-target="finding_examinations">Past Data</button>
|
<button type="button" class="btn btn-outline-secondary btn-sm btn-field-past" data-section="finding_examinations" data-target="finding_examinations">Past Data</button>
|
||||||
<button type="button" class="btn btn-outline-secondary btn-sm btn-template-load" data-section="finding_examinations" data-target="finding_examinations">Load Template</button>
|
<button type="button" class="btn btn-outline-secondary btn-sm btn-template-load" data-section="finding_examinations" data-target="finding_examinations">Load Template</button>
|
||||||
<button type="button" class="btn btn-outline-secondary btn-sm btn-template-save" data-section="finding_examinations" data-target="finding_examinations">Save as Template</button>
|
<button type="button" class="btn btn-outline-secondary btn-sm btn-template-save" data-section="finding_examinations" data-target="finding_examinations">Save as Template</button>
|
||||||
|
<button type="button" class="btn btn-outline-secondary btn-sm btn-save-autotype-keyword" data-section="finding_examinations" data-target="finding_examinations">Save keyword</button>
|
||||||
<button type="button" class="btn btn-outline-secondary btn-sm btn-field-clear" data-target="finding_examinations">Clear</button>
|
<button type="button" class="btn btn-outline-secondary btn-sm btn-field-clear" data-target="finding_examinations">Clear</button>
|
||||||
</div>
|
</div>
|
||||||
<textarea class="form-control rx-field" id="finding_examinations" rows="4" maxlength="4000"><?= esc($opd_prescription[0]->Finding_Examinations ?? '') ?></textarea>
|
<textarea class="form-control rx-field" id="finding_examinations" rows="4" maxlength="4000"><?= esc($opd_prescription[0]->Finding_Examinations ?? '') ?></textarea>
|
||||||
|
|
@ -698,6 +700,7 @@
|
||||||
<button type="button" class="btn btn-outline-secondary btn-sm btn-field-past" data-section="provisional_diagnosis" data-target="provisional_diagnosis">Past Data</button>
|
<button type="button" class="btn btn-outline-secondary btn-sm btn-field-past" data-section="provisional_diagnosis" data-target="provisional_diagnosis">Past Data</button>
|
||||||
<button type="button" class="btn btn-outline-secondary btn-sm btn-template-load" data-section="provisional_diagnosis" data-target="provisional_diagnosis">Load Template</button>
|
<button type="button" class="btn btn-outline-secondary btn-sm btn-template-load" data-section="provisional_diagnosis" data-target="provisional_diagnosis">Load Template</button>
|
||||||
<button type="button" class="btn btn-outline-secondary btn-sm btn-template-save" data-section="provisional_diagnosis" data-target="provisional_diagnosis">Save as Template</button>
|
<button type="button" class="btn btn-outline-secondary btn-sm btn-template-save" data-section="provisional_diagnosis" data-target="provisional_diagnosis">Save as Template</button>
|
||||||
|
<button type="button" class="btn btn-outline-secondary btn-sm btn-save-autotype-keyword" data-section="provisional_diagnosis" data-target="provisional_diagnosis">Save keyword</button>
|
||||||
<button type="button" class="btn btn-outline-secondary btn-sm btn-field-clear" data-target="provisional_diagnosis">Clear</button>
|
<button type="button" class="btn btn-outline-secondary btn-sm btn-field-clear" data-target="provisional_diagnosis">Clear</button>
|
||||||
</div>
|
</div>
|
||||||
<textarea class="form-control rx-field" id="provisional_diagnosis" rows="4" maxlength="4000"><?= esc($opd_prescription[0]->Provisional_diagnosis ?? '') ?></textarea>
|
<textarea class="form-control rx-field" id="provisional_diagnosis" rows="4" maxlength="4000"><?= esc($opd_prescription[0]->Provisional_diagnosis ?? '') ?></textarea>
|
||||||
|
|
@ -1192,6 +1195,11 @@
|
||||||
var medicineSearchLocalDbKey = 'opd_medicine_search_cache_v1';
|
var medicineSearchLocalDbKey = 'opd_medicine_search_cache_v1';
|
||||||
var medicineSearchCacheMaxEntries = 250;
|
var medicineSearchCacheMaxEntries = 250;
|
||||||
var medicineSearchLocalDbReady = false;
|
var medicineSearchLocalDbReady = false;
|
||||||
|
var autotypeSuggestCache = {};
|
||||||
|
var autotypeSuggestCacheTtlMs = 300000;
|
||||||
|
var autotypeSuggestCacheMaxEntries = 240;
|
||||||
|
var autotypeSuggestCacheLocalKey = 'opd_autotype_suggest_cache_v1';
|
||||||
|
var autotypeSuggestCacheLocalReady = false;
|
||||||
var medInputTimer = null;
|
var medInputTimer = null;
|
||||||
var crgcMedInputTimer = null;
|
var crgcMedInputTimer = null;
|
||||||
var medicineDoseMasterCache = { dose: [], when: [], freq: [], where: [] };
|
var medicineDoseMasterCache = { dose: [], when: [], freq: [], where: [] };
|
||||||
|
|
@ -1201,6 +1209,7 @@
|
||||||
};
|
};
|
||||||
var patientId = <?= (int) ($patient_master[0]->id ?? 0) ?>;
|
var patientId = <?= (int) ($patient_master[0]->id ?? 0) ?>;
|
||||||
var doctorViewId = <?= (int) ($doctorId ?? 0) ?>;
|
var doctorViewId = <?= (int) ($doctorId ?? 0) ?>;
|
||||||
|
autotypeSuggestCacheLocalKey = autotypeSuggestCacheLocalKey + '_' + doctorViewId;
|
||||||
var consultSectionPreferenceKey = 'opd_consult_section_pref_' + doctorViewId;
|
var consultSectionPreferenceKey = 'opd_consult_section_pref_' + doctorViewId;
|
||||||
var consultSectionMeta = [
|
var consultSectionMeta = [
|
||||||
{ key: 'pain_scale', selector: '#rx_sec_pain_scale', title: 'Pain Measurement Scale' },
|
{ key: 'pain_scale', selector: '#rx_sec_pain_scale', title: 'Pain Measurement Scale' },
|
||||||
|
|
@ -1581,6 +1590,106 @@
|
||||||
return splitCommaTerms(term).pop() || '';
|
return splitCommaTerms(term).pop() || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getAutotypeSuggestCacheKey(endpointUrl, term) {
|
||||||
|
return (endpointUrl || '') + '|' + (term || '').toString().trim().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function pruneAutotypeSuggestCache(nowMs) {
|
||||||
|
var now = nowMs || Date.now();
|
||||||
|
var keys = Object.keys(autotypeSuggestCache || {});
|
||||||
|
if (!keys.length) {
|
||||||
|
autotypeSuggestCache = {};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var rows = [];
|
||||||
|
keys.forEach(function(k) {
|
||||||
|
var item = autotypeSuggestCache[k] || {};
|
||||||
|
var ts = Number(item.ts || 0);
|
||||||
|
if (!ts || (now - ts) > autotypeSuggestCacheTtlMs) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rows.push({ key: k, ts: ts, rows: Array.isArray(item.rows) ? item.rows : [] });
|
||||||
|
});
|
||||||
|
|
||||||
|
rows.sort(function(a, b) { return b.ts - a.ts; });
|
||||||
|
if (rows.length > autotypeSuggestCacheMaxEntries) {
|
||||||
|
rows = rows.slice(0, autotypeSuggestCacheMaxEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
var next = {};
|
||||||
|
rows.forEach(function(item) {
|
||||||
|
next[item.key] = { ts: item.ts, rows: item.rows };
|
||||||
|
});
|
||||||
|
autotypeSuggestCache = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureAutotypeSuggestCacheLoaded() {
|
||||||
|
if (autotypeSuggestCacheLocalReady) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
autotypeSuggestCacheLocalReady = true;
|
||||||
|
try {
|
||||||
|
var raw = localStorage.getItem(autotypeSuggestCacheLocalKey);
|
||||||
|
if (!raw) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var parsed = JSON.parse(raw);
|
||||||
|
var entries = parsed && parsed.entries ? parsed.entries : {};
|
||||||
|
if (entries && typeof entries === 'object') {
|
||||||
|
autotypeSuggestCache = entries;
|
||||||
|
pruneAutotypeSuggestCache(Date.now());
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
autotypeSuggestCache = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function persistAutotypeSuggestCache() {
|
||||||
|
try {
|
||||||
|
pruneAutotypeSuggestCache(Date.now());
|
||||||
|
localStorage.setItem(autotypeSuggestCacheLocalKey, JSON.stringify({
|
||||||
|
saved_at: Date.now(),
|
||||||
|
entries: autotypeSuggestCache
|
||||||
|
}));
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore localStorage write failures.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLastKeywordFromField(targetId) {
|
||||||
|
var text = ($('#' + targetId).val() || '').toString();
|
||||||
|
var token = extractLastCommaTerm(text).toString().trim();
|
||||||
|
if (!token) {
|
||||||
|
var parts = text.split(/[;,\n\r]+/);
|
||||||
|
token = (parts.pop() || '').toString().trim();
|
||||||
|
}
|
||||||
|
token = token.replace(/^[-*\s]+/, '').trim();
|
||||||
|
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';
|
||||||
|
}
|
||||||
|
|
||||||
|
apiPost('<?= base_url('Opd_prescription/autotype_keyword_save') ?>', {
|
||||||
|
section: section,
|
||||||
|
keyword: keyword,
|
||||||
|
scope: scope
|
||||||
|
}, function(data) {
|
||||||
|
if (data && Number(data.update || 0) === 1) {
|
||||||
|
autotypeSuggestCache = {};
|
||||||
|
persistAutotypeSuggestCache();
|
||||||
|
}
|
||||||
|
if (typeof done === 'function') {
|
||||||
|
done(data || {});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function bindLegacyCommaAutocomplete(selector, endpointUrl, extractValue) {
|
function bindLegacyCommaAutocomplete(selector, endpointUrl, extractValue) {
|
||||||
var $field = $(selector);
|
var $field = $(selector);
|
||||||
if (!$field.length || !($.ui && $.ui.autocomplete)) {
|
if (!$field.length || !($.ui && $.ui.autocomplete)) {
|
||||||
|
|
@ -1602,6 +1711,15 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ensureAutotypeSuggestCacheLoaded();
|
||||||
|
var cacheKey = getAutotypeSuggestCacheKey(endpointUrl, term);
|
||||||
|
var hit = autotypeSuggestCache[cacheKey];
|
||||||
|
var now = Date.now();
|
||||||
|
if (hit && (now - Number(hit.ts || 0) < autotypeSuggestCacheTtlMs)) {
|
||||||
|
response(hit.rows || []);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$.getJSON(endpointUrl, { q: term }, function(data) {
|
$.getJSON(endpointUrl, { q: term }, function(data) {
|
||||||
var rows = (data && data.rows) ? data.rows : [];
|
var rows = (data && data.rows) ? data.rows : [];
|
||||||
var out = [];
|
var out = [];
|
||||||
|
|
@ -1616,6 +1734,8 @@
|
||||||
out.push({ label: value, value: value });
|
out.push({ label: value, value: value });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
autotypeSuggestCache[cacheKey] = { ts: Date.now(), rows: out };
|
||||||
|
persistAutotypeSuggestCache();
|
||||||
response(out);
|
response(out);
|
||||||
}).fail(function() {
|
}).fail(function() {
|
||||||
response([]);
|
response([]);
|
||||||
|
|
@ -5827,6 +5947,28 @@
|
||||||
loadOldPrescribedPanel();
|
loadOldPrescribedPanel();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '.btn-save-autotype-keyword', function() {
|
||||||
|
var section = ($(this).data('section') || '').toString();
|
||||||
|
var targetId = ($(this).data('target') || '').toString();
|
||||||
|
if (!section || !targetId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyword = getLastKeywordFromField(targetId);
|
||||||
|
if (!keyword || keyword.length < 2) {
|
||||||
|
$('.jsError').removeClass('text-success text-muted').addClass('text-danger').text('Type at least 2 characters in this field, then click Save keyword.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
} else {
|
||||||
|
$('.jsError').removeClass('text-success text-muted').addClass('text-danger').text(data.error_text || 'Unable to save keyword.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
initConsultSectionDesigner();
|
initConsultSectionDesigner();
|
||||||
|
|
||||||
bindLegacyCommaAutocomplete('#complaints', '<?= base_url('Opd_prescription/complaints_search') ?>', function(row) {
|
bindLegacyCommaAutocomplete('#complaints', '<?= base_url('Opd_prescription/complaints_search') ?>', function(row) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user