fix(doctor): correct IPD query columns (ipd_status, r_doc_id) to list admitted patients assigned to doctor

This commit is contained in:
DevSoftTech Server 2026-08-29 03:05:46 +05:30
parent 9665e820ec
commit 5bfe0689f1
2 changed files with 203 additions and 14 deletions

View File

@ -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,

View File

@ -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 &amp; 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 &amp; Complete Visit
<i className="bi bi-check-circle-fill me-1"></i> Save &amp; Mark Visited
</button>
</div>
</div>