medora-provider/lib/screens/patientScreens/appoinmentBooking/consultation_time_screen.dart
DhanshCOSQ b57523599c feature/medora-55 (#6)
Booking physical consultation ,  bugs fixed and Profile picture adding using firebase storage is complete.

Co-authored-by: Jipson George <152465898+Jipson-cosq@users.noreply.github.com>
Reviewed-on: cosqnet/telemednet#6
Co-authored-by: DhanshCOSQ <dhanshas@cosq.net>
Co-committed-by: DhanshCOSQ <dhanshas@cosq.net>
2024-11-05 08:22:13 +00:00

554 lines
17 KiB
Dart

import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:medora/data/models/consultation_center.dart';
import 'package:medora/data/models/doctor.dart';
import 'package:intl/intl.dart';
import 'package:medora/route/route_names.dart';
class ConsultationTimeScreen extends StatefulWidget {
final Doctor doctor;
final ConsultationCenter selectedConsultation;
const ConsultationTimeScreen({
super.key,
required this.doctor,
required this.selectedConsultation,
});
@override
State<ConsultationTimeScreen> createState() => _ConsultationTimeScreenState();
}
class _ConsultationTimeScreenState extends State<ConsultationTimeScreen> {
DateTime? selectedDate;
String? selectedTime;
List<TimeSlot> getTimeSlotsForDay(String dayName) {
try {
final schedule = widget.selectedConsultation.weeklySchedule?.firstWhere(
(schedule) => schedule.day == dayName,
orElse: () => AvailabilitySchedule(
day: dayName,
timeSlots: [],
),
);
return schedule?.timeSlots ?? [];
} catch (e) {
debugPrint('Error getting time slots: $e');
return [];
}
}
DateTime? parseTimeString(String? timeStr) {
if (timeStr == null) return null;
try {
// Try parsing 12-hour format first
return DateFormat('h:mm a').parse(timeStr);
} catch (e) {
try {
// Try parsing 24-hour format
return DateFormat('HH:mm').parse(timeStr);
} catch (e) {
debugPrint('Error parsing time: $timeStr');
return null;
}
}
}
String get formattedAddress {
final parts = [
widget.selectedConsultation.floorBuilding,
widget.selectedConsultation.street,
widget.selectedConsultation.city,
widget.selectedConsultation.state,
widget.selectedConsultation.postalCode
].where((part) => part != null && part.isNotEmpty).toList();
return parts.join(', ');
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF5F7FF),
appBar: _buildAppBar(),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildLocationInfo(),
const SizedBox(height: 24),
_buildDateSelection(),
const SizedBox(height: 24),
_buildTimeSlots(),
],
),
),
),
);
}
PreferredSizeWidget _buildAppBar() {
return AppBar(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.black87),
onPressed: () => Navigator.pop(context),
),
title: Text(
'Select Date & Time',
style: GoogleFonts.poppins(
color: Colors.black87,
fontWeight: FontWeight.w600,
fontSize: 20,
),
),
centerTitle: true,
);
}
Widget _buildLocationInfo() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.doctor.firstName ?? '',
style: GoogleFonts.poppins(
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
Text(
widget.doctor.speciality!,
style: GoogleFonts.poppins(
fontSize: 14,
color: Colors.grey[600],
),
),
],
),
),
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.network(
widget.doctor.profileImage!,
width: 60,
height: 60,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Container(
width: 60,
height: 60,
color: Colors.grey[300],
child:
Icon(Icons.person, size: 30, color: Colors.grey[600]),
);
},
),
),
],
),
const Divider(height: 24),
Text(
'Selected Location',
style: GoogleFonts.poppins(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 8),
Text(
widget.selectedConsultation.city ?? '',
style: GoogleFonts.poppins(
fontSize: 14,
color: Colors.grey[600],
),
),
const SizedBox(height: 4),
Text(
'Average consultation time: ${widget.selectedConsultation.averageDurationMinutes}',
style: GoogleFonts.poppins(
fontSize: 14,
color: Colors.grey[600],
),
),
],
),
);
}
Widget _buildDateSelection() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Select Date',
style: GoogleFonts.poppins(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 12),
SizedBox(
height: 100,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: 20, // Show next 20 days
itemBuilder: (context, index) {
final date = DateTime.now().add(Duration(days: index));
final isSelected = selectedDate?.day == date.day &&
selectedDate?.month == date.month &&
selectedDate?.year == date.year;
final isAvailable = _isDateAvailable(date);
return GestureDetector(
onTap: isAvailable
? () {
setState(() {
selectedDate = date;
selectedTime = null; // Reset time when date changes
});
}
: null,
child: Container(
width: 70,
margin: const EdgeInsets.only(right: 12),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: isSelected
? Colors.blue
: isAvailable
? Colors.white
: Colors.grey[200],
borderRadius: BorderRadius.circular(16),
boxShadow: isAvailable
? [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 2),
),
]
: null,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
DateFormat('EEE').format(date).toUpperCase(),
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w500,
color: isSelected
? Colors.white
: isAvailable
? Colors.grey[600]
: Colors.grey[400],
),
),
const SizedBox(height: 8),
Text(
date.day.toString(),
style: GoogleFonts.poppins(
fontSize: 18,
fontWeight: FontWeight.w600,
color: isSelected
? Colors.white
: isAvailable
? Colors.black87
: Colors.grey[400],
),
),
],
),
),
);
},
),
),
],
);
}
Widget _buildTimeSlots() {
if (selectedDate == null) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.5),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.grey.withOpacity(0.2)),
),
child: Center(
child: Text(
'Please select a date to view available time slots',
style: GoogleFonts.poppins(
fontSize: 14,
color: Colors.grey[600],
),
),
),
);
}
final dayName = DateFormat('EEEE').format(selectedDate!);
final timeSlots = getTimeSlotsForDay(dayName);
final allTimeSlots = _generateTimeSlots(timeSlots);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Select Time',
style: GoogleFonts.poppins(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Wrap(
spacing: 12,
runSpacing: 12,
children: allTimeSlots.map((time) {
final isSelected = selectedTime == time;
final isAvailable = _isTimeSlotAvailable(time);
return GestureDetector(
onTap: isAvailable
? () {
Navigator.pushNamed(
context,
RouteNames.consultationBookingScreen,
arguments: {
'doctor': widget.doctor,
'selectedConsultation': widget.selectedConsultation,
'selectedDate': selectedDate,
'selectedTime': time
},
);
}
: null,
child: Container(
padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
decoration: BoxDecoration(
color: isSelected
? Colors.blue
: isAvailable
? Colors.white
: Colors.grey[200],
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: isSelected
? Colors.blue
: isAvailable
? Colors.grey.withOpacity(0.2)
: Colors.grey.withOpacity(0.1),
),
),
child: Text(
time,
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w500,
color: isSelected
? Colors.white
: isAvailable
? Colors.black87
: Colors.grey[400],
),
),
),
);
}).toList(),
),
),
],
);
}
bool _isDateAvailable(DateTime date) {
final dayName = DateFormat('EEEE').format(date);
return widget.selectedConsultation.weeklySchedule
?.any((schedule) => schedule.day == dayName) ??
false;
}
List<String> _generateTimeSlots(List<TimeSlot> timeSlots) {
final slots = <String>[];
final timeFormat = DateFormat('h:mm a');
for (var slot in timeSlots) {
final startTime = parseTimeString(slot.startTime);
final endTime = parseTimeString(slot.endTime);
if (startTime == null || endTime == null) continue;
var currentTime = startTime;
while (currentTime.isBefore(endTime)) {
slots.add(timeFormat.format(currentTime));
currentTime = currentTime.add(const Duration(minutes: 30));
}
}
return slots;
}
bool _isTimeSlotAvailable(String time) {
final now = DateTime.now();
if (selectedDate == null) return false;
// Parse the time slot
final timeSlot = parseTimeString(time);
if (timeSlot == null) return false;
// Create a DateTime combining selected date and time
final slotDateTime = DateTime(
selectedDate!.year,
selectedDate!.month,
selectedDate!.day,
timeSlot.hour,
timeSlot.minute,
);
// Check if the slot is in the past
if (slotDateTime.isBefore(now)) return false;
// Here you would typically check against your booking database
// For now, returning true for future slots
return true;
}
void _handleBooking() {
showDialog(
context: context,
builder: (context) => AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
title: Text(
'Confirm Booking',
style: GoogleFonts.poppins(
fontWeight: FontWeight.w600,
),
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildConfirmationDetail(
'Doctor',
widget.doctor.firstName!,
),
const SizedBox(height: 8),
_buildConfirmationDetail(
'Location',
widget.selectedConsultation.city!,
),
const SizedBox(height: 8),
_buildConfirmationDetail(
'Date',
DateFormat('EEEE, MMMM d').format(selectedDate!),
),
const SizedBox(height: 8),
_buildConfirmationDetail(
'Time',
selectedTime!,
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(
'Cancel',
style: GoogleFonts.poppins(
color: Colors.grey[600],
),
),
),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Text(
'Confirm',
style: GoogleFonts.poppins(
color: Colors.white,
),
),
),
],
),
);
}
Widget _buildConfirmationDetail(String label, String value) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 80,
child: Text(
'$label:',
style: GoogleFonts.poppins(
fontWeight: FontWeight.w500,
color: Colors.grey[600],
),
),
),
Expanded(
child: Text(
value,
style: GoogleFonts.poppins(
fontWeight: FontWeight.w500,
),
),
),
],
);
}
}