fix(nursing): resolve SQL column errors in opdList query, add Doctor filter dropdown, and Vitals Pending/Done status filter buttons
This commit is contained in:
parent
7bdfbab2bd
commit
38573f5b15
|
|
@ -247,16 +247,17 @@ class NursingApi extends BaseController
|
|||
|
||||
$dateMode = $this->request->getGet('date_mode') ?? 'today';
|
||||
$docId = (int) ($this->request->getGet('doctor_id') ?? 0);
|
||||
$vitalsFilter = $this->request->getGet('vitals_status') ?? 'all'; // 'all', 'pending', 'done'
|
||||
|
||||
$whereClause = "WHERE 1=1";
|
||||
if ($dateMode === 'today') {
|
||||
$whereClause .= " AND DATE(o.date_of_assign) = CURDATE()";
|
||||
$whereClause .= " AND DATE(o.apointment_date) = CURDATE()";
|
||||
}
|
||||
if ($docId > 0) {
|
||||
$whereClause .= " AND o.doc_id = " . $docId;
|
||||
}
|
||||
|
||||
$sql = "SELECT o.opd_id, o.opd_code, o.opd_no, o.p_id, o.doc_id, o.date_of_assign, o.opd_status, o.opd_type,
|
||||
$sql = "SELECT o.opd_id, o.opd_code, o.opd_no, o.p_id, o.doc_id, o.apointment_date, o.opd_status, coalesce(o.opd_fee_type, 'Cash') as opd_type,
|
||||
p.p_fname as fname, p.p_rname as rname, p.p_code as uhid, p.mphone1, p.gender, p.age, p.age_in_month, p.estimate_dob, p.dob,
|
||||
concat('Dr. ', d.p_fname, ' ', coalesce(d.p_lname, '')) as doctor_name,
|
||||
pr.temp, pr.pulse, pr.bp, pr.diastolic, pr.spo2, pr.weight, pr.height, pr.rr_min
|
||||
|
|
@ -270,10 +271,14 @@ class NursingApi extends BaseController
|
|||
$query = $db->query($sql);
|
||||
$records = $query->getResultArray();
|
||||
|
||||
$todayTotalCount = (int) $db->query("SELECT count(*) as c FROM opd_master WHERE DATE(date_of_assign) = CURDATE()")->getRow()->c;
|
||||
$todayTotalCount = (int) $db->query("SELECT count(*) as c FROM opd_master WHERE DATE(apointment_date) = CURDATE()")->getRow()->c;
|
||||
$allTotalCount = (int) $db->query("SELECT count(*) as c FROM opd_master")->getRow()->c;
|
||||
|
||||
$counts = ['all' => 0, 'waiting' => 0, 'visited' => 0, 'booked' => 0, 'cancelled' => 0, 'today_total' => $todayTotalCount, 'all_total' => $allTotalCount];
|
||||
$counts = [
|
||||
'all' => 0, 'waiting' => 0, 'visited' => 0, 'booked' => 0, 'cancelled' => 0,
|
||||
'today_total' => $todayTotalCount, 'all_total' => $allTotalCount,
|
||||
'pending_vitals' => 0, 'done_vitals' => 0
|
||||
];
|
||||
$appointments = [];
|
||||
|
||||
foreach ($records as $r) {
|
||||
|
|
@ -291,6 +296,21 @@ class NursingApi extends BaseController
|
|||
$statusLabel = 'Booked';
|
||||
}
|
||||
|
||||
$hasVitals = !empty($r['temp']) || !empty($r['pulse']) || !empty($r['bp']) || !empty($r['spo2']) || !empty($r['weight']);
|
||||
|
||||
if ($hasVitals) {
|
||||
$counts['done_vitals']++;
|
||||
} else {
|
||||
$counts['pending_vitals']++;
|
||||
}
|
||||
|
||||
if ($vitalsFilter === 'pending' && $hasVitals) {
|
||||
continue;
|
||||
}
|
||||
if ($vitalsFilter === 'done' && !$hasVitals) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$counts['all']++;
|
||||
if (isset($counts[$statusKey])) {
|
||||
$counts[$statusKey]++;
|
||||
|
|
@ -303,8 +323,6 @@ class NursingApi extends BaseController
|
|||
$ageDisplay = $r['age'] . ' Year';
|
||||
}
|
||||
|
||||
$hasVitals = !empty($r['temp']) || !empty($r['pulse']) || !empty($r['bp']) || !empty($r['spo2']) || !empty($r['weight']);
|
||||
|
||||
$r['status_key'] = $statusKey;
|
||||
$r['status_label'] = $statusLabel;
|
||||
$r['age_display'] = $ageDisplay;
|
||||
|
|
|
|||
|
|
@ -232,9 +232,12 @@
|
|||
|
||||
// OPD Patients State
|
||||
const [opdAppointments, setOpdAppointments] = useState([]);
|
||||
const [opdCounts, setOpdCounts] = useState({ all: 0, waiting: 0, visited: 0, booked: 0, cancelled: 0 });
|
||||
const [opdCounts, setOpdCounts] = useState({ all: 0, waiting: 0, visited: 0, booked: 0, cancelled: 0, pending_vitals: 0, done_vitals: 0 });
|
||||
const [opdFilter, setOpdFilter] = useState('all');
|
||||
const [opdDateMode, setOpdDateMode] = useState('today');
|
||||
const [doctors, setDoctors] = useState([]);
|
||||
const [selectedDoctorId, setSelectedDoctorId] = useState('0');
|
||||
const [vitalsFilter, setVitalsFilter] = useState('all'); // 'all', 'pending', 'done'
|
||||
|
||||
// Modals & Action State
|
||||
const [activeOpdForVitals, setActiveOpdForVitals] = useState(null);
|
||||
|
|
@ -287,10 +290,25 @@
|
|||
}
|
||||
};
|
||||
|
||||
const fetchOpdAppointments = async (dateModeOverride) => {
|
||||
const fetchDoctors = async () => {
|
||||
try {
|
||||
const mode = dateModeOverride || opdDateMode;
|
||||
const res = await fetch(`/api/v1/nursing/opd/list?date_mode=${mode}`);
|
||||
const res = await fetch('/api/v1/doctor/list');
|
||||
const json = await res.json();
|
||||
if (json.status === 1 && json.data) {
|
||||
setDoctors(json.data);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to load doctors', err);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchOpdAppointments = async (dateModeOverride, docIdOverride, vitalsOverride) => {
|
||||
try {
|
||||
const mode = dateModeOverride !== undefined ? dateModeOverride : opdDateMode;
|
||||
const docId = docIdOverride !== undefined ? docIdOverride : selectedDoctorId;
|
||||
const vFilter = vitalsOverride !== undefined ? vitalsOverride : vitalsFilter;
|
||||
|
||||
const res = await fetch(`/api/v1/nursing/opd/list?date_mode=${mode}&doctor_id=${docId}&vitals_status=${vFilter}`);
|
||||
const json = await res.json();
|
||||
if (json.status === 1) {
|
||||
setOpdAppointments(json.appointments || []);
|
||||
|
|
@ -332,7 +350,7 @@
|
|||
if (parsed && parsed.nurse && parsed.expires_at > Date.now()) {
|
||||
setAuthenticatedNurse(parsed.nurse);
|
||||
fetchBeds();
|
||||
fetchOpdAppointments(opdDateMode);
|
||||
fetchOpdAppointments(opdDateMode, selectedDoctorId, vitalsFilter);
|
||||
} else {
|
||||
localStorage.removeItem(DEVICE_AUTH_KEY);
|
||||
}
|
||||
|
|
@ -340,6 +358,7 @@
|
|||
}
|
||||
fetchBeds();
|
||||
fetchNurses();
|
||||
fetchDoctors();
|
||||
}, []);
|
||||
|
||||
const handleVerifyPin = async () => {
|
||||
|
|
@ -825,18 +844,18 @@
|
|||
OPD PATIENTS TAB (VITALS & DOCUMENT SCANNING)
|
||||
------------------------------------------------------------- */
|
||||
<div>
|
||||
{/* Date Mode Segmented Controller */}
|
||||
{/* Date Mode Segmented Controller & Filters */}
|
||||
<div className="d-flex align-items-center justify-content-between mb-3 bg-white p-2 rounded-4 border shadow-sm flex-wrap gap-2">
|
||||
<div className="d-flex gap-1 bg-light p-1 rounded-pill border">
|
||||
<button
|
||||
className={`btn btn-sm rounded-pill px-3 py-1 fw-bold ${opdDateMode === 'today' ? 'btn-primary shadow-sm' : 'btn-light text-secondary'}`}
|
||||
onClick={() => { setOpdDateMode('today'); fetchOpdAppointments('today'); }}
|
||||
onClick={() => { setOpdDateMode('today'); fetchOpdAppointments('today', selectedDoctorId, vitalsFilter); }}
|
||||
>
|
||||
<i className="bi bi-calendar-event me-1"></i> Today's OPDs ({opdCounts.today_total || 0})
|
||||
</button>
|
||||
<button
|
||||
className={`btn btn-sm rounded-pill px-3 py-1 fw-bold ${opdDateMode === 'all' ? 'btn-primary shadow-sm' : 'btn-light text-secondary'}`}
|
||||
onClick={() => { setOpdDateMode('all'); fetchOpdAppointments('all'); }}
|
||||
onClick={() => { setOpdDateMode('all'); fetchOpdAppointments('all', selectedDoctorId, vitalsFilter); }}
|
||||
>
|
||||
<i className="bi bi-calendar3 me-1"></i> All Dates ({opdCounts.all_total || 0})
|
||||
</button>
|
||||
|
|
@ -846,16 +865,67 @@
|
|||
</span>
|
||||
</div>
|
||||
|
||||
{/* Status Filters */}
|
||||
{/* Doctor Filter & Vitals Filter Controls Box */}
|
||||
<div className="bg-white p-3 mb-3 rounded-4 border shadow-sm">
|
||||
<div className="row g-2 align-items-center">
|
||||
<div className="col-12 col-md-6">
|
||||
<label className="form-label small fw-bold text-dark mb-1">
|
||||
<i className="bi bi-person-badge text-primary me-1"></i> Filter by Doctor
|
||||
</label>
|
||||
<select
|
||||
className="form-select form-select-sm rounded-pill fw-semibold"
|
||||
value={selectedDoctorId}
|
||||
onChange={(e) => {
|
||||
const val = e.target.value;
|
||||
setSelectedDoctorId(val);
|
||||
fetchOpdAppointments(opdDateMode, val, vitalsFilter);
|
||||
}}
|
||||
>
|
||||
<option value="0">-- All Hospital Doctors --</option>
|
||||
{doctors.map(d => (
|
||||
<option key={d.id} value={d.id}>{d.name}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="col-12 col-md-6">
|
||||
<label className="form-label small fw-bold text-dark mb-1">
|
||||
<i className="bi bi-heart-pulse-fill text-danger me-1"></i> Vitals Status Filter
|
||||
</label>
|
||||
<div className="d-flex gap-1">
|
||||
<button
|
||||
className={`btn btn-sm flex-fill rounded-pill py-1 fw-bold ${vitalsFilter === 'all' ? 'btn-primary shadow-sm' : 'btn-light border text-secondary'}`}
|
||||
onClick={() => { setVitalsFilter('all'); fetchOpdAppointments(opdDateMode, selectedDoctorId, 'all'); }}
|
||||
>
|
||||
All ({opdCounts.all})
|
||||
</button>
|
||||
<button
|
||||
className={`btn btn-sm flex-fill rounded-pill py-1 fw-bold ${vitalsFilter === 'pending' ? 'btn-warning text-dark shadow-sm' : 'btn-light border text-secondary'}`}
|
||||
onClick={() => { setVitalsFilter('pending'); fetchOpdAppointments(opdDateMode, selectedDoctorId, 'pending'); }}
|
||||
>
|
||||
⚠️ Pending ({opdCounts.pending_vitals || 0})
|
||||
</button>
|
||||
<button
|
||||
className={`btn btn-sm flex-fill rounded-pill py-1 fw-bold ${vitalsFilter === 'done' ? 'btn-success text-white shadow-sm' : 'btn-light border text-secondary'}`}
|
||||
onClick={() => { setVitalsFilter('done'); fetchOpdAppointments(opdDateMode, selectedDoctorId, 'done'); }}
|
||||
>
|
||||
✅ Filled ({opdCounts.done_vitals || 0})
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Visit Status Filter Pills */}
|
||||
<div className="d-flex gap-2 mb-3 overflow-auto no-scrollbar pb-1">
|
||||
<button className={`btn btn-sm rounded-pill px-3 ${opdFilter === 'all' ? 'btn-primary fw-bold' : 'btn-white bg-white text-secondary border'}`} onClick={() => setOpdFilter('all')}>
|
||||
All ({opdCounts.all})
|
||||
All Visits
|
||||
</button>
|
||||
<button className={`btn btn-sm rounded-pill px-3 ${opdFilter === 'waiting' ? 'btn-primary fw-bold' : 'btn-white bg-white text-secondary border'}`} onClick={() => setOpdFilter('waiting')}>
|
||||
Waiting ({opdCounts.waiting})
|
||||
Waiting
|
||||
</button>
|
||||
<button className={`btn btn-sm rounded-pill px-3 ${opdFilter === 'visited' ? 'btn-info text-dark fw-bold' : 'btn-white bg-white text-secondary border'}`} onClick={() => setOpdFilter('visited')}>
|
||||
Visited ({opdCounts.visited})
|
||||
Visited
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user