Fixed padding issues with speciality screen and corrected logic in consultation sheduling screen.
This commit is contained in:
parent
89551b9fd7
commit
b0fcb14b0b
@ -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,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user