diff --git a/lib/screens/doctor_screen/doctor_consultation_schedule/consultation_time_slot_screen.dart b/lib/screens/doctor_screen/doctor_consultation_schedule/consultation_time_slot_screen.dart index 2be85a1..cc56856 100644 --- a/lib/screens/doctor_screen/doctor_consultation_schedule/consultation_time_slot_screen.dart +++ b/lib/screens/doctor_screen/doctor_consultation_schedule/consultation_time_slot_screen.dart @@ -59,7 +59,6 @@ class _ConsultationTimeSlotScreenState int startMins = startTime.hour * 60 + startTime.minute; int endMins = endTime.hour * 60 + endTime.minute; int duration = endMins - startMins; - int minimumDuration = int.parse(widget.controller.model.averageDurationMinutes ?? '0'); if (duration < minimumDuration) { @@ -94,53 +93,125 @@ class _ConsultationTimeSlotScreenState (newEndMins > existingStartMins && newEndMins <= existingEndMins) || (newStartMins <= existingStartMins && newEndMins >= existingEndMins)) { - throw 'This time slot overlaps with existing slot ${slot.startTime} - ${slot.endTime}'; + throw 'This time slot overlaps with an existing slot'; + // 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(), - ); + void _addTimeSlot() { + TimeOfDay? startTime; + TimeOfDay? endTime; + showModalBottomSheet( + context: context, + builder: (context) => StatefulBuilder( + builder: (context, setModalState) => Container( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Expanded( + child: _buildTimeField( + context: context, + label: 'Start Time', + time: startTime, + onSelect: (time) => setModalState(() => startTime = time), + ), + ), + const SizedBox(width: 16), + Expanded( + child: _buildTimeField( + context: context, + label: 'End Time', + time: endTime, + onSelect: (time) => setModalState(() => endTime = time), + ), + ), + ], + ), + const SizedBox(height: 16), + ElevatedButton( + onPressed: () { + if (startTime != null && endTime != null) { + try { + _validateTimeSlot(startTime!, endTime!); + _validateOverlap(startTime!, endTime!); + final slot = TimeSlot( + startTime: formatTime(startTime!), + endTime: formatTime(endTime!), + ); + widget.controller.addTimeSlot(widget.selectedDay, slot); + Navigator.pop(context); + setState(() { + currentSchedule = + widget.controller.model.weeklySchedule!.firstWhere( + (schedule) => schedule.day == widget.selectedDay, + orElse: () => AvailabilitySchedule( + day: widget.selectedDay, + timeSlots: [], + ), + ); + }); + } catch (e) { + Navigator.pop(context); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(e.toString()), + backgroundColor: Colors.red, + ), + ); + } + } + }, + child: const Text('Add Time Slot'), + ), + ], + ), + ), + ), + ); + } - 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: [], + Widget _buildTimeField({ + required BuildContext context, + required String label, + required TimeOfDay? time, + required Function(TimeOfDay) onSelect, + }) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(label), + const SizedBox(height: 8), + InkWell( + onTap: () async { + final selected = await showTimePicker( + context: context, + initialTime: time ?? TimeOfDay.now(), + ); + if (selected != null) onSelect(selected); + }, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade300), + borderRadius: BorderRadius.circular(4), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + time != null ? formatTime(time) : 'Select', ), - ); - }); - } - } - } - } catch (e) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(e.toString()), - backgroundColor: Colors.red, - )); - } + const Icon(Icons.access_time), + ], + ), + ), + ), + ], + ); } void _editTimeSlot(TimeSlot currentSlot) async { @@ -238,176 +309,181 @@ class _ConsultationTimeSlotScreenState @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.grey[50], - appBar: AppBar( - elevation: 0, - backgroundColor: Colors.white, - leading: IconButton( - icon: const Icon(Icons.arrow_back_ios, size: 20), - color: Colors.black54, - onPressed: () => Navigator.pop(context), - ), - title: Text( - '${widget.selectedDay} Schedule', - style: const TextStyle( - color: Colors.black87, - fontSize: 20, - fontWeight: FontWeight.w600, - ), - ), - centerTitle: true, - actions: [ - IconButton( - onPressed: () { - Navigator.of(context) - .pop(true); // Pop with true to indicate changes - }, - icon: const Icon( - Icons.check, - color: Color(0xFF4FB6D8), + return Stack( + children: [ + Scaffold( + backgroundColor: Colors.grey[50], + appBar: AppBar( + elevation: 0, + backgroundColor: Colors.white, + leading: IconButton( + icon: const Icon(Icons.arrow_back_ios, size: 20), + color: Colors.black54, + onPressed: () => Navigator.pop(context), ), - ), - ], - ), - body: Padding( - padding: const EdgeInsets.all(20.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Padding( - padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Text( - 'Time Slots', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Colors.black87, - ), + title: Text( + '${widget.selectedDay} Schedule', + style: const TextStyle( + color: Colors.black87, + fontSize: 20, + fontWeight: FontWeight.w600, ), ), - Expanded( - child: Container( - height: - MediaQuery.of(context).size.height * 0.2, // Reduced height - margin: const EdgeInsets.only(bottom: 250), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(25), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.08), - spreadRadius: 5, - blurRadius: 15, - offset: const Offset(0, 3), + centerTitle: true, + actions: [ + IconButton( + onPressed: () { + Navigator.of(context) + .pop(true); // Pop with true to indicate changes + }, + icon: const Icon( + Icons.check, + color: Color(0xFF4FB6D8), + ), + ), + ], + ), + body: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Padding( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Text( + 'Time Slots', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.black87, ), - ], + ), ), - padding: const EdgeInsets.all(16), - child: (currentSchedule.timeSlots == null || - currentSchedule.timeSlots!.isEmpty) - ? Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.access_time, - size: 48, - color: Colors.grey[400], - ), - const SizedBox(height: 16), - Text( - 'No time slots added yet', - style: TextStyle( - fontSize: 16, - color: Colors.grey[600], - fontWeight: FontWeight.w500, - ), - ), - const SizedBox(height: 8), - Text( - 'Tap + to add your first time slot', - style: TextStyle( - fontSize: 14, - color: Colors.grey[400], - ), - ), - ], + Expanded( + child: Container( + height: MediaQuery.of(context).size.height * 0.2, + margin: const EdgeInsets.only(bottom: 250), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(25), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.08), + spreadRadius: 5, + blurRadius: 15, + offset: const Offset(0, 3), ), - ) - : ListView.separated( - padding: const EdgeInsets.symmetric(vertical: 8), - itemCount: currentSchedule.timeSlots!.length, - separatorBuilder: (context, index) => - const Divider(height: 1), - itemBuilder: (context, index) { - final slot = currentSchedule.timeSlots![index]; - return Container( - padding: const EdgeInsets.symmetric(vertical: 8), - child: ListTile( - leading: Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: Colors.blue.withOpacity(0.1), - borderRadius: BorderRadius.circular(12), - ), - child: const Icon( + ], + ), + padding: const EdgeInsets.all(16), + child: (currentSchedule.timeSlots == null || + currentSchedule.timeSlots!.isEmpty) + ? Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( Icons.access_time, - color: Colors.blue, - size: 24, + size: 48, + color: Colors.grey[400], ), - ), - title: Text( - '${slot.startTime} - ${slot.endTime}', - style: const TextStyle( - fontSize: 13.5, - fontWeight: FontWeight.w600, - color: Colors.black87, + const SizedBox(height: 16), + Text( + 'No time slots added yet', + style: TextStyle( + fontSize: 16, + color: Colors.grey[600], + fontWeight: FontWeight.w500, + ), ), - ), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - icon: const Icon( - Icons.edit_outlined, - size: 26, - color: Colors.blue, - ), - onPressed: () => _editTimeSlot(slot), + const SizedBox(height: 8), + Text( + 'Tap + to add your first time slot', + style: TextStyle( + fontSize: 14, + color: Colors.grey[400], ), - IconButton( - icon: const Icon( - Icons.delete_outline, - size: 26, - color: Colors.red, - ), - onPressed: () => _deleteTimeSlot(slot), - ), - ], - ), + ), + ], ), - ); - }, - ), - ), + ) + : ListView.separated( + padding: const EdgeInsets.symmetric(vertical: 8), + itemCount: currentSchedule.timeSlots!.length, + separatorBuilder: (context, index) => + const Divider(height: 1), + itemBuilder: (context, index) { + final slot = currentSchedule.timeSlots![index]; + return Container( + padding: + const EdgeInsets.symmetric(vertical: 8), + child: ListTile( + leading: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.blue.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: const Icon( + Icons.access_time, + color: Colors.blue, + size: 24, + ), + ), + title: Text( + '${slot.startTime} - ${slot.endTime}', + style: const TextStyle( + fontSize: 13.5, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon( + Icons.edit_outlined, + size: 26, + color: Colors.blue, + ), + onPressed: () => _editTimeSlot(slot), + ), + IconButton( + icon: const Icon( + Icons.delete_outline, + size: 26, + color: Colors.red, + ), + onPressed: () => _deleteTimeSlot(slot), + ), + ], + ), + ), + ); + }, + ), + ), + ), + ], ), - ], - ), - ), - floatingActionButton: Container( - margin: const EdgeInsets.only(bottom: 270, right: 25), - child: FloatingActionButton( - onPressed: _addTimeSlot, - backgroundColor: const Color(0xFF4FB6D8), - elevation: 2, - child: const Icon( - Icons.add, - size: 28, - color: Colors.white, ), ), - ), + Positioned( + bottom: 280, + right: 29, + child: FloatingActionButton( + onPressed: _addTimeSlot, + backgroundColor: const Color(0xFF4FB6D8), + elevation: 2, + child: const Icon( + Icons.add, + size: 28, + color: Colors.white, + ), + ), + ), + ], ); } } diff --git a/lib/screens/doctor_screen/doctor_profile_screens/achivements_screen.dart b/lib/screens/doctor_screen/doctor_profile_screens/achivements_screen.dart index acdaa90..2d9bde8 100644 --- a/lib/screens/doctor_screen/doctor_profile_screens/achivements_screen.dart +++ b/lib/screens/doctor_screen/doctor_profile_screens/achivements_screen.dart @@ -74,6 +74,60 @@ class _AchievementsScreenState extends State { } } + void _addAchievementBottomSheet() { + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context) => Padding( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).viewInsets.bottom, + ), + child: Container( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Add Achievement', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 16), + TextFormField( + controller: _achievementController, + decoration: InputDecoration( + hintText: 'Enter your achievement', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + ), + const SizedBox(height: 16), + ElevatedButton( + onPressed: () { + final achievement = _achievementController.text.trim(); + if (_validateAchievement(achievement)) { + setState(() { + achievements.add(achievement); + _isEditing = true; + }); + _controller + .updateAchievements(List.from(achievements)); + _achievementController.clear(); + Navigator.pop(context); + } + }, + child: const Text('Add'), + ), + ], + ), + ), + ), + ); + } + void _removeAchievement(int index) { setState(() { achievements.removeAt(index); @@ -103,167 +157,91 @@ class _AchievementsScreenState extends State { @override Widget build(BuildContext context) { return WillPopScope( - onWillPop: () async { - if (_isEditing) { - final shouldPop = await showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Discard Changes?'), - content: const Text( - 'You have unsaved changes. Are you sure you want to go back?'), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context, false), - child: const Text('CANCEL'), - ), - TextButton( - onPressed: () => Navigator.pop(context, true), - child: const Text('DISCARD'), - ), - ], - ), - ); - return shouldPop ?? false; - } - return true; - }, - child: Scaffold( - appBar: AppBar( - actions: [ - IconButton( - onPressed: () { - if (_validateBeforeNextPage()) { - Navigator.pushNamed( - context, RouteNames.digitalSignatureScreeen, - arguments: _controller); - } - }, - icon: const Icon(Icons.arrow_forward)), - ], - title: const Text('Achievements'), - ), - body: Form( - key: _formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Padding( - padding: EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Add Your Achievements', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - ], - ), + onWillPop: () async { + if (_isEditing) { + final shouldPop = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Discard Changes?'), + content: const Text( + 'You have unsaved changes. Are you sure you want to go back?'), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: const Text('CANCEL'), ), - const SizedBox(height: 8), - Container( - margin: - const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: Colors.blueGrey.withOpacity(0.5), - blurRadius: 10, - offset: const Offset(0, 2), - ), - ], - ), - child: Padding( - padding: const EdgeInsets.all(12.0), - child: TextFormField( - controller: _achievementController, - autovalidateMode: AutovalidateMode.onUserInteraction, - decoration: InputDecoration( - hintText: 'Enter your achievement', - labelText: 'Achievement', - filled: true, - fillColor: Colors.grey[100], - suffixIcon: IconButton( - icon: const Icon(Icons.add_circle_outline, - color: Colors.blue), - onPressed: _addAchievement, - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - borderSide: - BorderSide(color: Colors.grey.shade300, width: 1), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - borderSide: - const BorderSide(color: Colors.blue, width: 1.5), - ), - errorBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - borderSide: - const BorderSide(color: Colors.red, width: 1.5), - ), - focusedErrorBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - borderSide: - const BorderSide(color: Colors.red, width: 1.5), - ), - helperText: achievements.isEmpty - ? 'Minimum 3 characters required' - : null, - ), - validator: (value) { - if (achievements.isEmpty) { - if (value == null || value.isEmpty) { - return 'Please enter an achievement'; - } - if (value.length < 3) { - return 'Achievement must be at least 3 characters long'; - } - } - if (value != null && - value.isNotEmpty && - achievements.any((a) => - a.toLowerCase() == value.toLowerCase())) { - return 'This achievement has already been added'; - } - return null; - }, - onFieldSubmitted: (_) => _addAchievement(), - ), - ), - ), - Expanded( - child: ListView.builder( - itemCount: achievements.length, - padding: const EdgeInsets.all(16), - itemBuilder: (context, index) { - return Card( - elevation: 2, - margin: const EdgeInsets.only(bottom: 8), - child: ListTile( - leading: CircleAvatar( - backgroundColor: const Color(0xFF5BC0DE), - child: Text('${index + 1}'), - ), - title: Text(achievements[index]), - trailing: IconButton( - icon: const Icon(Icons.delete, color: Colors.red), - onPressed: () => _removeAchievement(index), - ), - ), - ); - }, - ), + TextButton( + onPressed: () => Navigator.pop(context, true), + child: const Text('DISCARD'), ), ], ), - ), - )); + ); + return shouldPop ?? false; + } + return true; + }, + child: Scaffold( + appBar: AppBar( + actions: [ + IconButton( + onPressed: () { + if (_validateBeforeNextPage()) { + Navigator.pushNamed( + context, RouteNames.digitalSignatureScreeen, + arguments: _controller); + } + }, + icon: const Icon(Icons.arrow_forward), + ), + ], + title: const Text('Achievements'), + ), + body: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Padding( + padding: EdgeInsets.all(16.0), + child: Text( + 'Add Your Achievements', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ), + Expanded( + child: ListView.builder( + itemCount: achievements.length, + padding: const EdgeInsets.all(16), + itemBuilder: (context, index) { + return Card( + elevation: 2, + margin: const EdgeInsets.only(bottom: 8), + child: ListTile( + leading: CircleAvatar( + backgroundColor: const Color(0xFF5BC0DE), + child: Text('${index + 1}'), + ), + title: Text(achievements[index]), + trailing: IconButton( + icon: const Icon(Icons.delete, color: Colors.red), + onPressed: () => _removeAchievement(index), + ), + ), + ); + }, + ), + ), + ], + ), + floatingActionButton: FloatingActionButton( + onPressed: _addAchievementBottomSheet, + backgroundColor: const Color(0xFF4FB6D8), + child: const Icon(Icons.add), + ), + ), + ); } @override