180 lines
9.6 KiB
Dart
180 lines
9.6 KiB
Dart
import 'package:flutter/material.dart';
|
|
import '../../core/app_colors.dart';
|
|
import '../../database/db_helper.dart';
|
|
|
|
class ManageConductorsScreen extends StatefulWidget {
|
|
const ManageConductorsScreen({super.key});
|
|
@override State<ManageConductorsScreen> createState() => _ManageConductorsScreenState();
|
|
}
|
|
|
|
class _ManageConductorsScreenState extends State<ManageConductorsScreen> {
|
|
List<Map<String, dynamic>> _conductores = [];
|
|
bool _loading = true;
|
|
|
|
@override void initState() { super.initState(); _load(); }
|
|
|
|
Future<void> _load() async {
|
|
final c = await DbHelper.getConductoresConMeta();
|
|
if (mounted) setState(() { _conductores = c; _loading = false; });
|
|
}
|
|
|
|
Future<void> _showFormDialog({Map<String, dynamic>? existing}) async {
|
|
final nombreCtrl = TextEditingController(text: existing?['nombre'] ?? '');
|
|
final emailCtrl = TextEditingController(text: existing?['email'] ?? '');
|
|
final passCtrl = TextEditingController();
|
|
final notasCtrl = TextEditingController(text: existing?['notas'] ?? '');
|
|
bool activo = (existing?['activo'] as int? ?? 1) == 1;
|
|
bool obscure = true;
|
|
|
|
await showDialog(context: context, builder: (ctx) => StatefulBuilder(
|
|
builder: (ctx, setSt) => AlertDialog(
|
|
title: Text(existing == null ? 'Nuevo Conductor' : 'Editar Conductor'),
|
|
content: SingleChildScrollView(child: Column(mainAxisSize: MainAxisSize.min, children: [
|
|
TextField(controller: nombreCtrl,
|
|
decoration: const InputDecoration(labelText: 'Nombre completo',
|
|
prefixIcon: Icon(Icons.person_outline), border: OutlineInputBorder())),
|
|
const SizedBox(height: 10),
|
|
TextField(controller: emailCtrl, keyboardType: TextInputType.emailAddress,
|
|
decoration: const InputDecoration(labelText: 'Correo electronico',
|
|
prefixIcon: Icon(Icons.email_outlined), border: OutlineInputBorder())),
|
|
const SizedBox(height: 10),
|
|
if (existing == null)
|
|
TextField(controller: passCtrl, obscureText: obscure,
|
|
decoration: InputDecoration(labelText: 'Contrasena',
|
|
prefixIcon: const Icon(Icons.lock_outline), border: const OutlineInputBorder(),
|
|
suffixIcon: IconButton(icon: Icon(obscure ? Icons.visibility_off : Icons.visibility),
|
|
onPressed: () => setSt(() => obscure = !obscure)))),
|
|
if (existing == null) const SizedBox(height: 10),
|
|
TextField(controller: notasCtrl, maxLines: 2,
|
|
decoration: const InputDecoration(labelText: 'Notas internas (opcional)',
|
|
border: OutlineInputBorder())),
|
|
const SizedBox(height: 10),
|
|
if (existing != null)
|
|
SwitchListTile(value: activo, dense: true,
|
|
title: Text(activo ? 'Conductor Activo' : 'Conductor Inactivo',
|
|
style: TextStyle(color: activo ? AppColors.verdeAdmin : AppColors.rojoError,
|
|
fontWeight: FontWeight.bold)),
|
|
activeColor: AppColors.verdeAdmin,
|
|
onChanged: (v) => setSt(() => activo = v)),
|
|
])),
|
|
actions: [
|
|
TextButton(onPressed: () => Navigator.pop(ctx), child: const Text('Cancelar')),
|
|
ElevatedButton(
|
|
style: ElevatedButton.styleFrom(backgroundColor: AppColors.verdeAdmin,
|
|
foregroundColor: Colors.white),
|
|
onPressed: () async {
|
|
if (nombreCtrl.text.trim().isEmpty || emailCtrl.text.trim().isEmpty) return;
|
|
if (existing == null) {
|
|
if (passCtrl.text.length < 6) {
|
|
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
|
|
content: Text('La contrasena debe tener al menos 6 caracteres'),
|
|
backgroundColor: AppColors.rojoError));
|
|
return;
|
|
}
|
|
await DbHelper.insertConductor(nombreCtrl.text.trim(),
|
|
emailCtrl.text.trim().toLowerCase(), passCtrl.text);
|
|
} else {
|
|
await DbHelper.updateConductor(existing['id'], nombreCtrl.text.trim(),
|
|
emailCtrl.text.trim().toLowerCase());
|
|
await DbHelper.updateConductorMeta(existing['id'], activo, notasCtrl.text.trim());
|
|
}
|
|
if (ctx.mounted) Navigator.pop(ctx);
|
|
await _load();
|
|
},
|
|
child: Text(existing == null ? 'Crear' : 'Guardar')),
|
|
])));
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) => Scaffold(
|
|
backgroundColor: AppColors.grisFondo,
|
|
appBar: AppBar(
|
|
backgroundColor: AppColors.verdeAdmin, foregroundColor: Colors.white,
|
|
title: Text('Conductores (${_conductores.length})'),
|
|
bottom: PreferredSize(preferredSize: const Size.fromHeight(4),
|
|
child: Container(height: 4, color: AppColors.dorado)),
|
|
actions: [
|
|
IconButton(icon: const Icon(Icons.refresh), onPressed: _load),
|
|
IconButton(icon: const Icon(Icons.add_circle_outline),
|
|
tooltip: 'Nuevo conductor',
|
|
onPressed: () => _showFormDialog()),
|
|
],
|
|
),
|
|
body: _loading
|
|
? const Center(child: CircularProgressIndicator())
|
|
: _conductores.isEmpty
|
|
? Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
|
const Icon(Icons.person_off, color: AppColors.grisTexto, size: 48),
|
|
const SizedBox(height: 12),
|
|
const Text('Sin conductores registrados',
|
|
style: TextStyle(color: AppColors.grisTexto)),
|
|
const SizedBox(height: 16),
|
|
ElevatedButton.icon(
|
|
style: ElevatedButton.styleFrom(backgroundColor: AppColors.verdeAdmin,
|
|
foregroundColor: Colors.white),
|
|
onPressed: () => _showFormDialog(),
|
|
icon: const Icon(Icons.add), label: const Text('Agregar primer conductor')),
|
|
]))
|
|
: ListView.builder(
|
|
padding: const EdgeInsets.all(12),
|
|
itemCount: _conductores.length,
|
|
itemBuilder: (_, i) {
|
|
final c = _conductores[i];
|
|
final activo = (c['activo'] as int? ?? 1) == 1;
|
|
final incidentes = c['total_incidentes'] as int? ?? 0;
|
|
return Card(
|
|
margin: const EdgeInsets.only(bottom: 10),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10),
|
|
side: BorderSide(color: activo
|
|
? AppColors.verdeAdmin.withOpacity(0.3)
|
|
: AppColors.rojoError.withOpacity(0.3))),
|
|
child: Padding(padding: const EdgeInsets.all(14), child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start, children: [
|
|
Row(children: [
|
|
CircleAvatar(radius: 22,
|
|
backgroundColor: activo
|
|
? AppColors.verdeAdmin.withOpacity(0.15)
|
|
: Colors.grey.shade200,
|
|
child: Icon(Icons.person,
|
|
color: activo ? AppColors.verdeAdmin : AppColors.grisTexto, size: 24)),
|
|
const SizedBox(width: 12),
|
|
Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
|
Text(c['nombre'] ?? '', style: const TextStyle(
|
|
fontWeight: FontWeight.bold, fontSize: 14)),
|
|
Text(c['email'] ?? '', style: const TextStyle(
|
|
color: AppColors.grisTexto, fontSize: 12)),
|
|
])),
|
|
Container(padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
|
decoration: BoxDecoration(
|
|
color: activo ? AppColors.verdeAdmin.withOpacity(0.1)
|
|
: AppColors.rojoError.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(10)),
|
|
child: Text(activo ? 'Activo' : 'Inactivo',
|
|
style: TextStyle(fontSize: 11, fontWeight: FontWeight.bold,
|
|
color: activo ? AppColors.verdeAdmin : AppColors.rojoError))),
|
|
IconButton(icon: const Icon(Icons.edit_outlined, size: 18),
|
|
onPressed: () => _showFormDialog(existing: c)),
|
|
]),
|
|
if (incidentes > 0 || (c['notas'] as String?)?.isNotEmpty == true) ...[
|
|
const Divider(height: 16),
|
|
if (incidentes > 0)
|
|
Row(children: [
|
|
Icon(Icons.warning_amber, size: 14,
|
|
color: incidentes > 3 ? AppColors.rojoError : AppColors.naranjaAlerta),
|
|
const SizedBox(width: 4),
|
|
Text('$incidentes incidente${incidentes != 1 ? 's' : ''} historico${incidentes != 1 ? 's' : ''}',
|
|
style: TextStyle(fontSize: 12,
|
|
color: incidentes > 3 ? AppColors.rojoError : AppColors.naranjaAlerta)),
|
|
]),
|
|
if ((c['notas'] as String?)?.isNotEmpty == true) ...[
|
|
const SizedBox(height: 4),
|
|
Text('Notas: ${c['notas']}',
|
|
style: const TextStyle(fontSize: 11, color: AppColors.grisTexto,
|
|
fontStyle: FontStyle.italic)),
|
|
],
|
|
],
|
|
])));
|
|
}),
|
|
);
|
|
}
|