diff --git a/lib/main.dart b/lib/main.dart index d24b2ec..ed7a0ad 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -19,7 +19,6 @@ import 'package:prosappco/src/screens/profile_pro.dart'; import 'package:prosappco/src/screens/register.dart'; import 'package:prosappco/src/screens/request_sent.dart'; import 'package:prosappco/src/screens/service.dart'; - import 'firebase_options.dart'; void main() async { @@ -60,7 +59,6 @@ class MyApp extends StatelessWidget { '/solicitudEnviada': (context) => const RequestSentScreen(), '/nuevaPassword': (context) => NewPasswordScreen(), '/servicio': (context) => const ServiceScreen(), - '/profesional': (context) => const ProfessionalScreen(), '/profilePro': (context) => const ProfilePro(), '/calendar': (context) => const CalendarScreen(), }, diff --git a/lib/src/models/event_model.dart b/lib/src/models/event_model.dart index 7577248..93c70e7 100644 --- a/lib/src/models/event_model.dart +++ b/lib/src/models/event_model.dart @@ -4,13 +4,8 @@ import 'package:prosappco/src/authentication/authentication_repository.dart'; final uid = AuthenticationRepository.instance.getCurrentUserUid(); class EventoService { - Future createEvent( - String title, - String description, - String day, - String range1Hour1, - String range1Hour2, - ) async { + Future createEvent(String title, String description, String day, + String range1Hour1, String range1Hour2, String professionalId) async { try { await FirebaseFirestore.instance.collection('services').add({ 'user_id': uid, @@ -18,7 +13,8 @@ class EventoService { 'description': description, 'day': day, 'range1Hour1': range1Hour1, - 'range1Hour2': range1Hour2 + 'range1Hour2': range1Hour2, + 'professional_id': professionalId }).then((value) { FirebaseFirestore.instance.collection('users').doc(uid).update({ 'services': FieldValue.arrayUnion([value.id]) @@ -34,7 +30,7 @@ Future> getByProId(String day) async { try { var snapshot = await FirebaseFirestore.instance .collection('services') - .where('user_id', isEqualTo: uid) + .where('professional_id', isEqualTo: uid) .where('day', isEqualTo: day.toString()) .get(); @@ -57,6 +53,7 @@ class Event { String? range1Hour1; String? range1Hour2; String? userId; + String? professionalId; Event({ this.title, @@ -65,6 +62,7 @@ class Event { this.range1Hour1, this.range1Hour2, this.userId, + this.professionalId, }); factory Event.fromJson(Map json) { @@ -75,6 +73,7 @@ class Event { range1Hour1: json['range1Hour1'], range1Hour2: json['range1Hour2'], userId: json['user_id'], + professionalId: json['professional_id'], ); } @@ -82,7 +81,7 @@ class Event { try { var snapshot = await FirebaseFirestore.instance .collection('services') - .where('user_id', isEqualTo: uid) + .where('professional_id', isEqualTo: uid) .get(); List eventos = []; diff --git a/lib/src/screens/calendar.dart b/lib/src/screens/calendar.dart index bcaebd0..8931162 100644 --- a/lib/src/screens/calendar.dart +++ b/lib/src/screens/calendar.dart @@ -1,9 +1,11 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_rating_bar/flutter_rating_bar.dart'; import 'package:prosappco/src/authentication/authentication_repository.dart'; import 'package:prosappco/src/components/pop_appbar.dart'; import 'package:prosappco/src/components/primary_btn.dart'; import 'package:prosappco/src/models/event_model.dart'; +import 'package:prosappco/src/models/scores_model.dart'; import 'package:prosappco/src/screens/cita.dart'; import 'package:table_calendar/table_calendar.dart'; import 'package:intl/intl.dart'; @@ -22,6 +24,8 @@ class _CalendarScreenState extends State { final _titleController = TextEditingController(); final _descriptionController = TextEditingController(); + ScoresModel? scoresModel; + EventoService eventoService = EventoService(); CalendarFormat _calendarFormat = CalendarFormat.month; DateTime today = DateTime.now(); @@ -63,6 +67,12 @@ class _CalendarScreenState extends State { _events = value; })); } + + if (scoresModel == null) { + ScoresModel.scoreFrom(uid.toString(), false).then( + (ScoresModel s) => setState(() => scoresModel = s), + ); + } } void _onDaySelected(DateTime day, DateTime focusedDay) { @@ -287,6 +297,7 @@ class _CalendarScreenState extends State { today.toString(), _selectedTime1!.format(context).toString(), _selectedTime2!.format(context).toString(), + uid.toString(), ) .then((value) { Navigator.pop(context); @@ -380,8 +391,34 @@ class _CalendarScreenState extends State { subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('${e.description}'), - Text('${e.description}'), + Row( + children: [ + RatingBar.builder( + initialRating: scoresModel?.average ?? 0, + minRating: 1, + direction: Axis.horizontal, + allowHalfRating: true, + itemCount: 5, + itemSize: 25, + maxRating: 5, + itemPadding: + const EdgeInsets.symmetric(horizontal: 0), + itemBuilder: (context, _) => const Icon( + Icons.star, + color: Color(0xFF2BA4EC), + ), + onRatingUpdate: (rating) {}, + ignoreGestures: true, + ), + const SizedBox(width: 5), + Text( + '(${scoresModel?.total.toString()}) ${scoresModel?.average.toString()}'), + ], + ), + Text( + '" ${e.description} "', + style: const TextStyle(fontStyle: FontStyle.italic), + ), ], ), trailing: const Icon(Icons.keyboard_arrow_right), diff --git a/lib/src/screens/cita.dart b/lib/src/screens/cita.dart index 2b287ee..614395f 100644 --- a/lib/src/screens/cita.dart +++ b/lib/src/screens/cita.dart @@ -74,7 +74,7 @@ class _CitaScreenState extends State { text: TextSpan( children: [ TextSpan( - text: '${nombre}, ', + text: '$nombre, ', style: const TextStyle( color: Colors.black, fontWeight: FontWeight.bold, @@ -84,7 +84,7 @@ class _CitaScreenState extends State { TextSpan( text: '${DateFormat('dd MMM', 'es').format(DateTime.parse(widget.evento.day!))} ${widget.evento.range1Hour1}', - style: TextStyle( + style: const TextStyle( color: Colors.grey, fontSize: 16, ), @@ -119,7 +119,7 @@ class _CitaScreenState extends State { Text( textAlign: TextAlign.center, '" ${widget.evento.description} "', - style: TextStyle( + style: const TextStyle( color: Colors.grey, fontStyle: FontStyle.italic), ) ], diff --git a/lib/src/screens/horario.dart b/lib/src/screens/horario.dart index ddc1efa..a2145b2 100644 --- a/lib/src/screens/horario.dart +++ b/lib/src/screens/horario.dart @@ -120,14 +120,15 @@ class _HorarioScreenState extends State { Padding( padding: const EdgeInsets.only(top: 30, bottom: 30), child: PrimaryButtom( - onPressed: () async { - updateHorario().then((value) => Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (context) => const ProfilePro()), - )); - }, - label: 'Guardar'), + onPressed: () async { + updateHorario().then((value) => Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => const ProfilePro()), + )); + }, + label: 'Guardar', + ), ) ], ), diff --git a/lib/src/screens/professional.dart b/lib/src/screens/professional.dart index daf2953..909a20a 100644 --- a/lib/src/screens/professional.dart +++ b/lib/src/screens/professional.dart @@ -11,7 +11,8 @@ import 'package:prosappco/src/screens/professional_info.dart'; import '../models/scores_model.dart'; class ProfessionalScreen extends StatefulWidget { - const ProfessionalScreen({super.key}); + final String profession; + const ProfessionalScreen({super.key, required this.profession}); @override State createState() => _ProfessionalScreenState(); @@ -24,6 +25,7 @@ final CollectionReference usersCollection = var _photo = '...'; class Professional { + final String id; final Reference professionalRef; final String name; final String professionName; @@ -33,6 +35,7 @@ class Professional { final ScoresModel scores; Professional({ + required this.id, required this.professionalRef, required this.name, required this.professionName, @@ -78,6 +81,7 @@ class Professional { .toList(); Professional professional = Professional( + id: user.id, name: data['name'], professionName: data['profesion'], cityName: data['city'], @@ -95,44 +99,6 @@ class Professional { } } -Future> getProfessionals() async { - List professionals = []; - - try { - QuerySnapshot users = await usersCollection.get(); - for (DocumentSnapshot user in users.docs) { - // Acceder a los datos de cada usuario - Map data = user.data() as Map; - - // Crear un objeto Professional a partir de los datos del usuario - _photo = await AuthenticationRepository.instance.getPhoto(user.id); - - if (data['estado'] == 'activo') { - List especializaciones; - - especializaciones = (data['especializaciones'] as List) - .map((e) => e.toString()) - .toList(); - - Professional professional = Professional( - name: data['name'], - professionName: data['profesion'], - cityName: data['city'], - professionalRef: storage.ref().child(_photo), - professionalEspecializado: especializaciones, - ubicacion: data['ubicacion'] ?? '', - scores: await ScoresModel.scoreTo(user.id, true, true), - ); - professionals.add(professional); - } - } - } catch (e) { - print('Error al obtener profesionales: $e'); - } - - return professionals; -} - class _ProfessionalScreenState extends State { List? filteredProfessionals; @@ -141,9 +107,52 @@ class _ProfessionalScreenState extends State { final uid = AuthenticationRepository.instance.getCurrentUserUid(); List? _professionals; + Future> getProfessionals() async { + List professionals = []; + + try { + QuerySnapshot users = await usersCollection.get(); + for (DocumentSnapshot user in users.docs) { + // Acceder a los datos de cada usuario + Map data = user.data() as Map; + + // Crear un objeto Professional a partir de los datos del usuario + _photo = await AuthenticationRepository.instance.getPhoto(user.id); + + if (data['estado'] == 'activo') { + List especializaciones; + + especializaciones = (data['especializaciones'] as List) + .map((e) => e.toString()) + .toList(); + + if (widget.profession == data['profesion'] || + widget.profession == '') { + Professional professional = Professional( + id: user.id, + name: data['name'], + professionName: data['profesion'], + cityName: data['city'], + professionalRef: storage.ref().child(_photo), + professionalEspecializado: especializaciones, + ubicacion: data['ubicacion'] ?? '', + scores: await ScoresModel.scoreTo(user.id, true, true), + ); + professionals.add(professional); + } + } + } + } catch (e) { + print('Error al obtener profesionales: $e'); + } + + return professionals; + } + @override void initState() { super.initState(); + searchController.addListener(() { setState(() { if (_professionals != null) { @@ -257,7 +266,7 @@ class _ProfessionalScreenState extends State { ), ), onTap: () { - // Navigator.pop(context, professionals[index]); + Navigator.pop(context, professionals[index]); }, ); }, diff --git a/lib/src/screens/service.dart b/lib/src/screens/service.dart index 834b537..210ad78 100644 --- a/lib/src/screens/service.dart +++ b/lib/src/screens/service.dart @@ -1,4 +1,4 @@ -import 'dart:async'; +import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_storage/firebase_storage.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; @@ -10,8 +10,11 @@ import 'package:get/get.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:prosappco/src/authentication/authentication_repository.dart'; import 'package:prosappco/src/components/drawer_menu.dart'; +import 'package:prosappco/src/models/event_model.dart'; +import 'package:prosappco/src/models/user_model.dart'; import 'package:prosappco/src/screens/professional.dart'; import 'package:intl/intl.dart'; +import 'package:prosappco/src/screens/service_type.dart'; class ServiceScreen extends StatefulWidget { const ServiceScreen({super.key}); @@ -21,9 +24,16 @@ class ServiceScreen extends StatefulWidget { } class _ServiceScreenState extends State { + EventoService eventoService = EventoService(); + final TextEditingController _locationController = TextEditingController(); final String _locationPosition = ''; - + final _profesionalController = TextEditingController(); + final _serviceTypeController = TextEditingController(); + final _observacionController = TextEditingController(); + var professionalId = ''; + String _serviceType = 'Servicio'; + String _professional = ''; final DateFormat formatter = DateFormat('dd/MM/yyyy'); final DateTime now = DateTime.now(); @@ -32,7 +42,7 @@ class _ServiceScreenState extends State { late GoogleMapController googleMapController; - static const CameraPosition initialCameraPosition = CameraPosition( + static CameraPosition initialCameraPosition = const CameraPosition( target: LatLng(7.8939100, -72.5078200), zoom: 14.4746, ); @@ -130,9 +140,6 @@ class _ServiceScreenState extends State { @override Widget build(BuildContext context) { - List opc = ["Servicio", "Opción 1", "Opción 2", "Opción 3"]; - String defaultValue = opc[0]; - return SafeArea( child: Scaffold( backgroundColor: const Color(0xFFD6F4FF), @@ -218,7 +225,7 @@ class _ServiceScreenState extends State { // markers.clear(); // markers.add(Marker( - // markerId: const MarkerId('currentLocation'), + // markerId: MarkerId('currentLocation'), // position: // LatLng(position.latitude, position.longitude))); }, @@ -254,54 +261,56 @@ class _ServiceScreenState extends State { ), Padding( padding: - const EdgeInsets.only(top: 23, left: 100, right: 25), + const EdgeInsets.only(top: 22, left: 100, right: 25), child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(50), + color: Colors.white, boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.4), spreadRadius: 1, blurRadius: 2, - offset: const Offset( - 1, 2), // changes position of shadow + offset: const Offset(1, 2), ), ], ), - child: DropdownButtonFormField( - value: defaultValue, - elevation: 0, - decoration: InputDecoration( - filled: true, - fillColor: Colors.white, - contentPadding: const EdgeInsets.symmetric( - horizontal: 16, vertical: 8), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(50), - borderSide: BorderSide.none, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: TextFormField( + onTap: () async { + final String? serviceType = await Navigator.push( + context, + CupertinoPageRoute( + builder: (BuildContext context) { + return const ServiceTypeScreen(); + }, + ), + ) as String?; + + if (serviceType != null) { + setState(() { + _serviceType = serviceType; + _serviceTypeController.text = _serviceType; + }); + } + }, + controller: _serviceTypeController, + style: const TextStyle( + color: Colors.black87, + fontWeight: FontWeight.w600, ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(50), - borderSide: BorderSide.none, - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(50), - borderSide: BorderSide.none, + readOnly: true, + decoration: InputDecoration( + hintText: + _serviceType == '' ? 'Servicio' : _serviceType, + hintStyle: const TextStyle( + color: Colors.black87, + fontWeight: FontWeight.w600), + border: InputBorder.none, + enabledBorder: InputBorder.none, ), ), - items: opc.map((e) { - return DropdownMenuItem( - value: e, - child: Text( - e, - style: - const TextStyle(fontWeight: FontWeight.bold), - ), - ); - }).toList(), - onChanged: (value) { - print(value); - }, ), ), ), @@ -319,29 +328,35 @@ class _ServiceScreenState extends State { child: Column( children: [ TextFormField( + readOnly: true, controller: _locationController, decoration: const InputDecoration( - prefixIcon: Icon(Icons.near_me), - hintText: 'Dirección'), + prefixIcon: Icon(Icons.near_me), + hintText: 'Dirección', + ), ), const SizedBox(height: 15), TextFormField( - // controller: _cedulaController, + controller: _profesionalController, readOnly: true, onTap: () async { - final String? profesional = + final Professional? profesional = await Navigator.push( context, CupertinoPageRoute( builder: (BuildContext context) { - return const ProfessionalScreen(); + return ProfessionalScreen( + profession: _serviceTypeController.text, + ); }, ), - ) as String?; + ); if (profesional != null) { setState(() { - // _profession = profesional; + _profesionalController.text = + profesional.name.toString(); + professionalId = profesional.id; }); } }, @@ -396,14 +411,36 @@ class _ServiceScreenState extends State { ), const SizedBox(height: 15), TextFormField( - // controller: _cedulaController, + controller: _observacionController, decoration: const InputDecoration( prefixIcon: Icon(Icons.message_outlined), hintText: 'Observaciones'), ), const SizedBox(height: 20), ElevatedButton( - onPressed: () {}, + onPressed: () { + int tiempo2 = ((_selectedTime!.hour * 3600 + + _selectedTime!.minute * 60) + + 7200) % + 86400; + + int hora2 = tiempo2 ~/ 3600; + int minutos2 = (tiempo2 % 3600) ~/ 60; + + TimeOfDay time2 = + TimeOfDay(hour: hora2, minute: minutos2); + + UserModel.getUser(uid.toString()).then((value) { + eventoService.createEvent( + value.name, + _observacionController.text, + '${_selectedDate}Z', + _selectedTime!.format(context), + time2.format(context), + professionalId, + ); + }); + }, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF2BA4EC), shape: RoundedRectangleBorder( diff --git a/lib/src/screens/service_type.dart b/lib/src/screens/service_type.dart new file mode 100644 index 0000000..c50522c --- /dev/null +++ b/lib/src/screens/service_type.dart @@ -0,0 +1,101 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:diacritic/diacritic.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:prosappco/src/components/pop_appbar.dart'; +import 'package:prosappco/src/screens/profession.dart'; +import 'package:prosappco/src/screens/service.dart'; + +class ServiceTypeScreen extends StatefulWidget { + const ServiceTypeScreen({super.key}); + + @override + State createState() => _ServiceTypeScreenState(); +} + +class _ServiceTypeScreenState extends State { + List? filteredProfessions; + TextEditingController searchController = TextEditingController(); + List? _professions; + + @override + void initState() { + super.initState(); + searchController.addListener(() { + setState(() { + if (_professions != null) { + if (searchController.text.isEmpty) { + filteredProfessions = _professions!; + } else { + filteredProfessions = _professions! + .where((profession) => removeDiacritics(profession) + .toLowerCase() + .contains( + removeDiacritics(searchController.text.toLowerCase()))) + .toList(); + } + } + }); + }); + + if (_professions == null) { + getProfessions().then((List element) => setState(() { + _professions = element; + filteredProfessions = element; + })); + } + } + + @override + Widget build(BuildContext context) { + if (filteredProfessions == null) { + return const Center( + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(Color(0xFF2BA4EC)), + ), + ); + } + var professions = filteredProfessions!; + + return Scaffold( + appBar: PopAppbar( + onPressed: () { + Navigator.pop(context); + }, + label: 'Tipo de servicio'), + body: Column( + children: [ + Padding( + padding: const EdgeInsets.all(10), + child: TextField( + controller: searchController, + decoration: const InputDecoration( + hintText: 'Escribe el tipo de servicio', + prefixIcon: Icon(Icons.assignment_ind_rounded), + ), + ), + ), + Expanded( + child: ListView.builder( + itemCount: professions.length, + itemBuilder: (BuildContext context, int index) { + return ListTile( + title: Text( + professions[index], + style: const TextStyle( + fontSize: 18.0, + color: Colors.black, + ), + ), + onTap: () { + Navigator.pop(context, professions[index]); + }, + ); + }, + ), + ), + ], + ), + ); + } +}