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;
|
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: [],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user