replace: swap prosappweb content for prosapp_web_app (more complete version)
prosapp_web_app has chat, dashboard, calendar, support, 13 providers and Fluro URL routing. Keep Dockerfile + nginx.conf from previous prosappweb. Upgrade google_fonts 6.2.1 → 8.1.0 (Dart 3.12 compat fix). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
74a4f41902
commit
15175c1b91
@@ -0,0 +1,398 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:prosapp_web_app/models/schedules_entity.dart';
|
||||
import 'package:prosapp_web_app/models/service.dart';
|
||||
import 'package:prosapp_web_app/models/service_location_preferences.dart';
|
||||
import 'package:prosapp_web_app/models/service_status.dart';
|
||||
import 'package:prosapp_web_app/models/usuario.dart';
|
||||
import 'package:prosapp_web_app/models/usuario_profesional.dart';
|
||||
import 'package:prosapp_web_app/providers/auth_provider.dart';
|
||||
import 'package:prosapp_web_app/providers/cities_provider.dart';
|
||||
import 'package:prosapp_web_app/router/router.dart';
|
||||
import 'package:prosapp_web_app/services/navigation_service.dart';
|
||||
import 'package:prosapp_web_app/services/notifications_service.dart';
|
||||
import 'package:prosapp_web_app/ui/cards/white_card.dart';
|
||||
import 'package:prosapp_web_app/ui/inputs/custom_inputs.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:prosapp_web_app/utils/local_notifications.dart';
|
||||
import 'package:prosapp_web_app/utils/network_utility.dart';
|
||||
import 'package:prosapp_web_app/utils/time_of_day_extension.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class DashboardView extends StatefulWidget {
|
||||
const DashboardView({super.key});
|
||||
|
||||
@override
|
||||
State<DashboardView> createState() => _DashboardViewState();
|
||||
}
|
||||
|
||||
class _DashboardViewState extends State<DashboardView> {
|
||||
Usuario? user;
|
||||
List<dynamic> _placesList = [];
|
||||
final TextEditingController _addressController = TextEditingController();
|
||||
Timer? _debounce;
|
||||
UsuarioProfesional? selectedProfessional;
|
||||
String? selectedProfessionalName;
|
||||
DateTime? selectedDay;
|
||||
TimeOfDay? selectedHour;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
final authProvider = Provider.of<AuthProvider>(context, listen: false);
|
||||
|
||||
setState(() {
|
||||
user = authProvider.user;
|
||||
});
|
||||
}
|
||||
|
||||
void placeAutoComplete(String query, String _coords) async {
|
||||
Uri uri = Uri.https("admin.prosapp.co", "/autocomplete", {
|
||||
"input": query,
|
||||
"location": _coords,
|
||||
});
|
||||
|
||||
String? response = await NetworkUtility.fetchUrl(uri);
|
||||
|
||||
if (response != null) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_placesList = jsonDecode(response.toString())['results'];
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _selectProfessional(BuildContext context) async {
|
||||
try {
|
||||
List<dynamic> result = await NavigationService.navigateToFuture(
|
||||
Flurorouter.professionalsRoute);
|
||||
|
||||
final UsuarioProfesional selectedProfessional = result[0];
|
||||
|
||||
print('selectedProfessional: $selectedProfessional');
|
||||
|
||||
setState(() {
|
||||
selectedDay = result[1];
|
||||
selectedHour = result[2];
|
||||
selectedProfessionalName = selectedProfessional.user.name;
|
||||
this.selectedProfessional = selectedProfessional;
|
||||
_addressController.text = selectedProfessional.professionalInfo.address;
|
||||
});
|
||||
} catch (e) {
|
||||
print('debugeando $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_debounce?.cancel();
|
||||
_addressController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (user == null) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
bool isUserComplete() {
|
||||
return user!.name != '' &&
|
||||
user!.email != '' &&
|
||||
user!.phone != '' &&
|
||||
user!.city != '';
|
||||
}
|
||||
|
||||
final citiesProvider = Provider.of<CitiesProvider>(context);
|
||||
|
||||
if (citiesProvider.isLoading) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
final _coords = citiesProvider.getCoordsOfCity(user!.city!);
|
||||
|
||||
_createService() async {
|
||||
print('debug ${selectedProfessional?.user.id ?? 'user.id'}');
|
||||
print('debug ${user!.id ?? 'user.id'}');
|
||||
print('debug ${_addressController.text ?? 'user.id'}');
|
||||
|
||||
try {
|
||||
double latitude = 0.0;
|
||||
double longitude = 0.0;
|
||||
|
||||
print('debug 1');
|
||||
|
||||
Service service = Service(
|
||||
id: null,
|
||||
professionalId: selectedProfessional!.user.id,
|
||||
professionalScored: false,
|
||||
userId: user!.id,
|
||||
userScored: false,
|
||||
address: _addressController.text,
|
||||
aditionalAddress: '',
|
||||
latitude: latitude,
|
||||
longitude: longitude,
|
||||
day: selectedDay.toString(),
|
||||
createdAt: Timestamp.now(),
|
||||
description: '',
|
||||
range1Hour1: selectedHour!,
|
||||
range1Hour2: selectedHour!.add(hour: 2),
|
||||
rate: '',
|
||||
status: ServiceStatus.pending,
|
||||
location: ServiceLocationPreferences.delivery,
|
||||
);
|
||||
|
||||
print('debug 1');
|
||||
|
||||
await FirebaseFirestore.instance
|
||||
.collection('services')
|
||||
.add(service.toDocument());
|
||||
|
||||
print('debug 2');
|
||||
|
||||
NotificationsService.showSnackbar('Servicio solicitado exitosamente');
|
||||
|
||||
if (selectedProfessional != null) {
|
||||
if (selectedProfessional!.user.token != null) {
|
||||
LocalNotifications.sendPushNotification(
|
||||
selectedProfessional!.user.token!,
|
||||
'Nuevo servicio',
|
||||
'Tienes una nueva solicitud de servicio pendiente',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_addressController.clear();
|
||||
setState(() {
|
||||
selectedProfessional = null;
|
||||
selectedProfessionalName = null;
|
||||
selectedDay = null;
|
||||
selectedHour = null;
|
||||
});
|
||||
} catch (e) {
|
||||
NotificationsService.showSnackBarError(
|
||||
'$e Error al solicitar el servicio, intenta de nuevo');
|
||||
}
|
||||
}
|
||||
|
||||
return Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 900),
|
||||
child: Stack(
|
||||
children: [
|
||||
ListView(
|
||||
physics: const ClampingScrollPhysics(),
|
||||
children: [
|
||||
WhiteCard(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 10),
|
||||
isUserComplete()
|
||||
? TextFormField(
|
||||
controller: _addressController,
|
||||
onChanged: (value) {
|
||||
if (_debounce?.isActive ?? false)
|
||||
_debounce?.cancel();
|
||||
|
||||
_debounce = Timer(
|
||||
const Duration(milliseconds: 500), () {
|
||||
String modifiedValue =
|
||||
value.replaceAll(' ', '_');
|
||||
placeAutoComplete(modifiedValue, _coords);
|
||||
});
|
||||
},
|
||||
decoration: CustomInputs.formInputDecoration(
|
||||
hint: 'Ingresa tu dirección',
|
||||
label: 'Dirección',
|
||||
icon: Icons.location_on,
|
||||
),
|
||||
)
|
||||
: GestureDetector(
|
||||
onTap: () => NotificationsService.showSnackBarError(
|
||||
'Completa tu perfil para solicitar un servicio'),
|
||||
child: AbsorbPointer(
|
||||
child: TextFormField(
|
||||
decoration: CustomInputs.formInputDecoration(
|
||||
hint: 'Ingresa tu dirección',
|
||||
label: 'Dirección',
|
||||
icon: Icons.location_on,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
GestureDetector(
|
||||
onTap: () => isUserComplete()
|
||||
? _selectProfessional(context)
|
||||
: NotificationsService.showSnackBarError(
|
||||
'Completa tu perfil para solicitar un servicio'),
|
||||
child: AbsorbPointer(
|
||||
child: TextFormField(
|
||||
controller: TextEditingController(
|
||||
text: selectedProfessionalName,
|
||||
),
|
||||
decoration: CustomInputs.formInputDecoration(
|
||||
hint: 'Selecciona un Profesional',
|
||||
label: 'Profesional',
|
||||
icon: Icons.person_rounded,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
if (selectedDay != null && selectedHour != null) ...[
|
||||
TextFormField(
|
||||
readOnly: true,
|
||||
controller: TextEditingController(
|
||||
text: selectedDay == null
|
||||
? ''
|
||||
: DateFormat('dd/MM/yyyy').format(selectedDay!),
|
||||
),
|
||||
decoration: CustomInputs.formInputDecoration(
|
||||
hint: 'Fecha',
|
||||
label: 'Fecha',
|
||||
icon: Icons.calendar_month,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
TextFormField(
|
||||
readOnly: true,
|
||||
controller: TextEditingController(
|
||||
text: selectedHour == null
|
||||
? ''
|
||||
: ScheduleEntity.getFormatTime(selectedHour),
|
||||
),
|
||||
decoration: CustomInputs.formInputDecoration(
|
||||
hint: 'Hora',
|
||||
label: 'Hora',
|
||||
icon: Icons.watch_later_outlined,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
const SizedBox(height: 10),
|
||||
Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 230),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
if (!isUserComplete()) {
|
||||
NotificationsService.showSnackBarError(
|
||||
'Completa tu perfil para solicitar un servicio');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_createService();
|
||||
},
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty.all(
|
||||
Colors.blue.shade400),
|
||||
shape: WidgetStateProperty.all(
|
||||
const RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(5)),
|
||||
)),
|
||||
shadowColor: WidgetStateProperty.all(
|
||||
Colors.transparent),
|
||||
),
|
||||
child: const Text(
|
||||
'Solicitar cita',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
if (selectedDay != null &&
|
||||
selectedHour != null &&
|
||||
selectedProfessional != null) ...[
|
||||
const SizedBox(width: 10),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
_addressController.clear();
|
||||
selectedProfessional = null;
|
||||
selectedDay = null;
|
||||
selectedHour = null;
|
||||
|
||||
setState(() {});
|
||||
},
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty.all(
|
||||
Colors.red,
|
||||
),
|
||||
shape: WidgetStateProperty.all(
|
||||
const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(5)))),
|
||||
shadowColor: WidgetStateProperty.all(
|
||||
Colors.transparent)),
|
||||
child: const Icon(
|
||||
Icons.close_rounded,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Positioned(
|
||||
left: 20,
|
||||
right: 20,
|
||||
top: 80,
|
||||
child: _placesList.isNotEmpty
|
||||
? Material(
|
||||
elevation: 5.0,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: _placesList.length,
|
||||
itemBuilder: (context, index) {
|
||||
return ListTile(
|
||||
title:
|
||||
Text(_placesList[index]['formatted_address']),
|
||||
dense: true,
|
||||
visualDensity: VisualDensity.compact,
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_addressController.text =
|
||||
_placesList[index]['formatted_address'];
|
||||
_placesList = [];
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
: Container(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user