Actualiza buzon de sugerencias
This commit is contained in:
237
lib/main.dart
237
lib/main.dart
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
@@ -10,7 +9,7 @@ void main() {
|
|||||||
runApp(const RecolectorApp());
|
runApp(const RecolectorApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ======================= JSON LOCAL DE RUTAS ======================= */
|
// ======================= JSON LOCAL DE RUTAS =======================
|
||||||
|
|
||||||
const String rutasJson = '''
|
const String rutasJson = '''
|
||||||
[
|
[
|
||||||
@@ -72,7 +71,7 @@ const String rutasJson = '''
|
|||||||
]
|
]
|
||||||
''';
|
''';
|
||||||
|
|
||||||
/* ======================= MODELOS ======================= */
|
// ======================= MODELOS =======================
|
||||||
|
|
||||||
class Ruta {
|
class Ruta {
|
||||||
final String id;
|
final String id;
|
||||||
@@ -99,15 +98,15 @@ class Ruta {
|
|||||||
|
|
||||||
factory Ruta.fromJson(Map<String, dynamic> json) {
|
factory Ruta.fromJson(Map<String, dynamic> json) {
|
||||||
return Ruta(
|
return Ruta(
|
||||||
id: json['id'],
|
id: json['id'] ?? '',
|
||||||
zona: json['zona'],
|
zona: json['zona'] ?? '',
|
||||||
keywords: List<String>.from(json['keywords']),
|
keywords: List<String>.from(json['keywords'] ?? []),
|
||||||
dias: List<String>.from(json['dias']),
|
dias: List<String>.from(json['dias'] ?? []),
|
||||||
inicio: json['inicio'],
|
inicio: json['inicio'] ?? '',
|
||||||
fin: json['fin'],
|
fin: json['fin'] ?? '',
|
||||||
proxima: json['proxima'],
|
proxima: json['proxima'] ?? '',
|
||||||
eta: json['eta'],
|
eta: json['eta'] ?? '',
|
||||||
consejo: json['consejo'],
|
consejo: json['consejo'] ?? '',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +177,7 @@ class Servicio {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ======================= PERSISTENCIA Y RUTAS ======================= */
|
// ======================= REPOSITORIO =======================
|
||||||
|
|
||||||
class Repo {
|
class Repo {
|
||||||
static List<Ruta> rutas() {
|
static List<Ruta> rutas() {
|
||||||
@@ -240,7 +239,6 @@ class Repo {
|
|||||||
return list.map((e) => Domicilio.fromJson(Map<String, dynamic>.from(e))).toList();
|
return list.map((e) => Domicilio.fromJson(Map<String, dynamic>.from(e))).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compatibilidad con versiones anteriores del proyecto.
|
|
||||||
final principal = prefs.getString('domicilio') ?? '';
|
final principal = prefs.getString('domicilio') ?? '';
|
||||||
final colonia = prefs.getString('colonia') ?? '';
|
final colonia = prefs.getString('colonia') ?? '';
|
||||||
final negocio = prefs.getString('negocio') ?? '';
|
final negocio = prefs.getString('negocio') ?? '';
|
||||||
@@ -305,7 +303,7 @@ class Repo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ======================= ESTILO ======================= */
|
// ======================= ESTILO =======================
|
||||||
|
|
||||||
class AppColors {
|
class AppColors {
|
||||||
static const green = Color(0xFF2E7D32);
|
static const green = Color(0xFF2E7D32);
|
||||||
@@ -392,7 +390,7 @@ class AppCard extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ======================= LOGIN ======================= */
|
// ======================= LOGIN =======================
|
||||||
|
|
||||||
class LoginPage extends StatefulWidget {
|
class LoginPage extends StatefulWidget {
|
||||||
const LoginPage({super.key});
|
const LoginPage({super.key});
|
||||||
@@ -504,7 +502,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ======================= HOME ======================= */
|
// ======================= HOME =======================
|
||||||
|
|
||||||
class HomePage extends StatefulWidget {
|
class HomePage extends StatefulWidget {
|
||||||
const HomePage({super.key});
|
const HomePage({super.key});
|
||||||
@@ -516,7 +514,6 @@ class HomePage extends StatefulWidget {
|
|||||||
class _HomePageState extends State<HomePage> {
|
class _HomePageState extends State<HomePage> {
|
||||||
List<Domicilio> domicilios = [];
|
List<Domicilio> domicilios = [];
|
||||||
List<Servicio> servicios = [];
|
List<Servicio> servicios = [];
|
||||||
final sugerencia = TextEditingController();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -536,26 +533,6 @@ class _HomePageState extends State<HomePage> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> enviarSugerencia() async {
|
|
||||||
final texto = sugerencia.text.trim();
|
|
||||||
|
|
||||||
if (texto.isEmpty) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(content: Text('Escribe una queja o sugerencia')),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await Repo.guardarSugerencia(texto);
|
|
||||||
sugerencia.clear();
|
|
||||||
|
|
||||||
if (!mounted) return;
|
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(content: Text('Sugerencia enviada correctamente')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget menuCard({
|
Widget menuCard({
|
||||||
required IconData icon,
|
required IconData icon,
|
||||||
required String title,
|
required String title,
|
||||||
@@ -578,12 +555,6 @@ class _HomePageState extends State<HomePage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
sugerencia.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final principal = domicilios.isEmpty ? null : domicilios.first;
|
final principal = domicilios.isEmpty ? null : domicilios.first;
|
||||||
@@ -687,35 +658,13 @@ class _HomePageState extends State<HomePage> {
|
|||||||
Navigator.push(context, MaterialPageRoute(builder: (_) => const GuiaPage()));
|
Navigator.push(context, MaterialPageRoute(builder: (_) => const GuiaPage()));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SectionTitle(
|
menuCard(
|
||||||
'Buzón de sugerencias',
|
icon: Icons.feedback,
|
||||||
subtitle: 'Widget principal para quejas y comentarios.',
|
title: 'Buzón de sugerencias',
|
||||||
),
|
subtitle: 'Envía quejas, reportes o comentarios del servicio.',
|
||||||
AppCard(
|
onTap: () {
|
||||||
child: Column(
|
Navigator.push(context, MaterialPageRoute(builder: (_) => const BuzonPage()));
|
||||||
children: [
|
},
|
||||||
TextField(
|
|
||||||
controller: sugerencia,
|
|
||||||
minLines: 3,
|
|
||||||
maxLines: 5,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: 'Queja o sugerencia',
|
|
||||||
hintText: 'Ejemplo: el camión pasó fuera del horario indicado',
|
|
||||||
prefixIcon: Icon(Icons.feedback),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
SizedBox(
|
|
||||||
height: 52,
|
|
||||||
width: double.infinity,
|
|
||||||
child: FilledButton.icon(
|
|
||||||
onPressed: enviarSugerencia,
|
|
||||||
icon: const Icon(Icons.send),
|
|
||||||
label: const Text('Enviar sugerencia', style: TextStyle(fontSize: 17)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (servicios.isNotEmpty) ...[
|
if (servicios.isNotEmpty) ...[
|
||||||
const SectionTitle('Último servicio'),
|
const SectionTitle('Último servicio'),
|
||||||
@@ -734,7 +683,143 @@ class _HomePageState extends State<HomePage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ======================= DATOS ======================= */
|
// ======================= BUZÓN =======================
|
||||||
|
|
||||||
|
class BuzonPage extends StatefulWidget {
|
||||||
|
const BuzonPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<BuzonPage> createState() => _BuzonPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BuzonPageState extends State<BuzonPage> {
|
||||||
|
final comentario = TextEditingController();
|
||||||
|
String tipoReporte = 'Sugerencia';
|
||||||
|
|
||||||
|
Future<void> enviar() async {
|
||||||
|
final texto = comentario.text.trim();
|
||||||
|
|
||||||
|
if (texto.isEmpty) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text('Escribe tu comentario antes de enviarlo')),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Repo.guardarSugerencia('[$tipoReporte] $texto');
|
||||||
|
comentario.clear();
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text('Reporte enviado correctamente')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void limpiar() {
|
||||||
|
setState(() {
|
||||||
|
tipoReporte = 'Sugerencia';
|
||||||
|
comentario.clear();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
comentario.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Buzón de sugerencias'),
|
||||||
|
),
|
||||||
|
body: ListView(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
children: [
|
||||||
|
AppCard(
|
||||||
|
color: AppColors.softGreen,
|
||||||
|
child: Column(
|
||||||
|
children: const [
|
||||||
|
Icon(Icons.feedback, size: 72, color: AppColors.green),
|
||||||
|
SizedBox(height: 12),
|
||||||
|
Text(
|
||||||
|
'Tu opinión ayuda a mejorar el servicio',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(fontSize: 24, fontWeight: FontWeight.w900),
|
||||||
|
),
|
||||||
|
SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'Envía reportes, quejas o sugerencias sobre la recolección de basura.',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(fontSize: 16.5),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SectionTitle('Tipo de reporte'),
|
||||||
|
DropdownButtonFormField<String>(
|
||||||
|
value: tipoReporte,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Selecciona una opción',
|
||||||
|
prefixIcon: Icon(Icons.category),
|
||||||
|
),
|
||||||
|
items: const [
|
||||||
|
DropdownMenuItem(value: 'Sugerencia', child: Text('Sugerencia')),
|
||||||
|
DropdownMenuItem(value: 'Queja', child: Text('Queja')),
|
||||||
|
DropdownMenuItem(value: 'Retraso del camión', child: Text('Retraso del camión')),
|
||||||
|
DropdownMenuItem(value: 'Camión no pasó', child: Text('Camión no pasó')),
|
||||||
|
DropdownMenuItem(value: 'Basura no recolectada', child: Text('Basura no recolectada')),
|
||||||
|
],
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
tipoReporte = value ?? 'Sugerencia';
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SectionTitle('Describe el problema'),
|
||||||
|
TextField(
|
||||||
|
controller: comentario,
|
||||||
|
minLines: 5,
|
||||||
|
maxLines: 8,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Comentario',
|
||||||
|
hintText: 'Ejemplo: el camión no pasó por mi domicilio el día indicado',
|
||||||
|
prefixIcon: Icon(Icons.edit_note),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
SizedBox(
|
||||||
|
height: 54,
|
||||||
|
child: FilledButton.icon(
|
||||||
|
onPressed: enviar,
|
||||||
|
icon: const Icon(Icons.send),
|
||||||
|
label: const Text(
|
||||||
|
'Enviar reporte',
|
||||||
|
style: TextStyle(fontSize: 17),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
SizedBox(
|
||||||
|
height: 54,
|
||||||
|
child: OutlinedButton.icon(
|
||||||
|
onPressed: limpiar,
|
||||||
|
icon: const Icon(Icons.cleaning_services),
|
||||||
|
label: const Text(
|
||||||
|
'Limpiar formulario',
|
||||||
|
style: TextStyle(fontSize: 17),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================= DATOS =======================
|
||||||
|
|
||||||
class DatosPage extends StatefulWidget {
|
class DatosPage extends StatefulWidget {
|
||||||
const DatosPage({super.key});
|
const DatosPage({super.key});
|
||||||
@@ -1063,7 +1148,7 @@ class _DatosPageState extends State<DatosPage> with SingleTickerProviderStateMix
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ======================= SEGUIMIENTO ======================= */
|
// ======================= SEGUIMIENTO =======================
|
||||||
|
|
||||||
enum EventoCamion { normal, retraso, averia }
|
enum EventoCamion { normal, retraso, averia }
|
||||||
|
|
||||||
@@ -1543,7 +1628,7 @@ class _SeguimientoPageState extends State<SeguimientoPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ======================= GUÍA ======================= */
|
// ======================= GUÍA =======================
|
||||||
|
|
||||||
class GuiaPage extends StatelessWidget {
|
class GuiaPage extends StatelessWidget {
|
||||||
const GuiaPage({super.key});
|
const GuiaPage({super.key});
|
||||||
|
|||||||
Reference in New Issue
Block a user