Fix discharge medicine save, add CKEditor, and Review After quick select
Medicine Saving Fix (MAJOR):
- Add hidden field 'discharge_medicine_json' to capture medicine table data
- Add serializeDischargeMedicineTable() function to convert DOM table to JSON before form submit
- Hook serialization into form submit event (before syncEditorValues)
- Update controller to process discharge_medicine_json in save_main action
- Insert medicines into ipd_discharge_prescrption_prescribed or ipd_discharge_prescription_prescribed table
- Medicine data now persists on page reload
CKEditor Integration:
- Initialize CKEditor for 'instruction_other' (Other Advice) field
- Initialize CKEditor for 'instruction_remark' (Discharge Summary) field
- Add simplified toolbar: Bold, Italic, Underline, Lists, Undo/Redo
- Properly destroy existing instances before re-initialization
- Height: 120px for Other Advice, 150px for Discharge Summary
Review After Quick Select Buttons:
- Add getNextVisitOptions() method to Ipd_discharge controller (copied from Opd_prescription)
- Add resolveFirstField() helper method
- Pass next_visit_options array to view
- Render quick select buttons: '3 Days (09-06-2026)', '1 Week (13-06-2026)', etc.
- Add JavaScript handler: click button → populate #discharge_review_after input
- Change Review After field from col-md-4 to col-md-6 for better layout
- Add col-md-12 wrapper for button row with flex-wrap gap-2 layout
Technical Details:
- Medicine JSON structure: {med_type, med_name, dosage, dosage_when, dosage_freq, no_of_days, qty, remark}
- JSON processing includes column existence checks for flexibility
- Quick select buttons use data-value attribute for value insertion
- Falls back to hardcoded date options if opd_nextvisit table missing
- Medicine insert uses med_id=0 to mark client-added medicines
User Workflow:
1. Add medicines → shown in table (Type, Medicine, Dose labels, Duration formatted)
2. Click 'Save Discharge Advice' → medicines serialize to JSON → POST to controller
3. Controller parses JSON → inserts to database → medicines persist on reload
4. Other Advice/Discharge Summary now have rich text editing (bold, lists, etc.)
5. Review After: click '1 Week (13-06-2026)' button → auto-fills input
This commit is contained in:
parent
e229ae3df2
commit
e731ef61e9
|
|
@ -5126,6 +5126,58 @@ class Ipd_discharge extends BaseController
|
|||
$savedAny = $this->upsertByIpd('ipd_discharge_drug_food_interaction', $ipdId, $legacyData) || $savedAny;
|
||||
}
|
||||
|
||||
// Process discharge medicine JSON (newly added medicines from the form).
|
||||
$medicineJson = trim((string) ($this->request->getPost('discharge_medicine_json') ?? ''));
|
||||
if ($medicineJson !== '') {
|
||||
$medicines = json_decode($medicineJson, true);
|
||||
if (is_array($medicines) && ! empty($medicines)) {
|
||||
$legacyTable = $this->findFirstExistingTable([
|
||||
'ipd_discharge_prescrption_prescribed',
|
||||
'ipd_discharge_prescription_prescribed',
|
||||
]);
|
||||
|
||||
if ($legacyTable !== null) {
|
||||
// Clear previously added client-side medicines (optional - keeps only DB-saved ones).
|
||||
// Comment out if you want to preserve all medicines.
|
||||
// $this->db->table($legacyTable)->where('ipd_id', $ipdId)->where('med_id', 0)->delete();
|
||||
|
||||
foreach ($medicines as $med) {
|
||||
$medName = trim((string) ($med['med_name'] ?? ''));
|
||||
if ($medName === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$insert = [
|
||||
'ipd_id' => $ipdId,
|
||||
'med_id' => 0,
|
||||
'med_name' => $medName,
|
||||
'update_by' => $userLabel,
|
||||
];
|
||||
|
||||
// Add optional fields if columns exist.
|
||||
$optionalFields = [
|
||||
'med_type' => 'med_type',
|
||||
'dosage' => 'dosage',
|
||||
'dosage_when' => 'dosage_when',
|
||||
'dosage_freq' => 'dosage_freq',
|
||||
'no_of_days' => 'no_of_days',
|
||||
'qty' => 'qty',
|
||||
'remark' => 'remark',
|
||||
];
|
||||
|
||||
foreach ($optionalFields as $jsonKey => $colName) {
|
||||
if ($this->db->fieldExists($colName, $legacyTable)) {
|
||||
$insert[$colName] = trim((string) ($med[$jsonKey] ?? ''));
|
||||
}
|
||||
}
|
||||
|
||||
$this->db->table($legacyTable)->insert($insert);
|
||||
$savedAny = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Examination on Admission (General Examination values).
|
||||
if ($this->tableHasColumns('ipd_discharge_general_exam_col', ['id', 'col_name'])
|
||||
&& $this->tableHasColumns('ipd_discharge_1_b', ['ipd_d_id', 'col_id', 'short_head', 'rdata'])) {
|
||||
|
|
@ -5586,6 +5638,7 @@ class Ipd_discharge extends BaseController
|
|||
'instruction_other' => (string) ($instructionMeta['other_text'] ?? ''),
|
||||
'inhos_remark' => (string) ($inhosRow['comp_remark'] ?? ''),
|
||||
'other_exam_text' => (string) ($otherExamParsed['text'] ?? ''),
|
||||
'next_visit_options' => $this->getNextVisitOptions(date('Y-m-d')),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -6145,4 +6198,94 @@ class Ipd_discharge extends BaseController
|
|||
// Do not block discharge workflows if queue service is unavailable.
|
||||
}
|
||||
}
|
||||
|
||||
private function getNextVisitOptions(string $baseDate): array
|
||||
{
|
||||
$fallbackDays = [3, 4, 5, 7, 10, 15, 20, 30, 60];
|
||||
$fallbackDesc = [
|
||||
3 => '3 Days',
|
||||
4 => '4 Days',
|
||||
5 => '5 Days',
|
||||
7 => '1 Week',
|
||||
10 => '10 Days',
|
||||
15 => '15 Days',
|
||||
20 => '20 days',
|
||||
30 => '1 Month',
|
||||
60 => '2 Months',
|
||||
];
|
||||
|
||||
$baseTs = strtotime($baseDate);
|
||||
if ($baseTs === false) {
|
||||
$baseTs = strtotime(date('Y-m-d'));
|
||||
}
|
||||
|
||||
$rows = [];
|
||||
if ($this->db->tableExists('opd_nextvisit')) {
|
||||
$fields = $this->db->getFieldNames('opd_nextvisit') ?? [];
|
||||
$descField = $this->resolveFirstField($fields, ['next_visit_desc', 'visit_desc', 'description', 'nextvisit_desc', 'name']);
|
||||
$daysField = $this->resolveFirstField($fields, ['no_of_days', 'days', 'day_count']);
|
||||
|
||||
if ($descField !== null && $daysField !== null) {
|
||||
$builder = $this->db->table('opd_nextvisit')
|
||||
->select($descField . ' as visit_desc,' . $daysField . ' as no_of_days');
|
||||
|
||||
if (in_array('status', $fields, true)) {
|
||||
$builder->where('status', 1);
|
||||
}
|
||||
|
||||
$rows = $builder
|
||||
->orderBy($daysField, 'ASC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
}
|
||||
}
|
||||
|
||||
$options = [];
|
||||
if (! empty($rows)) {
|
||||
foreach ($rows as $row) {
|
||||
$days = (int) ($row['no_of_days'] ?? 0);
|
||||
$desc = trim((string) ($row['visit_desc'] ?? ''));
|
||||
if ($days <= 0 || $desc === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$visitDate = date('d-m-Y', strtotime('+' . $days . ' day', $baseTs));
|
||||
$value = $desc . ' (' . $visitDate . ')';
|
||||
|
||||
$options[] = [
|
||||
'desc' => $desc,
|
||||
'days' => $days,
|
||||
'date' => $visitDate,
|
||||
'value' => $value,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($options)) {
|
||||
foreach ($fallbackDays as $days) {
|
||||
$desc = (string) ($fallbackDesc[$days] ?? ($days . ' Days'));
|
||||
$visitDate = date('d-m-Y', strtotime('+' . $days . ' day', $baseTs));
|
||||
$value = $desc . ' (' . $visitDate . ')';
|
||||
$options[] = [
|
||||
'desc' => $desc,
|
||||
'days' => $days,
|
||||
'date' => $visitDate,
|
||||
'value' => $value,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
private function resolveFirstField(array $fields, array $candidates): ?string
|
||||
{
|
||||
foreach ($candidates as $candidate) {
|
||||
if (in_array($candidate, $fields, true)) {
|
||||
return $candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1269,6 +1269,7 @@ $allergyStatusNoKnown = in_array($allergyStatusNormalized, ['no known drug aller
|
|||
</table>
|
||||
</div>
|
||||
<input type="hidden" name="drug_remove_id" id="drug_remove_id" value="0">
|
||||
<input type="hidden" name="discharge_medicine_json" id="discharge_medicine_json" value="">
|
||||
</div>
|
||||
|
||||
<!-- Add Medicine Form -->
|
||||
|
|
@ -1426,9 +1427,19 @@ $allergyStatusNoKnown = in_array($allergyStatusNormalized, ['no known drug aller
|
|||
<label class="form-label">Discharge Summary</label>
|
||||
<textarea class="form-control" name="instruction_remark" id="instruction_remark" rows="3"><?= esc((string) ($instruction_remark ?? '')) ?></textarea>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Review After (days/text)</label>
|
||||
<input type="text" class="form-control" name="review_after" value="<?= esc((string) ($review_after ?? '')) ?>">
|
||||
<input type="text" class="form-control" name="review_after" id="discharge_review_after" value="<?= esc((string) ($review_after ?? '')) ?>" placeholder="e.g. 5 Days">
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<?php $nextVisitOptions = is_array($next_visit_options ?? null) ? $next_visit_options : []; ?>
|
||||
<?php foreach ($nextVisitOptions as $nextVisitOpt) : ?>
|
||||
<?php $nextVisitValue = trim((string) ($nextVisitOpt['value'] ?? '')); ?>
|
||||
<?php if ($nextVisitValue === '') { continue; } ?>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm discharge-review-chip" data-value="<?= esc($nextVisitValue) ?>"><?= esc($nextVisitValue) ?></button>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12 d-flex justify-content-end">
|
||||
<button type="submit" class="btn btn-outline-success btn-sm" name="action" value="save_main" data-reload-section="section-instructions">Save Discharge Advice</button>
|
||||
|
|
@ -1713,6 +1724,14 @@ $allergyStatusNoKnown = in_array($allergyStatusNormalized, ['no known drug aller
|
|||
CKEDITOR.instances.systemic_exam_editor.destroy(true);
|
||||
}
|
||||
|
||||
if (CKEDITOR.instances.instruction_other) {
|
||||
CKEDITOR.instances.instruction_other.destroy(true);
|
||||
}
|
||||
|
||||
if (CKEDITOR.instances.instruction_remark) {
|
||||
CKEDITOR.instances.instruction_remark.destroy(true);
|
||||
}
|
||||
|
||||
if (document.getElementById('complaint_remark_editor')) {
|
||||
CKEDITOR.replace('complaint_remark_editor', {
|
||||
height: 180
|
||||
|
|
@ -1724,6 +1743,28 @@ $allergyStatusNoKnown = in_array($allergyStatusNormalized, ['no known drug aller
|
|||
height: 200
|
||||
});
|
||||
}
|
||||
|
||||
if (document.getElementById('instruction_other')) {
|
||||
CKEDITOR.replace('instruction_other', {
|
||||
height: 120,
|
||||
toolbar: [
|
||||
{ name: 'basicstyles', items: ['Bold', 'Italic', 'Underline'] },
|
||||
{ name: 'paragraph', items: ['NumberedList', 'BulletedList'] },
|
||||
{ name: 'editing', items: ['Undo', 'Redo'] }
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
if (document.getElementById('instruction_remark')) {
|
||||
CKEDITOR.replace('instruction_remark', {
|
||||
height: 150,
|
||||
toolbar: [
|
||||
{ name: 'basicstyles', items: ['Bold', 'Italic', 'Underline'] },
|
||||
{ name: 'paragraph', items: ['NumberedList', 'BulletedList'] },
|
||||
{ name: 'editing', items: ['Undo', 'Redo'] }
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getCsrfPair(form) {
|
||||
|
|
@ -3697,6 +3738,19 @@ $allergyStatusNoKnown = in_array($allergyStatusNormalized, ['no known drug aller
|
|||
|
||||
refreshInstructionPreview();
|
||||
initDietaryMasterCrud(getDischargeForm());
|
||||
|
||||
// Review After quick select buttons
|
||||
var reviewChips = section.querySelectorAll('.discharge-review-chip');
|
||||
var reviewInput = section.querySelector('#discharge_review_after');
|
||||
reviewChips.forEach(function(chip) {
|
||||
chip.addEventListener('click', function() {
|
||||
var value = chip.getAttribute('data-value') || chip.textContent.trim();
|
||||
if (reviewInput) {
|
||||
reviewInput.value = value;
|
||||
reviewInput.focus();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function initDietaryMasterCrud(form) {
|
||||
|
|
@ -4210,6 +4264,41 @@ $allergyStatusNoKnown = in_array($allergyStatusNormalized, ['no known drug aller
|
|||
}
|
||||
}
|
||||
|
||||
function serializeDischargeMedicineTable() {
|
||||
var tbody = document.querySelector('#discharge_medicine_tbody');
|
||||
var hiddenField = document.querySelector('#discharge_medicine_json');
|
||||
if (!tbody || !hiddenField) {
|
||||
return;
|
||||
}
|
||||
|
||||
var rows = tbody.querySelectorAll('tr');
|
||||
var medicines = [];
|
||||
|
||||
rows.forEach(function(row) {
|
||||
var cells = row.querySelectorAll('td');
|
||||
// Skip empty state row or rows with insufficient cells
|
||||
if (cells.length >= 8) {
|
||||
var cellData = {
|
||||
med_type: cells[0].textContent.trim(),
|
||||
med_name: cells[1].textContent.trim(),
|
||||
dosage: cells[2].textContent.trim(),
|
||||
dosage_when: cells[3].textContent.trim(),
|
||||
dosage_freq: cells[4].textContent.trim(),
|
||||
no_of_days: cells[5].textContent.trim(),
|
||||
qty: cells[6].textContent.trim(),
|
||||
remark: cells[7].textContent.trim()
|
||||
};
|
||||
|
||||
// Only add non-empty medicine names
|
||||
if (cellData.med_name !== '' && cellData.med_name !== 'No medicine added') {
|
||||
medicines.push(cellData);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
hiddenField.value = JSON.stringify(medicines);
|
||||
}
|
||||
|
||||
function syncEditorValues() {
|
||||
if (!window.CKEDITOR) {
|
||||
return;
|
||||
|
|
@ -4551,6 +4640,7 @@ $allergyStatusNoKnown = in_array($allergyStatusNormalized, ['no known drug aller
|
|||
form.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
syncEditorValues();
|
||||
serializeDischargeMedicineTable();
|
||||
syncClinicalLabSelection(form);
|
||||
syncCoMorbidityHidden(form);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user