fix(doctor): correct IPD query columns (ipd_status, r_doc_id) to list admitted patients assigned to doctor
This commit is contained in:
parent
9665e820ec
commit
5bfe0689f1
|
|
@ -400,19 +400,43 @@ class DoctorApi extends BaseController
|
|||
public function ipdList(int $docId)
|
||||
{
|
||||
$db = db_connect();
|
||||
$builder = $db->table('ipd_master ipd')
|
||||
->select('ipd.*, p.p_fname as fname, p.p_code as uhid, p.mphone1, p.gender, p.age, b.bed_code, b.bed_number, w.ward_name')
|
||||
->join('patient_master p', 'p.id = ipd.p_id', 'left')
|
||||
->join('bed_master b', 'b.id = ipd.bed_id', 'left')
|
||||
->join('ward_master w', 'w.id = b.ward_id', 'left')
|
||||
->where('ipd.status', 'admitted')
|
||||
->orderBy('ipd.id', 'DESC');
|
||||
helper('common');
|
||||
|
||||
if ($docId > 0 && $db->fieldExists('doc_id', 'ipd_master')) {
|
||||
$builder->where('ipd.doc_id', $docId);
|
||||
$sql = "SELECT ipd.id as ipd_id, ipd.ipd_code, ipd.p_id, ipd.r_doc_id, ipd.r_doc_name, ipd.register_date, ipd.problem,
|
||||
p.p_fname as fname, p.p_code as uhid, p.mphone1, p.gender, p.age, p.age_in_month, p.estimate_dob, p.dob,
|
||||
b.bed_code, b.bed_number, w.ward_name
|
||||
FROM ipd_master ipd
|
||||
JOIN patient_master p ON p.id = ipd.p_id
|
||||
LEFT JOIN bed_master b ON b.id = ipd.bed_no
|
||||
LEFT JOIN ward_master w ON w.id = b.ward_id
|
||||
WHERE ipd.ipd_status = 0";
|
||||
|
||||
if ($docId > 0) {
|
||||
$sql .= " AND (ipd.r_doc_id = " . (int) $docId . " OR ipd.r_doc_id IS NULL OR ipd.r_doc_id = 0)";
|
||||
}
|
||||
|
||||
$patients = $builder->get()->getResultArray();
|
||||
$sql .= " ORDER BY ipd.id DESC";
|
||||
|
||||
$query = $db->query($sql);
|
||||
$patients = $query->getResultArray();
|
||||
|
||||
foreach ($patients as &$r) {
|
||||
$ageDisplay = 'N/A';
|
||||
if (function_exists('get_age_1')) {
|
||||
$ageDisplay = get_age_1($r['dob'] ?? null, $r['age'] ?? '', $r['age_in_month'] ?? '', $r['estimate_dob'] ?? '');
|
||||
} elseif (! empty($r['age'])) {
|
||||
$ageDisplay = $r['age'] . ' Year';
|
||||
} elseif (! empty($r['dob']) && $r['dob'] !== '0000-00-00') {
|
||||
try {
|
||||
$ageDisplay = date_diff(date_create($r['dob']), date_create('today'))->y . ' Year';
|
||||
} catch (\Throwable $e) {}
|
||||
}
|
||||
|
||||
$r['age_display'] = $ageDisplay;
|
||||
$r['gender_label'] = ((int)($r['gender'] ?? 1) === 1) ? 'Male' : 'Female';
|
||||
$r['patient_display_name'] = $r['fname'] ?? 'Patient';
|
||||
$r['bed_label'] = ! empty($r['ward_name']) ? ($r['ward_name'] . ' - Bed ' . ($r['bed_number'] ?? $r['bed_code'] ?? '')) : 'General Ward';
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'status' => 1,
|
||||
|
|
|
|||
|
|
@ -136,7 +136,8 @@
|
|||
// Active Workspace state
|
||||
const [activePatient, setActivePatient] = useState(null);
|
||||
const [activeOpd, setActiveOpd] = useState(null);
|
||||
const [workspaceData, setWorkspaceData] = useState([]);
|
||||
const [workspaceEntries, setWorkspaceEntries] = useState([]);
|
||||
const [newTreatmentNote, setNewTreatmentNote] = useState('');
|
||||
const [emrTab, setEmrTab] = useState('vitals');
|
||||
const [installPrompt, setInstallPrompt] = useState(null);
|
||||
|
||||
|
|
@ -343,6 +344,46 @@
|
|||
setActiveOpd(null);
|
||||
};
|
||||
|
||||
const openIpdWorkspace = async (patient) => {
|
||||
setActivePatient(patient);
|
||||
try {
|
||||
const res = await fetch(`/api/v1/doctor/ipd/workspace/${patient.ipd_id}`);
|
||||
const json = await res.json();
|
||||
if (json.status === 1) {
|
||||
setWorkspaceEntries(json.entries || []);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to load IPD workspace', err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveTreatmentNote = async () => {
|
||||
if (!activePatient || !newTreatmentNote.trim()) return;
|
||||
|
||||
try {
|
||||
const body = new URLSearchParams();
|
||||
body.append('doctor_id', authenticatedDoctor ? authenticatedDoctor.id : 0);
|
||||
body.append('doctor_name', authenticatedDoctor ? authenticatedDoctor.name : 'Doctor');
|
||||
body.append('treatment_text', newTreatmentNote);
|
||||
|
||||
const res = await fetch(`/api/v1/doctor/ipd/treatment/save/${activePatient.ipd_id}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: body.toString()
|
||||
});
|
||||
const json = await res.json();
|
||||
if (json.status === 1) {
|
||||
alert(json.message || 'Clinical treatment note saved successfully!');
|
||||
setNewTreatmentNote('');
|
||||
openIpdWorkspace(activePatient);
|
||||
} else {
|
||||
alert(json.message || 'Failed to save treatment note');
|
||||
}
|
||||
} catch (err) {
|
||||
alert('Error saving treatment note');
|
||||
}
|
||||
};
|
||||
|
||||
const openOpdConsultation = async (app) => {
|
||||
setActiveOpd(app);
|
||||
try {
|
||||
|
|
@ -449,6 +490,14 @@
|
|||
return matchesFilter && matchesSearch;
|
||||
});
|
||||
|
||||
const filteredIpdPatients = ipdPatients.filter(pat => {
|
||||
const q = searchQuery.toLowerCase();
|
||||
const name = (pat.patient_display_name || pat.fname || '').toLowerCase();
|
||||
const uhid = (pat.uhid || '').toLowerCase();
|
||||
const ipdCode = (pat.ipd_code || '').toLowerCase();
|
||||
return searchQuery === '' || name.includes(q) || uhid.includes(q) || ipdCode.includes(q);
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// RENDER: LOCK SCREEN (PIN AUTHENTICATION)
|
||||
// -------------------------------------------------------------
|
||||
|
|
@ -554,7 +603,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div className="d-flex align-items-center gap-2">
|
||||
<button className="btn btn-sm btn-outline-light" onClick={() => fetchOpdAppointments(authenticatedDoctor.id)} title="Refresh">
|
||||
<button className="btn btn-sm btn-outline-light" onClick={() => { fetchOpdAppointments(authenticatedDoctor.id); fetchIpdPatients(authenticatedDoctor.id); }} title="Refresh">
|
||||
<i className="bi bi-arrow-clockwise"></i>
|
||||
</button>
|
||||
<button className="btn btn-sm btn-danger" onClick={handleLogout} title="Lock Device Access">
|
||||
|
|
@ -581,7 +630,7 @@
|
|||
</li>
|
||||
</ul>
|
||||
|
||||
{mainTab === 'opd' && (
|
||||
{mainTab === 'opd' ? (
|
||||
<div>
|
||||
{/* Status Filter Badges (Matching HMS Screen) */}
|
||||
<div className="d-flex gap-1 mb-3 overflow-auto pb-1">
|
||||
|
|
@ -683,8 +732,124 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
/* -------------------------------------------------------------
|
||||
ADMITTED IPD PATIENTS LIST (ASSIGNED TO DOCTOR)
|
||||
------------------------------------------------------------- */
|
||||
<div>
|
||||
{/* Search Input */}
|
||||
<div className="input-group mb-3">
|
||||
<span className="input-group-text bg-white"><i className="bi bi-search"></i></span>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="Search Admitted Patient Name, UHID, IPD Code..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="card shadow-sm border-0">
|
||||
<div className="card-body p-0 table-responsive">
|
||||
<table className="table table-hover align-middle mb-0" style={{minWidth: '650px'}}>
|
||||
<thead className="table-light">
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<th>IPD Code</th>
|
||||
<th>Admitted Patient</th>
|
||||
<th>Ward & Bed</th>
|
||||
<th>UHID</th>
|
||||
<th>Admitted Date</th>
|
||||
<th className="text-center">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredIpdPatients.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan="7" className="text-center text-muted py-4">
|
||||
<i className="bi bi-hospital fs-1 d-block text-secondary mb-2"></i>
|
||||
No Admitted IPD Patients found for Dr. {authenticatedDoctor.name}.
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
filteredIpdPatients.map(pat => (
|
||||
<tr key={pat.ipd_id}>
|
||||
<td><span className="badge bg-success text-uppercase">Admitted</span></td>
|
||||
<td><strong className="text-dark">{pat.ipd_code}</strong></td>
|
||||
<td>
|
||||
<div className="fw-bold text-primary">{pat.patient_display_name}</div>
|
||||
<small className="text-muted">({pat.age_display} / {pat.gender_label})</small>
|
||||
</td>
|
||||
<td><span className="badge bg-primary-subtle text-primary border">{pat.bed_label}</span></td>
|
||||
<td><code>{pat.uhid}</code></td>
|
||||
<td><small className="text-muted">{pat.register_date || 'Today'}</small></td>
|
||||
<td className="text-center">
|
||||
<button
|
||||
className="btn btn-sm btn-primary fw-bold"
|
||||
onClick={() => openIpdWorkspace(pat)}
|
||||
>
|
||||
<i className="bi bi-heart-pulse-fill me-1"></i> Bedside Workspace
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : activePatient ? (
|
||||
/* -------------------------------------------------------------
|
||||
IPD BEDSIDE WORKSPACE & CLINICAL ROUNDS
|
||||
------------------------------------------------------------- */
|
||||
<div>
|
||||
<div className="d-flex justify-content-between align-items-center mb-3">
|
||||
<button className="btn btn-sm btn-outline-dark" onClick={() => setActivePatient(null)}>
|
||||
<i className="bi bi-arrow-left me-1"></i> Back to Admitted IPDs
|
||||
</button>
|
||||
<span className="badge bg-success fs-6">Status: ADMITTED</span>
|
||||
</div>
|
||||
|
||||
<div className="card shadow-sm mb-3 border-0 bg-primary text-white">
|
||||
<div className="card-body p-3">
|
||||
<h5 className="fw-bold mb-1">{activePatient.patient_display_name}</h5>
|
||||
<small className="opacity-90 me-3">Demographics: {activePatient.age_display} / {activePatient.gender_label}</small>
|
||||
<small className="opacity-90 me-3">UHID: {activePatient.uhid}</small>
|
||||
<small className="opacity-90 me-3">IPD: {activePatient.ipd_code}</small>
|
||||
<small className="opacity-90">Bed: {activePatient.bed_label}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Write Doctor Round Note / Treatment Order */}
|
||||
<div className="card p-3 shadow-sm border-0 mb-3">
|
||||
<label className="form-label fw-bold small text-dark mb-1"><i className="bi bi-pencil-square text-primary me-1"></i> Write Doctor Round Clinical Note / Treatment Order</label>
|
||||
<textarea className="form-control form-control-sm mb-2" rows="3" placeholder="Enter clinical observations, daily round progress note, or bedside treatment instructions..." value={newTreatmentNote} onChange={e => setNewTreatmentNote(e.target.value)}></textarea>
|
||||
<button className="btn btn-primary btn-sm fw-bold align-self-end" onClick={handleSaveTreatmentNote}>
|
||||
<i className="bi bi-send-fill me-1"></i> Save Round Note
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Bedside Log Entries */}
|
||||
<div className="card p-3 shadow-sm border-0">
|
||||
<h6 className="fw-bold mb-3"><i className="bi bi-journal-medical me-1"></i> Bedside Clinical Log History</h6>
|
||||
{workspaceEntries.length === 0 ? (
|
||||
<p className="text-muted small">No bedside log entries recorded yet.</p>
|
||||
) : (
|
||||
workspaceEntries.map(e => (
|
||||
<div key={e.id} className="p-2 border rounded bg-light mb-2">
|
||||
<div className="d-flex justify-content-between align-items-center mb-1">
|
||||
<span className="badge bg-secondary" style={{fontSize: '10px'}}>{e.entry_type}</span>
|
||||
<small className="text-muted" style={{fontSize: '10px'}}>{e.recorded_at} - by {e.recorded_by}</small>
|
||||
</div>
|
||||
<div className="small fw-semibold">{e.treatment_text || e.general_note}</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : activeOpd ? (
|
||||
/* -------------------------------------------------------------
|
||||
OPD CONSULTATION & PRESCRIPTION FORM (NATIVE CONTROLS COPY)
|
||||
|
|
@ -866,7 +1031,7 @@
|
|||
<i className="bi bi-printer me-1"></i> Print PDF
|
||||
</button>
|
||||
<button className="btn btn-success btn-lg w-50 fw-bold py-2 shadow-sm" onClick={handleSaveOpdPrescription}>
|
||||
<i className="bi bi-check-circle-fill me-1"></i> Save & Complete Visit
|
||||
<i className="bi bi-check-circle-fill me-1"></i> Save & Mark Visited
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user