Fixed padding issues with speciality screen and corrected logic in consultation sheduling screen.

This commit is contained in:
Aswin B. S 2025-03-05 23:10:26 +05:30
parent 89551b9fd7
commit b0fcb14b0b
2 changed files with 185 additions and 204 deletions

View File

@ -43,45 +43,139 @@ class _ConsultationTimeSlotScreenState
return newSchedule; return newSchedule;
}, },
); );
// Ensure timeSlots is not null
currentSchedule.timeSlots ??= []; currentSchedule.timeSlots ??= [];
// Debug print
print( print(
'Initial schedule for ${widget.selectedDay}: ${currentSchedule.timeSlots}'); 'Initial schedule for ${widget.selectedDay}: ${currentSchedule.timeSlots}');
} }
String formatTime(TimeOfDay time) { String formatTime(TimeOfDay time) {
final hour = time.hourOfPeriod == 0 final hour = time.hourOfPeriod == 0 ? 12 : time.hourOfPeriod;
? 12
: time.hourOfPeriod; // Convert 0 to 12 for AM
final minute = time.minute.toString().padLeft(2, '0'); final minute = time.minute.toString().padLeft(2, '0');
final period = time.period == DayPeriod.am ? 'AM' : 'PM'; final period = time.period == DayPeriod.am ? 'AM' : 'PM';
return '$hour:$minute $period'; return '$hour:$minute $period';
} }
void _addTimeSlot() async { void _validateTimeSlot(TimeOfDay startTime, TimeOfDay endTime) {
TimeOfDay? startTime = await showTimePicker( int startMins = startTime.hour * 60 + startTime.minute;
context: context, int endMins = endTime.hour * 60 + endTime.minute;
initialTime: TimeOfDay.now(), int duration = endMins - startMins;
);
if (startTime != null) { int minimumDuration =
TimeOfDay? endTime = await showTimePicker( 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, context: context,
initialTime: startTime, initialTime: startTime,
); );
if (endTime != null) { if (newStartTime != null) {
final slot = TimeSlot( TimeOfDay? newEndTime = await showTimePicker(
startTime: formatTime(startTime), context: context,
endTime: formatTime(endTime), initialTime: newStartTime,
); );
if (mounted) { if (newEndTime != null && mounted) {
_validateTimeSlot(newStartTime, newEndTime);
_validateOverlap(newStartTime, newEndTime, currentSlot);
setState(() { 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 = currentSchedule =
widget.controller.model.weeklySchedule!.firstWhere( widget.controller.model.weeklySchedule!.firstWhere(
(schedule) => schedule.day == widget.selectedDay, (schedule) => schedule.day == widget.selectedDay,
@ -93,48 +187,11 @@ class _ConsultationTimeSlotScreenState
}); });
} }
} }
} } catch (e) {
} ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(e.toString()),
void _editTimeSlot(TimeSlot currentSlot) async { backgroundColor: Colors.red,
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: [],
),
);
});
}
} }
} }

View File

@ -31,48 +31,18 @@ class _SpecialitiesScreenState extends State<SpecialitiesScreen> {
'description': 'description':
'Primary healthcare for adults and general medical conditions', '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, 'icon': Icons.favorite,
'label': 'Cardiologist', 'label': 'Cardiology',
'value': 'Cardiologist', 'value': 'Cardiology',
'description': 'Diagnosis and treatment of heart conditions', '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, 'icon': Icons.face,
'label': 'Dermatologist', 'label': 'Dermatolology',
'value': 'Dermatologist', 'value': 'Dermatolology',
'description': 'Skin, hair, and nail conditions', '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, 'icon': Icons.hearing,
'label': 'ENT', 'label': 'ENT',
@ -86,88 +56,16 @@ class _SpecialitiesScreenState extends State<SpecialitiesScreen> {
'description': 'Mental health and behavioral disorders', 'description': 'Mental health and behavioral disorders',
}, },
{ {
'icon': Icons.pregnant_woman, 'icon': Icons.food_bank,
'label': 'Gynecology', 'label': 'Clinical Nutrition',
'value': 'Gynecology', 'value': 'Clinical Nutrition',
'description': "Women's health and reproductive care", 'description': 'Nutrition counseling and management',
}, },
{ {
'icon': Icons.water_drop, 'icon': Icons.medical_services_outlined,
'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,
'label': 'Dentistry', 'label': 'Dentistry',
'value': 'Dentistry', 'value': 'Dentistry',
'description': 'Oral health and dental care', 'description': 'Nutrition counseling and management',
},
{
'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',
} }
// { // {
// 'icon': Icons.child_care, // 'icon': Icons.child_care,
@ -244,9 +142,10 @@ class _SpecialitiesScreenState extends State<SpecialitiesScreen> {
); );
} }
List<String> selectedSpecialities = [];
bool _validateSelection() { bool _validateSelection() {
if (selectedSpeciality == null) { if (selectedSpecialities.isEmpty) {
_showError('Please select a speciality to continue'); _showError('Please select at least one speciality to continue');
return false; return false;
} }
return true; return true;
@ -295,21 +194,31 @@ class _SpecialitiesScreenState extends State<SpecialitiesScreen> {
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, crossAxisCount: 3,
childAspectRatio: 1, childAspectRatio: 0.9,
crossAxisSpacing: 16, crossAxisSpacing: 16,
mainAxisSpacing: 16, mainAxisSpacing: 16,
), ),
itemCount: specialities.length, itemCount: specialities.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final specialty = specialities[index]; final specialty = specialities[index];
final isSelected = selectedSpeciality == specialty['value']; // final isSelected = selectedSpeciality == specialty['value'];
final isSelected =
selectedSpecialities.contains(specialty['value']);
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
setState(() { setState(() {
// _speciality = specialty['value']; final value = specialty['value'] as String;
selectedSpeciality = specialty['value']; if (selectedSpecialities.contains(value)) {
widget.controller.updateSpeciality(selectedSpeciality!); 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']); // _controller.updateSpeciality(specialty['value']);
@ -327,30 +236,45 @@ class _SpecialitiesScreenState extends State<SpecialitiesScreen> {
) )
: null, : null,
), ),
child: Column( child: Center(
mainAxisAlignment: MainAxisAlignment.center, child: Padding(
children: [ padding: const EdgeInsets.all(8.0),
Icon( child: Column(
specialty['icon'], mainAxisAlignment: MainAxisAlignment.center,
size: 32, children: [
color: isSelected ? Colors.white : Colors.grey, 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,
),
],
), ),
), ),
); );