diff --git a/lib/screens/doctor_screen/doctor_consultation_schedule/consultation_time_slot_screen.dart b/lib/screens/doctor_screen/doctor_consultation_schedule/consultation_time_slot_screen.dart index e3ba36e..2be85a1 100644 --- a/lib/screens/doctor_screen/doctor_consultation_schedule/consultation_time_slot_screen.dart +++ b/lib/screens/doctor_screen/doctor_consultation_schedule/consultation_time_slot_screen.dart @@ -43,45 +43,139 @@ class _ConsultationTimeSlotScreenState return newSchedule; }, ); - - // Ensure timeSlots is not null currentSchedule.timeSlots ??= []; - - // Debug print print( 'Initial schedule for ${widget.selectedDay}: ${currentSchedule.timeSlots}'); } String formatTime(TimeOfDay time) { - final hour = time.hourOfPeriod == 0 - ? 12 - : time.hourOfPeriod; // Convert 0 to 12 for AM + final hour = time.hourOfPeriod == 0 ? 12 : time.hourOfPeriod; final minute = time.minute.toString().padLeft(2, '0'); final period = time.period == DayPeriod.am ? 'AM' : 'PM'; return '$hour:$minute $period'; } - void _addTimeSlot() async { - TimeOfDay? startTime = await showTimePicker( - context: context, - initialTime: TimeOfDay.now(), - ); + void _validateTimeSlot(TimeOfDay startTime, TimeOfDay endTime) { + int startMins = startTime.hour * 60 + startTime.minute; + int endMins = endTime.hour * 60 + endTime.minute; + int duration = endMins - startMins; - if (startTime != null) { - TimeOfDay? endTime = await showTimePicker( + int minimumDuration = + int.parse(widget.controller.model.averageDurationMinutes ?? '0'); + if (duration < minimumDuration) { + throw 'Time slot must be atleast $minimumDuration minutes long'; + } + } + + void _validateOverlap(TimeOfDay startTime, TimeOfDay endTime, + [TimeSlot? excludeSlot]) { + int newStartMins = startTime.hour * 60 + startTime.minute; + int newEndMins = endTime.hour * 60 + endTime.minute; + for (var slot in currentSchedule.timeSlots ?? []) { + if (excludeSlot != null && + slot.startTime == excludeSlot.startTime && + slot.endTime == excludeSlot.endTime) { + continue; + } + var existingStart = slot.startTime!.split(' '); + var existingEnd = slot.endTime!.split(' '); + var startHourMin = existingStart[0].split(':'); + var endHourMin = existingEnd[0].split(':'); + int startHour = int.parse(startHourMin[0]); + if (existingStart[1] == 'PM' && startHour != 12) startHour += 12; + if (existingStart[1] == 'AM' && startHour == 12) startHour = 0; + int endHour = int.parse(endHourMin[0]); + if (existingEnd[1] == 'PM' && endHour != 12) endHour += 12; + if (existingEnd[1] == 'AM' && endHour == 12) endHour = 0; + int existingStartMins = startHour * 60 + int.parse(startHourMin[1]); + int existingEndMins = endHour * 60 + int.parse(endHourMin[1]); + if ((newStartMins >= existingStartMins && + newStartMins < existingEndMins) || + (newEndMins > existingStartMins && newEndMins <= existingEndMins) || + (newStartMins <= existingStartMins && + newEndMins >= existingEndMins)) { + throw 'This time slot overlaps with existing slot ${slot.startTime} - ${slot.endTime}'; + } + } + } + + void _addTimeSlot() async { + try { + TimeOfDay? startTime = await showTimePicker( + context: context, + initialTime: TimeOfDay.now(), + ); + + if (startTime != null) { + TimeOfDay? endTime = await showTimePicker( + context: context, + initialTime: startTime, + ); + + if (endTime != null) { + _validateTimeSlot(startTime, endTime); + _validateOverlap(startTime, endTime); + final slot = TimeSlot( + startTime: formatTime(startTime), + endTime: formatTime(endTime), + ); + + if (mounted) { + setState(() { + widget.controller.addTimeSlot(widget.selectedDay, slot); + currentSchedule = + widget.controller.model.weeklySchedule!.firstWhere( + (schedule) => schedule.day == widget.selectedDay, + orElse: () => AvailabilitySchedule( + day: widget.selectedDay, + timeSlots: [], + ), + ); + }); + } + } + } + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(e.toString()), + backgroundColor: Colors.red, + )); + } + } + + void _editTimeSlot(TimeSlot currentSlot) async { + try { + final currentStart = currentSlot.startTime!.split(' '); + currentSlot.endTime!.split(' '); + + final startTime = TimeOfDay( + hour: int.parse(currentStart[0].split(':')[0]), + minute: int.parse(currentStart[0].split(':')[1]), + ); + + TimeOfDay? newStartTime = await showTimePicker( context: context, initialTime: startTime, ); - if (endTime != null) { - final slot = TimeSlot( - startTime: formatTime(startTime), - endTime: formatTime(endTime), + if (newStartTime != null) { + TimeOfDay? newEndTime = await showTimePicker( + context: context, + initialTime: newStartTime, ); - if (mounted) { + if (newEndTime != null && mounted) { + _validateTimeSlot(newStartTime, newEndTime); + _validateOverlap(newStartTime, newEndTime, currentSlot); setState(() { - widget.controller.addTimeSlot(widget.selectedDay, slot); + widget.controller.removeTimeSlot(widget.selectedDay, currentSlot); + + final newSlot = TimeSlot( + startTime: formatTime(newStartTime), + endTime: formatTime(newEndTime), + ); + widget.controller.addTimeSlot(widget.selectedDay, newSlot); + currentSchedule = widget.controller.model.weeklySchedule!.firstWhere( (schedule) => schedule.day == widget.selectedDay, @@ -93,48 +187,11 @@ class _ConsultationTimeSlotScreenState }); } } - } - } - - void _editTimeSlot(TimeSlot currentSlot) async { - final currentStart = currentSlot.startTime!.split(' '); - currentSlot.endTime!.split(' '); - - final startTime = TimeOfDay( - hour: int.parse(currentStart[0].split(':')[0]), - minute: int.parse(currentStart[0].split(':')[1]), - ); - - TimeOfDay? newStartTime = await showTimePicker( - context: context, - initialTime: startTime, - ); - - if (newStartTime != null) { - TimeOfDay? newEndTime = await showTimePicker( - context: context, - initialTime: newStartTime, - ); - - if (newEndTime != null && mounted) { - setState(() { - widget.controller.removeTimeSlot(widget.selectedDay, currentSlot); - - final newSlot = TimeSlot( - startTime: formatTime(newStartTime), - endTime: formatTime(newEndTime), - ); - widget.controller.addTimeSlot(widget.selectedDay, newSlot); - - currentSchedule = widget.controller.model.weeklySchedule!.firstWhere( - (schedule) => schedule.day == widget.selectedDay, - orElse: () => AvailabilitySchedule( - day: widget.selectedDay, - timeSlots: [], - ), - ); - }); - } + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(e.toString()), + backgroundColor: Colors.red, + )); } } diff --git a/lib/screens/doctor_screen/doctor_profile_screens/specialities_selection_screen.dart b/lib/screens/doctor_screen/doctor_profile_screens/specialities_selection_screen.dart index a9df8f7..4acd56d 100644 --- a/lib/screens/doctor_screen/doctor_profile_screens/specialities_selection_screen.dart +++ b/lib/screens/doctor_screen/doctor_profile_screens/specialities_selection_screen.dart @@ -31,48 +31,18 @@ class _SpecialitiesScreenState extends State { 'description': 'Primary healthcare for adults and general medical conditions', }, - { - 'icon': Icons.family_restroom, - 'label': 'Family Medicine', - 'value': 'Family Medicine', - 'description': 'Comprehensive healthcare for families and individuals', - }, { 'icon': Icons.favorite, - 'label': 'Cardiologist', - 'value': 'Cardiologist', + 'label': 'Cardiology', + 'value': 'Cardiology', 'description': 'Diagnosis and treatment of heart conditions', }, - { - 'icon': Icons.psychology, - 'label': 'Neurology', - 'value': 'Neurology', - 'description': 'Treatment of nervous system disorders', - }, - { - 'icon': Icons.local_hospital, - 'label': 'Gastroenterology', - 'value': 'Gastroenterology', - 'description': 'Digestive system disorders and treatment', - }, { 'icon': Icons.face, - 'label': 'Dermatologist', - 'value': 'Dermatologist', + 'label': 'Dermatolology', + 'value': 'Dermatolology', 'description': 'Skin, hair, and nail conditions', }, - { - 'icon': Icons.wheelchair_pickup, - 'label': 'Orthopedic', - 'value': 'Orthopedic', - 'description': 'Musculoskeletal system and injury treatment', - }, - { - 'icon': Icons.remove_red_eye, - 'label': 'Ophthalmology', - 'value': 'Ophthalmology', - 'description': 'Eye care and vision treatment', - }, { 'icon': Icons.hearing, 'label': 'ENT', @@ -86,88 +56,16 @@ class _SpecialitiesScreenState extends State { 'description': 'Mental health and behavioral disorders', }, { - 'icon': Icons.pregnant_woman, - 'label': 'Gynecology', - 'value': 'Gynecology', - 'description': "Women's health and reproductive care", + 'icon': Icons.food_bank, + 'label': 'Clinical Nutrition', + 'value': 'Clinical Nutrition', + 'description': 'Nutrition counseling and management', }, { - 'icon': Icons.water_drop, - 'label': 'Urology', - 'value': 'Urology', - 'description': 'Urinary tract and male reproductive health', - }, - { - 'icon': Icons.biotech, - 'label': 'Endocrinology', - 'value': 'Endocrinology', - 'description': 'Hormone and metabolic disorders', - }, - { - 'icon': Icons.bloodtype, - 'label': 'Oncology', - 'value': 'Oncology', - 'description': 'Cancer diagnosis and treatment', - }, - { - 'icon': Icons.accessibility, - 'label': 'Rheumatology', - 'value': 'Rheumatology', - 'description': 'Arthritis and autoimmune conditions', - }, - { - 'icon': Icons.air, - 'label': 'Pulmonology', - 'value': 'Pulmonology', - 'description': 'Respiratory system disorders', - }, - { - 'icon': Icons.water, - 'label': 'Nephrology', - 'value': 'Nephrology', - 'description': 'Kidney diseases and disorders', - }, - { - 'icon': Icons.cleaning_services, + 'icon': Icons.medical_services_outlined, 'label': 'Dentistry', 'value': 'Dentistry', - 'description': 'Oral health and dental care', - }, - { - 'icon': Icons.accessibility_new, - 'label': 'Physical Therapy', - 'value': 'Physical Therapy', - 'description': 'Rehabilitation and physical medicine', - }, - { - 'icon': Icons.sports, - 'label': 'Sports Medicine', - 'value': 'Sports Medicine', - 'description': 'Athletic injuries and performance', - }, - { - 'icon': Icons.sick, - 'label': 'Allergy & Immunology', - 'value': 'Allergy & Immunology', - 'description': 'Allergies and immune system disorders', - }, - { - 'icon': Icons.healing, - 'label': 'Pain Management', - 'value': 'Pain Management', - 'description': 'Chronic pain treatment', - }, - { - 'icon': Icons.bedtime, - 'label': 'Sleep Medicine', - 'value': 'Sleep Medicine', - 'description': 'Sleep disorders and treatment', - }, - { - 'icon': Icons.elderly, - 'label': 'Geriatrics', - 'value': 'Geriatrics', - 'description': 'Healthcare for elderly patients', + 'description': 'Nutrition counseling and management', } // { // 'icon': Icons.child_care, @@ -244,9 +142,10 @@ class _SpecialitiesScreenState extends State { ); } + List selectedSpecialities = []; bool _validateSelection() { - if (selectedSpeciality == null) { - _showError('Please select a speciality to continue'); + if (selectedSpecialities.isEmpty) { + _showError('Please select at least one speciality to continue'); return false; } return true; @@ -295,21 +194,31 @@ class _SpecialitiesScreenState extends State { padding: const EdgeInsets.all(16), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, - childAspectRatio: 1, + childAspectRatio: 0.9, crossAxisSpacing: 16, mainAxisSpacing: 16, ), itemCount: specialities.length, itemBuilder: (context, index) { final specialty = specialities[index]; - final isSelected = selectedSpeciality == specialty['value']; + // final isSelected = selectedSpeciality == specialty['value']; + final isSelected = + selectedSpecialities.contains(specialty['value']); return GestureDetector( onTap: () { setState(() { - // _speciality = specialty['value']; - selectedSpeciality = specialty['value']; - widget.controller.updateSpeciality(selectedSpeciality!); + final value = specialty['value'] as String; + if (selectedSpecialities.contains(value)) { + selectedSpecialities.remove(value); + } else if (selectedSpecialities.length < 2) { + selectedSpecialities.add(value); + } else { + _showError('You can only select two specialities'); + } + + widget.controller + .updateSpeciality(selectedSpecialities.join(', ')); }); // _controller.updateSpeciality(specialty['value']); @@ -327,30 +236,45 @@ class _SpecialitiesScreenState extends State { ) : null, ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - specialty['icon'], - size: 32, - color: isSelected ? Colors.white : Colors.grey, + child: Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + specialty['icon'], + size: 32, + color: isSelected ? Colors.white : Colors.grey, + ), + const SizedBox(height: 8), + Flexible( + child: Text( + specialty['label'], + textAlign: TextAlign.center, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: isSelected + ? Colors.white + : Colors.black87, + fontSize: 13, + fontWeight: FontWeight.w500, + ), + ), + ), + if (isSelected) + const Padding( + padding: EdgeInsets.only(top: 4), + child: Icon( + Icons.check_circle, + color: Colors.white, + size: 20, + ), + ), + ], ), - const SizedBox(height: 8), - Text( - specialty['label'], - style: TextStyle( - color: isSelected ? Colors.white : Colors.black87, - fontSize: 14, - fontWeight: FontWeight.w500, - ), - ), - if (isSelected) - const Icon( - Icons.check_circle, - color: Colors.white, - size: 20, - ), - ], + ), ), ), );