Skip to content

Commit bc06b81

Browse files
authored
Merge pull request #11 from kpcode11/feat-recep
Feat recep
2 parents 4a518f0 + 41c5324 commit bc06b81

9 files changed

Lines changed: 619 additions & 113 deletions

File tree

prisma/seed-rbac.ts

Lines changed: 113 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,14 @@ async function main() {
9999
'billing.claim.manage',
100100
'billing.discount.approve',
101101

102+
// Appointment Module (3)
103+
'appointment.create',
104+
'appointment.read',
105+
'appointment.update',
106+
107+
// Doctor Module (1)
108+
'doctor.read',
109+
102110
// Beds/Inventory Module (3)
103111
'beds.status.read',
104112
'beds.assign.manage',
@@ -149,6 +157,10 @@ async function main() {
149157
RECEPTIONIST: [
150158
// Patient registration & management
151159
'patient.create', 'patient.read', 'patient.update', 'patient.history.read',
160+
// Appointment scheduling
161+
'appointment.create', 'appointment.read', 'appointment.update',
162+
// Doctor information for scheduling
163+
'doctor.read',
152164
// Bed management
153165
'beds.status.read', 'beds.assign.manage',
154166
// View own permissions
@@ -322,45 +334,134 @@ async function main() {
322334
}
323335

324336
const adminEmail = envAdminEmail ?? 'admin@neon.example';
325-
const adminPassword = envAdminPassword ?? 'Admin123!';
337+
const adminPassword = envAdminPassword;
338+
339+
if (!adminPassword) {
340+
throw new Error('RBAC_ADMIN_PASSWORD environment variable must be set before seeding');
341+
}
342+
326343
const hashed = await bcrypt.hash(adminPassword, 10);
327344

328345
const adminUser = await prisma.user.upsert({
329346
where: { email: adminEmail },
330-
update: { password: hashed, role: 'ADMIN', roleEntityId: roleMap.ADMINISTRATOR.id },
347+
update: { password: hashed, role: 'ADMIN' as unknown as any, roleEntityId: roleMap.ADMINISTRATOR.id },
331348
create: {
332349
email: adminEmail,
333350
password: hashed,
334351
name: 'CureOS Administrator',
335-
role: 'ADMIN',
352+
role: 'ADMIN' as unknown as any,
336353
roleEntityId: roleMap.ADMINISTRATOR.id,
337354
},
338355
});
339356

340357
console.log(`✅ Created/updated admin user: ${adminUser.email}\n`);
341358

359+
// ========== CREATE TEST USERS FOR EACH ROLE ==========
360+
const testUsers = [
361+
{ email: 'keshav@example.com', name: 'Keshav Sharma', role: 'RECEPTIONIST' },
362+
{ email: 'doctor@example.com', name: 'Dr. John Doe', role: 'DOCTOR' },
363+
{ email: 'nurse@example.com', name: 'Jane Smith', role: 'NURSE' },
364+
{ email: 'pharmacist@example.com', name: 'Alex Johnson', role: 'PHARMACIST' },
365+
{ email: 'labtech@example.com', name: 'Rita Patel', role: 'LAB_TECH' },
366+
];
367+
368+
const testPassword = process.env.RBAC_TEST_PASSWORD;
369+
370+
if (!testPassword) {
371+
throw new Error('RBAC_TEST_PASSWORD environment variable must be set before seeding');
372+
}
373+
374+
const testHashedPassword = await bcrypt.hash(testPassword, 10);
375+
376+
console.log('📝 Creating/Updating test users with roles...');
377+
for (const testUser of testUsers) {
378+
const role = roleMap[testUser.role];
379+
if (!role) {
380+
console.warn(`⚠️ Role ${testUser.role} not found, skipping ${testUser.email}`);
381+
continue;
382+
}
383+
384+
await prisma.user.upsert({
385+
where: { email: testUser.email },
386+
update: {
387+
password: testHashedPassword,
388+
role: testUser.role as unknown as any,
389+
roleEntityId: role.id
390+
},
391+
create: {
392+
email: testUser.email,
393+
password: testHashedPassword,
394+
name: testUser.name,
395+
role: testUser.role as unknown as any,
396+
roleEntityId: role.id,
397+
},
398+
});
399+
console.log(` ✓ ${testUser.email} (${testUser.role})`);
400+
}
401+
console.log('');
402+
403+
// ========== CREATE DUMMY DOCTORS ==========
404+
console.log('📋 Creating dummy doctors...');
405+
// Create dummy doctors with separate user accounts
406+
const dummyDoctors = [
407+
{ email: 'cardio-doc@hospital.com', name: 'Dr. John Doe', specialization: 'Cardiology', license: 'LIC001' },
408+
{ email: 'derm-doc@hospital.com', name: 'Dr. Sarah Johnson', specialization: 'Dermatology', license: 'LIC002' },
409+
{ email: 'neuro-doc@hospital.com', name: 'Dr. Michael Chen', specialization: 'Neurology', license: 'LIC003' },
410+
{ email: 'ortho-doc@hospital.com', name: 'Dr. Emily Davis', specialization: 'Orthopedics', license: 'LIC004' },
411+
{ email: 'peds-doc@hospital.com', name: 'Dr. Robert Wilson', specialization: 'Pediatrics', license: 'LIC005' },
412+
];
413+
414+
const doctorRole = await prisma.roleEntity.findFirst({
415+
where: { name: 'DOCTOR' },
416+
});
417+
418+
for (const docData of dummyDoctors) {
419+
// Create or update doctor user
420+
const doctorUser = await prisma.user.upsert({
421+
where: { email: docData.email },
422+
update: { name: docData.name },
423+
create: {
424+
email: docData.email,
425+
name: docData.name,
426+
password: await bcrypt.hash(process.env.RBAC_DOCTOR_PASSWORD || 'temp', 10),
427+
role: 'DOCTOR' as unknown as any,
428+
roleEntityId: doctorRole?.id,
429+
},
430+
});
431+
432+
// Create or update doctor record
433+
await prisma.doctor.upsert({
434+
where: { licenseNumber: docData.license },
435+
update: { specialization: docData.specialization },
436+
create: {
437+
specialization: docData.specialization,
438+
licenseNumber: docData.license,
439+
userId: doctorUser.id,
440+
},
441+
});
442+
443+
console.log(` ✓ ${docData.name} (${docData.specialization})`);
444+
}
445+
console.log('');
446+
342447
// ========== SEED COMPLETE - SUMMARY ==========
343448
console.log('╔════════════════════════════════════════════════════════════╗');
344449
console.log('║ RBAC SEEDING COMPLETE - HOSPITAL SYSTEM ║');
345450
console.log('╠════════════════════════════════════════════════════════════╣');
346451
console.log(`║ Permissions: ${allPermissions.length}`.padEnd(62) + '║');
347452
console.log(`║ Roles: ${Object.keys(roleMap).length} (Admin, Doctor, Nurse, Pharmacist, Lab Tech, ...)`.padEnd(62) + '║');
348-
console.log(`║ Users: 1 Administrator`.padEnd(62) + '║');
453+
console.log(`║ Users: 6 (1 Admin + 5 Test Users)`.padEnd(62) + '║');
349454
console.log('╠════════════════════════════════════════════════════════════╣');
350455
console.log('║ Role Summary:'.padEnd(62) + '║');
351456
for (const [roleName, perms] of Object.entries(rolePermissions)) {
352457
const summary = `${roleName}: ${perms.length} permissions`;
353458
console.log(`║ ${summary}`.padEnd(62) + '║');
354459
}
355460
console.log('╠════════════════════════════════════════════════════════════╣');
356-
if (!isProdLike) {
357-
console.log(`║ 🔐 Admin Credentials: ║`);
358-
console.log(`║ Email: ${adminEmail}`.padEnd(62) + '║');
359-
console.log(`║ Password: ${adminPassword}`.padEnd(62) + '║');
360-
console.log('║ ⚠️ CHANGE credentials before production deployment ║');
361-
} else {
362-
console.log('║ ✓ Production mode - admin password not displayed ║');
363-
}
461+
console.log('║ ⚠️ IMPORTANT: Set these environment variables before seeding:║');
462+
console.log('║ - RBAC_ADMIN_PASSWORD ║');
463+
console.log('║ - RBAC_TEST_PASSWORD ║');
464+
console.log('║ - RBAC_DOCTOR_PASSWORD ║');
364465
console.log('╚════════════════════════════════════════════════════════════╝\n');
365466
}
366467

src/app/(dashboard)/receptionist/appointments/page.tsx

Lines changed: 76 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ export default function AppointmentBooking() {
8080
const [patients, setPatients] = useState<Patient[]>([]);
8181
const [doctors, setDoctors] = useState<Doctor[]>([]);
8282
const [appointments, setAppointments] = useState<Appointment[]>([]);
83+
const [selectedPatient, setSelectedPatient] = useState<Patient | null>(null);
84+
const [selectedDoctor, setSelectedDoctor] = useState<Doctor | null>(null);
8385
const [loadingPatients, setLoadingPatients] = useState(true);
8486
const [loadingDoctors, setLoadingDoctors] = useState(true);
8587
const [loadingAppointments, setLoadingAppointments] = useState(true);
@@ -97,7 +99,7 @@ export default function AppointmentBooking() {
9799
useEffect(() => {
98100
fetchPatients();
99101
fetchDoctors();
100-
loadMockAppointments();
102+
fetchAppointments();
101103
}, []);
102104

103105
const loadMockAppointments = () => {
@@ -249,15 +251,49 @@ export default function AppointmentBooking() {
249251

250252
const handleChange = (field: string, value: string) => {
251253
setFormData((prev) => ({ ...prev, [field]: value }));
254+
255+
// Update selectedPatient when patientId changes
256+
if (field === "patientId") {
257+
const patient = patients.find((p) => p.id === value);
258+
setSelectedPatient(patient || null);
259+
}
260+
261+
// Update selectedDoctor when doctorId changes
262+
if (field === "doctorId") {
263+
const doctor = doctors.find((d) => d.id === value);
264+
setSelectedDoctor(doctor || null);
265+
}
252266
};
253267

254268
const handleSubmit = async (e: React.FormEvent) => {
255269
e.preventDefault();
256270
setIsSubmitting(true);
257271

258272
try {
273+
// Validate form data
274+
if (!formData.patientId || !formData.doctorId || !formData.date || !formData.time) {
275+
toast({
276+
title: "Validation Error",
277+
description: "Please fill in all required fields (Patient, Doctor, Date, Time)",
278+
variant: "destructive",
279+
});
280+
setIsSubmitting(false);
281+
return;
282+
}
283+
259284
const dateTime = new Date(`${formData.date}T${formData.time}`);
260285

286+
// Check if date is in the past
287+
if (dateTime < new Date()) {
288+
toast({
289+
title: "Invalid Date",
290+
description: "Cannot book appointment in the past",
291+
variant: "destructive",
292+
});
293+
setIsSubmitting(false);
294+
return;
295+
}
296+
261297
const response = await fetch("/api/appointments", {
262298
method: "POST",
263299
headers: {
@@ -273,31 +309,62 @@ export default function AppointmentBooking() {
273309
});
274310

275311
if (!response.ok) {
276-
throw new Error("Failed to book appointment");
312+
const errorData = await response.json();
313+
const errorMsg = errorData.error || "Failed to book appointment";
314+
throw new Error(errorMsg);
277315
}
278316

279317
const appointment = await response.json();
280318
setBookedAppointment(appointment);
281319

282320
// Refresh appointments list
283-
// fetchAppointments();
321+
fetchAppointments();
284322

285323
toast({
286-
title: "Appointment Booked Successfully!",
287-
description: `Appointment scheduled for ${new Date(appointment.dateTime).toLocaleString()}`,
324+
title: "Appointment Booked Successfully!",
325+
description: `Appointment scheduled for ${new Date(appointment.dateTime).toLocaleString()} with ${appointment.doctor.user.name}`,
288326
});
289-
} catch (error) {
327+
328+
// Reset form
329+
handleReset();
330+
331+
// Switch to list tab to show new appointment
332+
setTimeout(() => setActiveTab("list"), 500);
333+
} catch (error: any) {
334+
const errorMsg = error?.message || "Unknown error occurred";
290335
toast({
291-
title: "Booking Failed",
292-
description:
293-
"There was an error booking the appointment. Please try again.",
336+
title: "❌ Booking Failed",
337+
description: errorMsg,
294338
variant: "destructive",
295339
});
340+
console.error("Appointment booking error:", error);
296341
} finally {
297342
setIsSubmitting(false);
298343
}
299344
};
300345

346+
const fetchAppointments = async () => {
347+
try {
348+
setLoadingAppointments(true);
349+
const response = await fetch("/api/appointments");
350+
if (response.ok) {
351+
const data = await response.json();
352+
setAppointments(data);
353+
console.log("Fetched appointments:", data);
354+
} else {
355+
console.error("Failed to fetch appointments:", response.status);
356+
// Fall back to mock appointments if API fails
357+
loadMockAppointments();
358+
}
359+
} catch (error) {
360+
console.error("Failed to fetch appointments:", error);
361+
// Fall back to mock appointments if API fails
362+
loadMockAppointments();
363+
} finally {
364+
setLoadingAppointments(false);
365+
}
366+
};
367+
301368
const handleReset = () => {
302369
setBookedAppointment(null);
303370
setFormData({

0 commit comments

Comments
 (0)