feat(doctor): add current date filter for OPD appointments and my assigned vs all hospital admissions toggle for IPDs
This commit is contained in:
parent
cd0b78d5fd
commit
7a0631979a
|
|
@ -167,43 +167,54 @@ class DoctorApi extends BaseController
|
|||
$db = db_connect();
|
||||
helper('common');
|
||||
|
||||
$sql = "SELECT o.opd_id, o.opd_code, o.opd_no, o.opd_status, o.apointment_date,
|
||||
o.opd_fee_desc, o.opd_fee_amount, o.payment_status, o.opd_fee_type,
|
||||
coalesce(o.running_opd, 0) as running_opd,
|
||||
p.id as p_id, p.p_code, p.p_fname as P_name, p.gender, p.age, p.age_in_month, p.estimate_dob, p.dob, p.mphone1,
|
||||
(CASE
|
||||
WHEN (coalesce(o.running_opd,0)=1 OR o.opd_fee_type=3 OR UPPER(coalesce(o.opd_fee_desc,'')) LIKE '%RUNNING%') THEN 'Running'
|
||||
WHEN o.payment_mode=4 THEN 'Credit to ECHS'
|
||||
WHEN o.payment_status=1 AND o.payment_mode=0 THEN 'No Cost'
|
||||
WHEN o.payment_mode=1 THEN 'Cash'
|
||||
WHEN o.payment_mode=2 THEN 'Bank Card'
|
||||
ELSE 'Pending'
|
||||
END) as opd_type,
|
||||
IF(pr.id IS NULL, 0, 1) as has_prescription,
|
||||
coalesce(pr.queue_no, 0) as queue_no
|
||||
FROM opd_master o
|
||||
JOIN patient_master p ON o.p_id = p.id
|
||||
LEFT JOIN opd_prescription pr ON o.opd_id = pr.opd_id";
|
||||
$dateMode = $this->request->getGet('date_mode') ?? 'today'; // 'today' or 'all'
|
||||
|
||||
$baseSql = "SELECT o.opd_id, o.opd_code, o.opd_no, o.opd_status, o.apointment_date,
|
||||
o.opd_fee_desc, o.opd_fee_amount, o.payment_status, o.opd_fee_type,
|
||||
coalesce(o.running_opd, 0) as running_opd,
|
||||
p.id as p_id, p.p_code, p.p_fname as P_name, p.gender, p.age, p.age_in_month, p.estimate_dob, p.dob, p.mphone1,
|
||||
(CASE
|
||||
WHEN (coalesce(o.running_opd,0)=1 OR o.opd_fee_type=3 OR UPPER(coalesce(o.opd_fee_desc,'')) LIKE '%RUNNING%') THEN 'Running'
|
||||
WHEN o.payment_mode=4 THEN 'Credit to ECHS'
|
||||
WHEN o.payment_status=1 AND o.payment_mode=0 THEN 'No Cost'
|
||||
WHEN o.payment_mode=1 THEN 'Cash'
|
||||
WHEN o.payment_mode=2 THEN 'Bank Card'
|
||||
ELSE 'Pending'
|
||||
END) as opd_type,
|
||||
IF(pr.id IS NULL, 0, 1) as has_prescription,
|
||||
coalesce(pr.queue_no, 0) as queue_no
|
||||
FROM opd_master o
|
||||
JOIN patient_master p ON o.p_id = p.id
|
||||
LEFT JOIN opd_prescription pr ON o.opd_id = pr.opd_id
|
||||
WHERE 1=1";
|
||||
|
||||
if ($docId > 0) {
|
||||
$sql .= " WHERE o.doc_id = " . (int) $docId;
|
||||
$baseSql .= " AND o.doc_id = " . (int) $docId;
|
||||
}
|
||||
|
||||
$sql .= " ORDER BY o.opd_id DESC LIMIT 100";
|
||||
$sqlToday = $baseSql . " AND DATE(o.apointment_date) = CURDATE() ORDER BY o.opd_id DESC LIMIT 100";
|
||||
$sqlAll = $baseSql . " ORDER BY o.opd_id DESC LIMIT 100";
|
||||
|
||||
$query = $db->query($sql);
|
||||
$allRows = $query->getResultArray();
|
||||
$queryToday = $db->query($sqlToday);
|
||||
$todayRows = $queryToday->getResultArray();
|
||||
|
||||
$queryAll = $db->query($sqlAll);
|
||||
$allRows = $queryAll->getResultArray();
|
||||
|
||||
$targetRows = ($dateMode === 'today') ? $todayRows : $allRows;
|
||||
|
||||
$appointments = [];
|
||||
$counts = [
|
||||
'all' => count($allRows),
|
||||
'all' => count($targetRows),
|
||||
'booked' => 0,
|
||||
'waiting' => 0,
|
||||
'visited' => 0,
|
||||
'cancelled' => 0,
|
||||
'today_total' => count($todayRows),
|
||||
'all_total' => count($allRows),
|
||||
];
|
||||
|
||||
foreach ($allRows as $r) {
|
||||
foreach ($targetRows as $r) {
|
||||
$statusInt = (int) ($r['opd_status'] ?? 0);
|
||||
$statusText = 'waiting';
|
||||
if ($statusInt === 0) {
|
||||
|
|
@ -245,6 +256,7 @@ class DoctorApi extends BaseController
|
|||
return $this->response->setJSON([
|
||||
'status' => 1,
|
||||
'doctor_id' => $docId,
|
||||
'date_mode' => $dateMode,
|
||||
'counts' => $counts,
|
||||
'appointments' => $appointments,
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -284,10 +284,15 @@
|
|||
|
||||
function DoctorApp() {
|
||||
const [doctors, setDoctors] = useState([]);
|
||||
const [ipdPatients, setIpdPatients] = useState([]);
|
||||
const [myIpdPatients, setMyIpdPatients] = useState([]);
|
||||
const [allIpdPatients, setAllIpdPatients] = useState([]);
|
||||
const [ipdViewMode, setIpdViewMode] = useState('mine'); // 'mine' or 'all'
|
||||
|
||||
const [opdAppointments, setOpdAppointments] = useState([]);
|
||||
const [opdCounts, setOpdCounts] = useState({ all: 0, waiting: 0, visited: 0, booked: 0, cancelled: 0 });
|
||||
const [opdFilter, setOpdFilter] = useState('all');
|
||||
const [opdDateMode, setOpdDateMode] = useState('today'); // 'today' or 'all'
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [mainTab, setMainTab] = useState('opd');
|
||||
|
|
@ -362,7 +367,8 @@
|
|||
const res = await fetch(`/api/v1/doctor/ipd/list/${docId || 0}`);
|
||||
const json = await res.json();
|
||||
if (json.status === 1) {
|
||||
setIpdPatients(json.patients || []);
|
||||
setAllIpdPatients(json.all_patients || json.patients || []);
|
||||
setMyIpdPatients(json.patients || []);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to load IPD patients', err);
|
||||
|
|
@ -371,9 +377,10 @@
|
|||
}
|
||||
};
|
||||
|
||||
const fetchOpdAppointments = async (docId) => {
|
||||
const fetchOpdAppointments = async (docId, dateModeOverride) => {
|
||||
try {
|
||||
const res = await fetch(`/api/v1/doctor/opd/list/${docId || 0}`);
|
||||
const mode = dateModeOverride || opdDateMode;
|
||||
const res = await fetch(`/api/v1/doctor/opd/list/${docId || 0}?date_mode=${mode}`);
|
||||
const json = await res.json();
|
||||
if (json.status === 1) {
|
||||
setOpdAppointments(json.appointments || []);
|
||||
|
|
@ -394,7 +401,7 @@
|
|||
if (parsed && parsed.doctor && parsed.expires_at > Date.now()) {
|
||||
setAuthenticatedDoctor(parsed.doctor);
|
||||
fetchIpdPatients(parsed.doctor.id);
|
||||
fetchOpdAppointments(parsed.doctor.id);
|
||||
fetchOpdAppointments(parsed.doctor.id, opdDateMode);
|
||||
} else {
|
||||
localStorage.removeItem(DOC_DEVICE_AUTH_KEY);
|
||||
}
|
||||
|
|
@ -447,7 +454,7 @@
|
|||
localStorage.setItem(DOC_DEVICE_AUTH_KEY, JSON.stringify(deviceAuthObj));
|
||||
setPinInput('');
|
||||
fetchIpdPatients(json.doctor.id);
|
||||
fetchOpdAppointments(json.doctor.id);
|
||||
fetchOpdAppointments(json.doctor.id, opdDateMode);
|
||||
} else if (json.status === 2) {
|
||||
setIsSettingNewPin(true);
|
||||
} else {
|
||||
|
|
@ -605,7 +612,7 @@
|
|||
if (json.status === 1) {
|
||||
alert(json.message || 'Prescription saved & OPD Visit completed!');
|
||||
setActiveOpd(null);
|
||||
fetchOpdAppointments(authenticatedDoctor ? authenticatedDoctor.id : 0);
|
||||
fetchOpdAppointments(authenticatedDoctor ? authenticatedDoctor.id : 0, opdDateMode);
|
||||
} else {
|
||||
alert(json.message || 'Failed to save prescription');
|
||||
}
|
||||
|
|
@ -648,7 +655,8 @@
|
|||
return matchesFilter && matchesSearch;
|
||||
});
|
||||
|
||||
const filteredIpdPatients = ipdPatients.filter(pat => {
|
||||
const activeIpdSource = (ipdViewMode === 'mine') ? myIpdPatients : allIpdPatients;
|
||||
const filteredIpdPatients = activeIpdSource.filter(pat => {
|
||||
const q = searchQuery.toLowerCase();
|
||||
const name = (pat.patient_display_name || pat.fname || '').toLowerCase();
|
||||
const uhid = (pat.uhid || '').toLowerCase();
|
||||
|
|
@ -773,7 +781,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div className="d-flex align-items-center gap-2">
|
||||
<button className="btn btn-sm btn-outline-light rounded-circle p-2" style={{width: '36px', height: '36px'}} onClick={() => { fetchOpdAppointments(authenticatedDoctor.id); fetchIpdPatients(authenticatedDoctor.id); }} title="Refresh Queue">
|
||||
<button className="btn btn-sm btn-outline-light rounded-circle p-2" style={{width: '36px', height: '36px'}} onClick={() => { fetchOpdAppointments(authenticatedDoctor.id, opdDateMode); fetchIpdPatients(authenticatedDoctor.id); }} title="Refresh Queue">
|
||||
<i className="bi bi-arrow-clockwise"></i>
|
||||
</button>
|
||||
<button className="btn btn-sm btn-danger px-3 py-1 fw-bold rounded-pill" onClick={handleLogout} title="Lock Device Access">
|
||||
|
|
@ -792,12 +800,33 @@
|
|||
<i className="bi bi-calendar-check-fill me-2"></i> OPD Appointments ({opdAppointments.length})
|
||||
</button>
|
||||
<button className={`segmented-tab ${mainTab === 'ipd' ? 'active' : ''}`} onClick={() => setMainTab('ipd')}>
|
||||
<i className="bi bi-hospital-fill me-2"></i> Admitted IPDs ({ipdPatients.length})
|
||||
<i className="bi bi-hospital-fill me-2"></i> Admitted IPDs ({activeIpdSource.length})
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{mainTab === 'opd' ? (
|
||||
<div>
|
||||
{/* Date Filter Segmented Bar (Current Date vs All Dates) */}
|
||||
<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(authenticatedDoctor.id, 'today'); }}
|
||||
>
|
||||
<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(authenticatedDoctor.id, 'all'); }}
|
||||
>
|
||||
<i className="bi bi-calendar3 me-1"></i> All Dates ({opdCounts.all_total || 0})
|
||||
</button>
|
||||
</div>
|
||||
<span className="badge bg-primary-subtle text-primary border border-primary-subtle px-2 py-1 rounded-pill small" style={{fontSize: '11px'}}>
|
||||
{opdDateMode === 'today' ? '● Current Date Only' : '● All Appointments'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Status Filter Pills Bar */}
|
||||
<div className="d-flex gap-2 mb-3 overflow-auto no-scrollbar pb-1">
|
||||
<button
|
||||
|
|
@ -848,7 +877,9 @@
|
|||
{filteredOpdAppointments.length === 0 ? (
|
||||
<div className="text-center text-muted py-5 bg-white rounded-4 border shadow-sm">
|
||||
<i className="bi bi-person-x fs-1 text-secondary opacity-50 d-block mb-2"></i>
|
||||
No OPD appointments found for selected filter.
|
||||
{opdDateMode === 'today'
|
||||
? "No OPD appointments registered for today's date."
|
||||
: "No OPD appointments found for selected filter."}
|
||||
</div>
|
||||
) : (
|
||||
filteredOpdAppointments.map(app => {
|
||||
|
|
@ -969,9 +1000,30 @@
|
|||
</div>
|
||||
) : (
|
||||
/* -------------------------------------------------------------
|
||||
ADMITTED IPD PATIENTS LIST (ASSIGNED TO DOCTOR)
|
||||
ADMITTED IPD PATIENTS LIST (ASSIGNED TO DOCTOR / ALL ADMISSIONS)
|
||||
------------------------------------------------------------- */
|
||||
<div>
|
||||
{/* IPD Admission View Mode Segmented Bar */}
|
||||
<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 ${ipdViewMode === 'mine' ? 'btn-success text-white shadow-sm' : 'btn-light text-secondary'}`}
|
||||
onClick={() => setIpdViewMode('mine')}
|
||||
>
|
||||
<i className="bi bi-person-check-fill me-1"></i> My Assigned Patients ({myIpdPatients.length})
|
||||
</button>
|
||||
<button
|
||||
className={`btn btn-sm rounded-pill px-3 py-1 fw-bold ${ipdViewMode === 'all' ? 'btn-success text-white shadow-sm' : 'btn-light text-secondary'}`}
|
||||
onClick={() => setIpdViewMode('all')}
|
||||
>
|
||||
<i className="bi bi-hospital-fill me-1"></i> All Hospital Admissions ({allIpdPatients.length})
|
||||
</button>
|
||||
</div>
|
||||
<span className="badge bg-success-subtle text-success border border-success-subtle px-2 py-1 rounded-pill small" style={{fontSize: '11px'}}>
|
||||
{ipdViewMode === 'mine' ? '● Doctor Assignments' : '● Hospital Wide IPDs'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Search Input Bar */}
|
||||
<div className="input-group mb-3 shadow-sm rounded-pill overflow-hidden bg-white border">
|
||||
<span className="input-group-text bg-white border-0 ps-3 text-secondary"><i className="bi bi-search"></i></span>
|
||||
|
|
@ -1020,6 +1072,9 @@
|
|||
<span className="badge bg-rose-50 text-danger border border-danger-subtle px-2 py-1" style={{fontSize: '11px'}}>
|
||||
<i className="bi bi-card-text me-1"></i>{pat.uhid}
|
||||
</span>
|
||||
<span className="badge bg-light text-dark border px-2 py-1" style={{fontSize: '11px'}}>
|
||||
<i className="bi bi-person-badge me-1"></i>{pat.assigned_doctors || pat.r_doc_name || `Dr. ${authenticatedDoctor.name}`}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user