Skip to content

Commit 540676b

Browse files
committed
live queue updation & emergency case & doctor delay
1 parent 766bf92 commit 540676b

17 files changed

Lines changed: 605 additions & 74 deletions

File tree

api-gateway/src/app.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,13 @@ app.use(
3838
authenticate,
3939
(req: any, _res, next) => {
4040
if (req.user) {
41-
req.headers['x-user-id'] = req.user.sub;
42-
req.headers['x-user-role'] = req.user.role;
43-
req.headers['x-user-type'] = req.user.type;
41+
if (req.user.sub) req.headers['x-user-id'] = String(req.user.sub);
42+
43+
if (req.user.role) req.headers['x-user-role'] = String(req.user.role);
44+
45+
if (req.user.type) req.headers['x-user-type'] = String(req.user.type);
4446
}
47+
4548
next();
4649
},
4750
createProxyMiddleware({
Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
1+
'use client';
2+
3+
import MyQueueCard from '@/src/features/appointments/components/MyQueueCard';
4+
15
export default function DashboardHome() {
26
return (
3-
<div>
4-
<h1 className="text-2xl font-bold mb-4">
5-
Welcome to your Dashboard
6-
</h1>
7-
8-
<p>
9-
Overview content goes here.
10-
</p>
7+
<div className="space-y-8">
8+
9+
<div>
10+
<h1 className="text-2xl font-bold mb-2">
11+
Welcome to your Dashboard
12+
</h1>
13+
14+
<p className="text-gray-600">
15+
Track your appointment and queue status in real time.
16+
</p>
17+
</div>
18+
19+
{/* 🔥 Live Queue Card */}
20+
<MyQueueCard />
21+
1122
</div>
1223
);
1324
}
Lines changed: 115 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,59 @@
11
'use client';
22

3+
import { useState } from 'react';
34
import { useStaffAuth } from '../../../../staff/auth/staff.auth.provider';
45
import { useDoctorQueue } from '../../../../features/appointments/hooks/useDoctorQueue';
6+
import {
7+
useStartAppointment,
8+
useCompleteAppointment,
9+
useCheckInAppointment,
10+
} from '../../../../features/appointments/hooks/useStaffActions';
11+
import { useEmergency } from '../../../../features/appointments/hooks/useEmergency';
512

613
export default function QueuePage() {
714
const { auth } = useStaffAuth();
8-
915
const doctorId = auth.staff?.id;
1016

11-
// Wait until auth restoration finishes
12-
if (auth.isRestoring) {
13-
return <p>Loading authentication...</p>;
14-
}
17+
const [showEmergency, setShowEmergency] = useState(false);
18+
const [patientId, setPatientId] = useState('');
19+
20+
if (auth.isRestoring) return <p>Loading authentication...</p>;
21+
if (!doctorId) return <p>Doctor information not available.</p>;
1522

16-
if (!doctorId) {
17-
return <p>Doctor information not available.</p>;
18-
}
23+
const { data, isLoading } = useDoctorQueue(doctorId);
1924

20-
const { data, isLoading, isError } =
21-
useDoctorQueue(doctorId);
25+
const startMutation = useStartAppointment();
26+
const completeMutation = useCompleteAppointment();
27+
const checkInMutation = useCheckInAppointment();
28+
const emergencyMutation = useEmergency(doctorId);
2229

2330
if (isLoading) return <p>Loading queue...</p>;
24-
if (isError) return <p>Failed to load queue.</p>;
2531
if (!data) return <p>No queue found.</p>;
2632

2733
const { queue, doctor_status } = data;
34+
const someoneInProgress = queue.some((q: any) => q.status === 'IN_PROGRESS');
2835

2936
return (
3037
<div className="space-y-8">
3138

32-
<h1 className="text-2xl font-bold">
33-
Today's Queue
34-
</h1>
39+
{/* 🔴 Emergency Button */}
40+
<div className="flex justify-between items-center">
41+
<h1 className="text-2xl font-bold">Today's Queue</h1>
42+
43+
<button
44+
onClick={() => setShowEmergency(true)}
45+
className="bg-red-600 text-white px-4 py-2 rounded-lg shadow hover:bg-red-700 transition"
46+
>
47+
+ Emergency Case
48+
</button>
49+
</div>
3550

3651
{/* Doctor Status Panel */}
3752
<div className="bg-white p-6 rounded shadow space-y-2">
38-
<p>
39-
<strong>Total Appointments:</strong>{' '}
40-
{doctor_status.total_appointments}
41-
</p>
42-
<p>
43-
<strong>Checked In:</strong>{' '}
44-
{doctor_status.checked_in_count}
45-
</p>
46-
<p>
47-
<strong>Doctor Delay:</strong>{' '}
48-
{doctor_status.doctor_delay_minutes} min
49-
</p>
50-
<p>
51-
<strong>Remaining Queue:</strong>{' '}
52-
{doctor_status.remaining_queue_minutes} min
53-
</p>
53+
<p><strong>Total:</strong> {doctor_status.total_appointments}</p>
54+
<p><strong>Checked In:</strong> {doctor_status.checked_in_count}</p>
55+
<p><strong>Delay:</strong> {doctor_status.doctor_delay_minutes} min</p>
56+
<p><strong>Remaining:</strong> {doctor_status.remaining_queue_minutes} min</p>
5457
</div>
5558

5659
{/* Queue List */}
@@ -59,32 +62,97 @@ export default function QueuePage() {
5962
<div
6063
key={item.id}
6164
className={`p-4 rounded border shadow-sm ${
62-
item.is_current
63-
? 'bg-green-100 border-green-400'
64-
: item.priority === 'HIGH'
65+
item.priority === 'HIGH'
6566
? 'bg-red-50 border-red-300'
67+
: item.is_current
68+
? 'bg-green-100 border-green-400'
6669
: 'bg-white'
6770
}`}
6871
>
69-
<div className="flex justify-between items-center">
70-
<div>
71-
<p className="font-semibold">
72-
Patient ID: {item.patient_id}
73-
</p>
74-
<p>Status: {item.status}</p>
75-
<p>Position: {item.position}</p>
76-
<p>Delay: {item.delay_minutes} min</p>
77-
</div>
78-
79-
{item.priority === 'HIGH' && (
80-
<span className="px-3 py-1 text-sm bg-red-500 text-white rounded-full">
81-
HIGH
82-
</span>
72+
<p className="font-semibold">
73+
Patient ID: {item.patient_id}
74+
</p>
75+
<p>Status: {item.status}</p>
76+
<p>Position: {item.position}</p>
77+
78+
<div className="flex gap-2 mt-3">
79+
80+
{item.status === 'SCHEDULED' && (
81+
<button
82+
onClick={() => checkInMutation.mutate(item.id)}
83+
className="px-3 py-1 bg-yellow-500 text-white rounded text-sm"
84+
>
85+
Check In
86+
</button>
87+
)}
88+
89+
{item.status === 'CHECKED_IN' && !someoneInProgress && (
90+
<button
91+
onClick={() => startMutation.mutate(item.id)}
92+
className="px-3 py-1 bg-blue-600 text-white rounded text-sm"
93+
>
94+
Start
95+
</button>
8396
)}
97+
98+
{item.status === 'IN_PROGRESS' && (
99+
<button
100+
onClick={() => completeMutation.mutate(item.id)}
101+
className="px-3 py-1 bg-green-600 text-white rounded text-sm"
102+
>
103+
Complete
104+
</button>
105+
)}
106+
84107
</div>
85108
</div>
86109
))}
87110
</div>
111+
112+
{/* 🔴 Emergency Modal */}
113+
{showEmergency && (
114+
<div className="fixed inset-0 bg-black/40 flex items-center justify-center">
115+
116+
<div className="bg-white p-6 rounded-xl w-96 space-y-4 shadow-xl">
117+
118+
<h2 className="text-xl font-semibold text-red-600">
119+
Create Emergency Case
120+
</h2>
121+
122+
<input
123+
type="text"
124+
placeholder="Enter Patient ID"
125+
value={patientId}
126+
onChange={(e) => setPatientId(e.target.value)}
127+
className="w-full border rounded px-3 py-2"
128+
/>
129+
130+
<div className="flex justify-end gap-3">
131+
132+
<button
133+
onClick={() => setShowEmergency(false)}
134+
className="px-4 py-2 rounded border"
135+
>
136+
Cancel
137+
</button>
138+
139+
<button
140+
onClick={() => {
141+
emergencyMutation.mutate(patientId);
142+
setShowEmergency(false);
143+
setPatientId('');
144+
}}
145+
className="px-4 py-2 bg-red-600 text-white rounded"
146+
>
147+
Confirm Emergency
148+
</button>
149+
150+
</div>
151+
152+
</div>
153+
</div>
154+
)}
155+
88156
</div>
89157
);
90158
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { api } from '@/src/lib/api';
2+
3+
export async function createEmergency(data: {
4+
doctorId: string;
5+
patientId: string;
6+
}) {
7+
const res = await api.post('/appointments/emergency', data);
8+
return res.data;
9+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { api } from '@/src/lib/api';
2+
3+
export async function startAppointment(id: string) {
4+
const { data } = await api.post(`/appointments/start/${id}`);
5+
return data;
6+
}
7+
8+
export async function completeAppointment(id: string) {
9+
const { data } = await api.post(`/appointments/complete/${id}`);
10+
return data;
11+
}
12+
13+
export async function checkInAppointment(id: string) {
14+
const { data } = await api.post(`/appointments/check-in/${id}`);
15+
return data;
16+
}

frontend/src/features/appointments/components/AppointmentCard.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ interface Props {
99
}
1010

1111
export default function AppointmentCard({ appointment }: Props) {
12+
console.log(appointment);
1213
const cancelMutation = useCancelAppointment();
1314
const [showReschedule, setShowReschedule] = useState(false);
1415

@@ -27,7 +28,7 @@ export default function AppointmentCard({ appointment }: Props) {
2728
return (
2829
<div className="bg-white p-4 rounded-lg shadow space-y-2">
2930
<p>
30-
<strong>Doctor:</strong> {appointment.doctor_name}
31+
<strong>Doctor:</strong> {appointment.doctor_id}
3132
</p>
3233

3334
<p>
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
'use client';
2+
3+
import { useEffect, useState } from 'react';
4+
5+
interface Props {
6+
startTime: string;
7+
durationMinutes: number;
8+
}
9+
10+
export default function ConsultationTimer({
11+
startTime,
12+
durationMinutes,
13+
}: Props) {
14+
const [elapsedSeconds, setElapsedSeconds] = useState(0);
15+
16+
useEffect(() => {
17+
const start = new Date(startTime).getTime();
18+
19+
const interval = setInterval(() => {
20+
const now = Date.now();
21+
const diff = Math.max(0, Math.floor((now - start) / 1000));
22+
setElapsedSeconds(diff);
23+
}, 1000);
24+
25+
return () => clearInterval(interval);
26+
}, [startTime]);
27+
28+
const plannedSeconds = durationMinutes * 60;
29+
30+
const overtimeSeconds =
31+
elapsedSeconds > plannedSeconds
32+
? elapsedSeconds - plannedSeconds
33+
: 0;
34+
35+
const formatTime = (total: number) => {
36+
const mins = Math.floor(total / 60)
37+
.toString()
38+
.padStart(2, '0');
39+
const secs = (total % 60)
40+
.toString()
41+
.padStart(2, '0');
42+
return `${mins}:${secs}`;
43+
};
44+
45+
return (
46+
<div className="mt-3 p-3 rounded-lg border bg-gray-50 space-y-1">
47+
<p className="font-semibold">
48+
{formatTime(elapsedSeconds)} elapsed
49+
</p>
50+
51+
<p className="text-sm text-gray-600">
52+
Planned: {durationMinutes} min
53+
</p>
54+
55+
{overtimeSeconds > 0 && (
56+
<p className="text-red-600 font-medium">
57+
⚠ Running {Math.floor(overtimeSeconds / 60)} min late
58+
</p>
59+
)}
60+
</div>
61+
);
62+
}

0 commit comments

Comments
 (0)