Skip to content

Commit 44d02f3

Browse files
authored
Merge pull request #79 from genzz-dev/refactor/PatientAppointments-page-ui
Refactor/patient appointments page UI
2 parents f9f373b + 414737e commit 44d02f3

5 files changed

Lines changed: 158 additions & 253 deletions

File tree

client/src/components/Patient/PatientAppointments/AppointmentCard.jsx

Lines changed: 82 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -29,130 +29,121 @@ const AppointmentCard = ({ appointment, prescriptionStatus, onAppointmentClick }
2929
const getStatusBadge = (status) => {
3030
const statusConfig = {
3131
pending: {
32-
color: 'bg-yellow-100 text-yellow-800 border-yellow-200',
33-
icon: <Clock3 className="w-3 h-3" />,
34-
text: 'Pending',
32+
color: 'bg-blue-50 text-blue-700 border-blue-100',
33+
icon: <Clock className="w-3 h-3" strokeWidth={2} />,
34+
label: 'Pending',
3535
},
3636
confirmed: {
37-
color: 'bg-blue-100 text-blue-800 border-blue-200',
38-
icon: <CheckCircle className="w-3 h-3" />,
39-
text: 'Confirmed',
37+
color: 'bg-blue-600 text-white border-blue-600',
38+
icon: <CheckCircle className="w-3 h-3" strokeWidth={2} />,
39+
label: 'Confirmed',
4040
},
4141
completed: {
42-
color: 'bg-green-100 text-green-800 border-green-200',
43-
icon: <CheckCircle className="w-3 h-3" />,
44-
text: 'Completed',
42+
color: 'bg-gray-100 text-black border-gray-200',
43+
icon: <CheckCircle className="w-3 h-3" strokeWidth={2} />,
44+
label: 'Completed',
4545
},
4646
cancelled: {
47-
color: 'bg-red-100 text-red-800 border-red-200',
48-
icon: <XCircle className="w-3 h-3" />,
49-
text: 'Cancelled',
50-
},
51-
'no-show': {
52-
color: 'bg-gray-100 text-gray-800 border-gray-200',
53-
icon: <AlertCircle className="w-3 h-3" />,
54-
text: 'No Show',
47+
color: 'bg-gray-100 text-gray-600 border-gray-200',
48+
icon: <XCircle className="w-3 h-3" strokeWidth={2} />,
49+
label: 'Cancelled',
5550
},
5651
};
5752

5853
const config = statusConfig[status] || statusConfig.pending;
59-
6054
return (
6155
<span
62-
className={`inline-flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium border ${config.color}`}
56+
className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-light border ${config.color}`}
6357
>
6458
{config.icon}
65-
{config.text}
59+
{config.label}
6660
</span>
6761
);
6862
};
6963

70-
const { date: formattedDate, time: formattedTime } = formatDateTime(
71-
appointment.date,
72-
appointment.startTime
73-
);
64+
const { date, time } = formatDateTime(appointment.date, appointment.startTime);
7465
const hasPrescription = prescriptionStatus[appointment._id];
7566

7667
return (
77-
<div
78-
onClick={() => onAppointmentClick(appointment._id)}
79-
className="bg-white rounded-xl shadow-sm border border-gray-100 p-6 hover:shadow-md hover:border-blue-200 transition-all duration-200 cursor-pointer group"
80-
>
81-
{/* Header */}
82-
<div className="flex items-start justify-between mb-4">
83-
<div className="flex items-center gap-3">
84-
<div className="w-12 h-12 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-full flex items-center justify-center text-white font-semibold">
85-
{appointment.doctorId?.firstName?.[0]}
86-
{appointment.doctorId?.lastName?.[0]}
87-
</div>
88-
<div>
89-
<h3 className="font-semibold text-gray-900 group-hover:text-blue-600 transition-colors">
90-
Dr. {appointment.doctorId?.firstName} {appointment.doctorId?.lastName}
91-
</h3>
92-
<p className="text-sm text-gray-600">{appointment.doctorId?.specialization}</p>
93-
</div>
94-
</div>
95-
{getStatusBadge(appointment.status)}
96-
</div>
68+
<div className="bg-white border border-gray-200 rounded-lg hover:shadow-md transition-all duration-200">
69+
<div className="p-4 sm:p-5">
70+
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-3 mb-4">
71+
<div className="flex items-start gap-3 flex-1 min-w-0">
72+
<div className="w-12 h-12 rounded-full bg-gray-100 flex items-center justify-center flex-shrink-0 overflow-hidden border border-gray-200">
73+
{appointment.doctorId?.profilePicture ? (
74+
<img
75+
src={appointment.doctorId.profilePicture}
76+
alt={`${appointment.doctorId?.firstName} ${appointment.doctorId?.lastName}`}
77+
className="w-full h-full object-cover"
78+
/>
79+
) : (
80+
<User className="w-6 h-6 text-gray-400" strokeWidth={1.5} />
81+
)}
82+
</div>
9783

98-
{/* Appointment Details */}
99-
<div className="space-y-3">
100-
{/* Date and Time */}
101-
<div className="flex items-center gap-4">
102-
<div className="flex items-center gap-2 text-gray-700">
103-
<Calendar className="w-4 h-4" />
104-
<span className="text-sm">{formattedDate}</span>
105-
</div>
106-
<div className="flex items-center gap-2 text-gray-700">
107-
<Clock className="w-4 h-4" />
108-
<span className="text-sm">{formattedTime}</span>
84+
<div className="flex-1 min-w-0">
85+
<h3 className="text-base font-normal text-black mb-0.5 truncate">
86+
Dr. {appointment.doctorId?.firstName} {appointment.doctorId?.lastName}
87+
</h3>
88+
<p className="text-xs text-gray-500 font-light mb-2">
89+
{appointment.doctorId?.specialization}
90+
</p>
91+
<div className="flex items-center gap-1.5 text-xs text-gray-600">
92+
<AlertCircle className="w-3.5 h-3.5 flex-shrink-0" strokeWidth={1.5} />
93+
<span className="font-light truncate">{appointment.reason}</span>
94+
</div>
95+
</div>
10996
</div>
110-
</div>
11197

112-
{/* Clinic */}
113-
<div className="flex items-center gap-2 text-gray-700">
114-
<MapPin className="w-4 h-4 flex-shrink-0" />
115-
<span className="text-sm truncate">{appointment.clinicId?.name}</span>
98+
<div className="flex sm:flex-col items-start gap-2">
99+
{getStatusBadge(appointment.status)}
100+
</div>
116101
</div>
117102

118-
{/* Consultation Type */}
119-
<div className="flex items-center gap-2">
120-
{appointment.isTeleconsultation ? (
121-
<>
122-
<Video className="w-4 h-4 text-green-600" />
123-
<span className="text-sm text-green-600 font-medium">Video Consultation</span>
124-
</>
125-
) : (
126-
<>
127-
<User className="w-4 h-4 text-blue-600" />
128-
<span className="text-sm text-blue-600 font-medium">In-Person Visit</span>
129-
</>
130-
)}
131-
</div>
103+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 mb-4 pt-4 border-t border-gray-100">
104+
<div className="flex items-center gap-2 text-sm">
105+
<Calendar className="w-4 h-4 text-gray-400 flex-shrink-0" strokeWidth={1.5} />
106+
<span className="text-gray-600 font-light">{date}</span>
107+
</div>
132108

133-
{/* Reason */}
134-
{appointment.reason && (
135-
<div className="bg-gray-50 rounded-lg p-3">
136-
<p className="text-sm text-gray-700">
137-
<span className="font-medium">Reason: </span>
138-
{appointment.reason}
139-
</p>
109+
<div className="flex items-center gap-2 text-sm">
110+
<Clock3 className="w-4 h-4 text-gray-400 flex-shrink-0" strokeWidth={1.5} />
111+
<span className="text-gray-600 font-light">
112+
{time} - {format(new Date(`2000-01-01T${appointment.endTime}`), 'hh:mm a')}
113+
</span>
140114
</div>
141-
)}
142115

143-
{/* Prescription Status */}
144-
<div className="flex items-center justify-between pt-3 border-t border-gray-100">
145-
<div className="flex items-center gap-2">
146-
<FileText className="w-4 h-4" />
147-
<span className="text-sm text-gray-600">Prescription:</span>
148-
{hasPrescription ? (
149-
<span className="text-sm text-green-600 font-medium">Available</span>
116+
<div className="flex items-center gap-2 text-sm">
117+
{appointment.appointmentType === 'virtual' ? (
118+
<>
119+
<Video className="w-4 h-4 text-blue-600 flex-shrink-0" strokeWidth={1.5} />
120+
<span className="text-gray-600 font-light">Virtual</span>
121+
</>
150122
) : (
151-
<span className="text-sm text-gray-400">Not available</span>
123+
<>
124+
<MapPin className="w-4 h-4 text-gray-400 flex-shrink-0" strokeWidth={1.5} />
125+
<span className="text-gray-600 font-light truncate">
126+
{appointment.clinicId?.name || 'In-Person'}
127+
</span>
128+
</>
152129
)}
153130
</div>
154-
<Eye className="w-4 h-4 text-gray-400 group-hover:text-blue-600 transition-colors" />
131+
132+
{hasPrescription && (
133+
<div className="flex items-center gap-2 text-sm">
134+
<FileText className="w-4 h-4 text-blue-600 flex-shrink-0" strokeWidth={1.5} />
135+
<span className="text-blue-600 font-light">Prescription Available</span>
136+
</div>
137+
)}
155138
</div>
139+
140+
<button
141+
onClick={() => onAppointmentClick(appointment._id)}
142+
className="w-full py-2.5 px-4 text-sm font-light text-blue-600 border border-blue-600 rounded-lg hover:bg-blue-50 transition-all flex items-center justify-center gap-2"
143+
>
144+
<Eye className="w-4 h-4" strokeWidth={1.5} />
145+
View Details
146+
</button>
156147
</div>
157148
</div>
158149
);

client/src/components/Patient/PatientAppointments/AppointmentHeader.jsx

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,41 @@ import { Search, Filter } from 'lucide-react';
22

33
const AppointmentHeader = ({ searchTerm, setSearchTerm, filterStatus, setFilterStatus }) => {
44
return (
5-
<div className="bg-white rounded-xl shadow-sm border border-gray-100 p-6 mb-8">
6-
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4">
7-
<div>
8-
<h1 className="text-2xl font-bold text-gray-900 mb-2">My Appointments</h1>
9-
<p className="text-gray-600">Manage and view your medical appointments</p>
10-
</div>
5+
<div className="mb-8">
6+
<div className="mb-6">
7+
<h1 className="text-2xl sm:text-3xl font-light text-black tracking-tight">
8+
My Appointments
9+
</h1>
10+
<p className="mt-1 text-sm text-gray-500 font-light">
11+
Manage and view your medical appointments
12+
</p>
13+
</div>
1114

12-
{/* Search and Filter */}
13-
<div className="flex flex-col sm:flex-row gap-3 lg:w-96">
14-
<div className="relative flex-1">
15-
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
16-
<input
17-
type="text"
18-
placeholder="Search by doctor, clinic, or reason..."
19-
value={searchTerm}
20-
onChange={(e) => setSearchTerm(e.target.value)}
21-
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
22-
/>
23-
</div>
15+
<div className="flex flex-col sm:flex-row gap-3">
16+
<div className="flex-1 relative">
17+
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
18+
<input
19+
type="text"
20+
placeholder="Search by doctor, clinic, or reason..."
21+
value={searchTerm}
22+
onChange={(e) => setSearchTerm(e.target.value)}
23+
className="w-full pl-10 pr-4 py-2.5 text-sm border border-gray-200 rounded-lg focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 transition-all"
24+
/>
25+
</div>
2426

25-
<div className="relative">
26-
<Filter className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
27-
<select
28-
value={filterStatus}
29-
onChange={(e) => setFilterStatus(e.target.value)}
30-
className="pl-10 pr-8 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent appearance-none bg-white"
31-
>
32-
<option value="all">All Status</option>
33-
<option value="pending">Pending</option>
34-
<option value="confirmed">Confirmed</option>
35-
<option value="completed">Completed</option>
36-
<option value="cancelled">Cancelled</option>
37-
</select>
38-
</div>
27+
<div className="relative sm:w-48">
28+
<Filter className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4 pointer-events-none" />
29+
<select
30+
value={filterStatus}
31+
onChange={(e) => setFilterStatus(e.target.value)}
32+
className="w-full pl-10 pr-4 py-2.5 text-sm border border-gray-200 rounded-lg focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 appearance-none bg-white cursor-pointer transition-all"
33+
>
34+
<option value="all">All Status</option>
35+
<option value="pending">Pending</option>
36+
<option value="confirmed">Confirmed</option>
37+
<option value="completed">Completed</option>
38+
<option value="cancelled">Cancelled</option>
39+
</select>
3940
</div>
4041
</div>
4142
</div>

client/src/components/Patient/PatientAppointments/AppointmentStats.jsx

Lines changed: 0 additions & 56 deletions
This file was deleted.

0 commit comments

Comments
 (0)