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;
},
);
// 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,
));
}
}

View File

@ -31,48 +31,18 @@ class _SpecialitiesScreenState extends State<SpecialitiesScreen> {
'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<SpecialitiesScreen> {
'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<SpecialitiesScreen> {
);
}
List<String> 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<SpecialitiesScreen> {
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<SpecialitiesScreen> {
)
: 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,
),
],
),
),
),
);