Co-authored-by: Azareth-Tr <Azareth-Tr@users.noreply.github.com> Co-authored-by: eddgranados12 <eddgranados12@users.noreply.github.com> vistas de mockup
813 lines
25 KiB
Dart
813 lines
25 KiB
Dart
import 'package:flutter/material.dart';
|
|
import '../theme/app_theme.dart';
|
|
import '../widgets/widgets.dart' as w;
|
|
|
|
enum TruckStatus { disponible, enRuta, mantenimiento, detenido }
|
|
|
|
extension TruckStatusX on TruckStatus {
|
|
String get label {
|
|
switch (this) {
|
|
case TruckStatus.disponible:
|
|
return 'Disponible';
|
|
case TruckStatus.enRuta:
|
|
return 'En ruta';
|
|
case TruckStatus.mantenimiento:
|
|
return 'Mantenimiento';
|
|
case TruckStatus.detenido:
|
|
return 'Detenido';
|
|
}
|
|
}
|
|
|
|
w.StatusBadge get badge {
|
|
switch (this) {
|
|
case TruckStatus.disponible:
|
|
return w.StatusBadge.green(label);
|
|
case TruckStatus.enRuta:
|
|
return w.StatusBadge.amber(label);
|
|
case TruckStatus.mantenimiento:
|
|
return w.StatusBadge.gray(label);
|
|
case TruckStatus.detenido:
|
|
return w.StatusBadge.gray(label);
|
|
}
|
|
}
|
|
}
|
|
|
|
class AdminUser {
|
|
final String id;
|
|
final String nombre;
|
|
final String apellido;
|
|
final String email;
|
|
final String telefono;
|
|
|
|
const AdminUser({
|
|
required this.id,
|
|
required this.nombre,
|
|
required this.apellido,
|
|
required this.email,
|
|
required this.telefono,
|
|
});
|
|
|
|
String get nombreCompleto => '$nombre $apellido';
|
|
String get iniciales =>
|
|
'${nombre.isNotEmpty ? nombre[0] : ''}${apellido.isNotEmpty ? apellido[0] : ''}'
|
|
.toUpperCase();
|
|
|
|
AdminUser copyWith({
|
|
String? nombre,
|
|
String? apellido,
|
|
String? email,
|
|
String? telefono,
|
|
}) {
|
|
return AdminUser(
|
|
id: id,
|
|
nombre: nombre ?? this.nombre,
|
|
apellido: apellido ?? this.apellido,
|
|
email: email ?? this.email,
|
|
telefono: telefono ?? this.telefono,
|
|
);
|
|
}
|
|
}
|
|
|
|
class AdminRoute {
|
|
final String id;
|
|
final String nombre;
|
|
final String zona;
|
|
final bool activa;
|
|
|
|
const AdminRoute({
|
|
required this.id,
|
|
required this.nombre,
|
|
required this.zona,
|
|
this.activa = true,
|
|
});
|
|
|
|
AdminRoute copyWith({
|
|
String? nombre,
|
|
String? zona,
|
|
bool? activa,
|
|
}) {
|
|
return AdminRoute(
|
|
id: id,
|
|
nombre: nombre ?? this.nombre,
|
|
zona: zona ?? this.zona,
|
|
activa: activa ?? this.activa,
|
|
);
|
|
}
|
|
}
|
|
|
|
class AdminTruck {
|
|
final String id;
|
|
final String placas;
|
|
final String modelo;
|
|
final String conductor;
|
|
final TruckStatus status;
|
|
final String rutaId;
|
|
|
|
const AdminTruck({
|
|
required this.id,
|
|
required this.placas,
|
|
required this.modelo,
|
|
required this.conductor,
|
|
required this.status,
|
|
required this.rutaId,
|
|
});
|
|
|
|
AdminTruck copyWith({
|
|
String? placas,
|
|
String? modelo,
|
|
String? conductor,
|
|
TruckStatus? status,
|
|
String? rutaId,
|
|
}) {
|
|
return AdminTruck(
|
|
id: id,
|
|
placas: placas ?? this.placas,
|
|
modelo: modelo ?? this.modelo,
|
|
conductor: conductor ?? this.conductor,
|
|
status: status ?? this.status,
|
|
rutaId: rutaId ?? this.rutaId,
|
|
);
|
|
}
|
|
}
|
|
|
|
class AdminScreen extends StatefulWidget {
|
|
const AdminScreen({super.key});
|
|
|
|
@override
|
|
State<AdminScreen> createState() => _AdminScreenState();
|
|
}
|
|
|
|
class _AdminScreenState extends State<AdminScreen>
|
|
with SingleTickerProviderStateMixin {
|
|
late final TabController _tabController;
|
|
int _activeTab = 0;
|
|
|
|
final List<AdminUser> _usuarios = [
|
|
const AdminUser(
|
|
id: 'user-01',
|
|
nombre: 'Laura',
|
|
apellido: 'Gómez',
|
|
email: 'laura.gomez@rutaverde.com',
|
|
telefono: '+52 461 987 1234',
|
|
),
|
|
const AdminUser(
|
|
id: 'user-02',
|
|
nombre: 'Miguel',
|
|
apellido: 'Sánchez',
|
|
email: 'miguel.sanchez@rutaverde.com',
|
|
telefono: '+52 461 123 7890',
|
|
),
|
|
];
|
|
|
|
final List<AdminRoute> _rutas = [
|
|
const AdminRoute(
|
|
id: 'ruta-01',
|
|
nombre: 'Ruta Norte',
|
|
zona: 'Zona Norte',
|
|
),
|
|
const AdminRoute(
|
|
id: 'ruta-02',
|
|
nombre: 'Ruta Sur',
|
|
zona: 'Zona Sur',
|
|
activa: false,
|
|
),
|
|
];
|
|
|
|
final List<AdminTruck> _camiones = [
|
|
const AdminTruck(
|
|
id: 'truck-01',
|
|
placas: 'ABC-1234',
|
|
modelo: 'Volvo FH',
|
|
conductor: 'Javier Pérez',
|
|
status: TruckStatus.enRuta,
|
|
rutaId: 'ruta-01',
|
|
),
|
|
const AdminTruck(
|
|
id: 'truck-02',
|
|
placas: 'DEF-5678',
|
|
modelo: 'Mercedes 1830',
|
|
conductor: 'Ana Díaz',
|
|
status: TruckStatus.disponible,
|
|
rutaId: 'ruta-02',
|
|
),
|
|
];
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_tabController = TabController(length: 3, vsync: this)
|
|
..addListener(() {
|
|
if (_tabController.indexIsChanging) return;
|
|
setState(() => _activeTab = _tabController.index);
|
|
});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_tabController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
backgroundColor: AppTheme.background,
|
|
appBar: AppBar(
|
|
title: const Text('Panel de administración'),
|
|
bottom: TabBar(
|
|
controller: _tabController,
|
|
indicatorColor: AppTheme.primary,
|
|
tabs: const [
|
|
Tab(text: 'Usuarios'),
|
|
Tab(text: 'Rutas'),
|
|
Tab(text: 'Camiones'),
|
|
],
|
|
),
|
|
),
|
|
body: TabBarView(
|
|
controller: _tabController,
|
|
children: [
|
|
_buildUsersTab(),
|
|
_buildRoutesTab(),
|
|
_buildTrucksTab(),
|
|
],
|
|
),
|
|
floatingActionButton: FloatingActionButton.extended(
|
|
onPressed: () {
|
|
if (_activeTab == 0) {
|
|
_showUserForm();
|
|
} else if (_activeTab == 1) {
|
|
_showRouteForm();
|
|
} else {
|
|
_showTruckForm();
|
|
}
|
|
},
|
|
label: Text(_activeTab == 0
|
|
? 'Nuevo usuario'
|
|
: _activeTab == 1
|
|
? 'Nueva ruta'
|
|
: 'Nuevo camión'),
|
|
icon: const Icon(Icons.add),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildUsersTab() {
|
|
if (_usuarios.isEmpty) {
|
|
return _buildEmptyState('No hay usuarios registrados aún.');
|
|
}
|
|
|
|
return ListView.separated(
|
|
padding: const EdgeInsets.all(16),
|
|
itemCount: _usuarios.length,
|
|
separatorBuilder: (_, __) => const SizedBox(height: 12),
|
|
itemBuilder: (context, index) {
|
|
final user = _usuarios[index];
|
|
return w.AppCard(
|
|
child: Row(
|
|
children: [
|
|
CircleAvatar(
|
|
backgroundColor: AppTheme.primaryLight,
|
|
foregroundColor: AppTheme.primary,
|
|
child: Text(user.iniciales),
|
|
),
|
|
const SizedBox(width: 14),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(user.nombreCompleto,
|
|
style: const TextStyle(
|
|
fontSize: 15, fontWeight: FontWeight.w600)),
|
|
const SizedBox(height: 4),
|
|
Text(user.email,
|
|
style: const TextStyle(
|
|
fontSize: 13, color: AppTheme.textSecondary)),
|
|
const SizedBox(height: 2),
|
|
Text(user.telefono, style: const TextStyle(fontSize: 13)),
|
|
],
|
|
),
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.edit_outlined, color: AppTheme.primary),
|
|
onPressed: () => _showUserForm(user: user),
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.delete_outline, color: AppTheme.danger),
|
|
onPressed: () => _confirmDeleteUser(user),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _buildRoutesTab() {
|
|
if (_rutas.isEmpty) {
|
|
return _buildEmptyState('No hay rutas registradas aún.');
|
|
}
|
|
|
|
return ListView.separated(
|
|
padding: const EdgeInsets.all(16),
|
|
itemCount: _rutas.length,
|
|
separatorBuilder: (_, __) => const SizedBox(height: 12),
|
|
itemBuilder: (context, index) {
|
|
final ruta = _rutas[index];
|
|
return w.AppCard(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Text(ruta.nombre,
|
|
style: const TextStyle(
|
|
fontSize: 15, fontWeight: FontWeight.w600)),
|
|
),
|
|
Text(ruta.activa ? 'Activa' : 'Inactiva',
|
|
style: TextStyle(
|
|
fontSize: 13,
|
|
color: ruta.activa
|
|
? AppTheme.primary
|
|
: AppTheme.textSecondary)),
|
|
],
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text('Zona ${ruta.zona}',
|
|
style: const TextStyle(
|
|
fontSize: 13, color: AppTheme.textSecondary)),
|
|
const SizedBox(height: 12),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
children: [
|
|
TextButton.icon(
|
|
onPressed: () => _showRouteForm(route: ruta),
|
|
icon: const Icon(Icons.edit_outlined, size: 18),
|
|
label: const Text('Editar'),
|
|
),
|
|
const SizedBox(width: 8),
|
|
TextButton.icon(
|
|
onPressed: () => _confirmDeleteRoute(ruta),
|
|
icon: const Icon(Icons.delete_outline, size: 18),
|
|
label: const Text('Eliminar'),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _buildTrucksTab() {
|
|
if (_camiones.isEmpty) {
|
|
return _buildEmptyState('No hay camiones registrados aún.');
|
|
}
|
|
|
|
return ListView.separated(
|
|
padding: const EdgeInsets.all(16),
|
|
itemCount: _camiones.length,
|
|
separatorBuilder: (_, __) => const SizedBox(height: 12),
|
|
itemBuilder: (context, index) {
|
|
final truck = _camiones[index];
|
|
final route = _rutas.firstWhere(
|
|
(route) => route.id == truck.rutaId,
|
|
orElse: () =>
|
|
const AdminRoute(id: 'none', nombre: 'Sin ruta', zona: ''),
|
|
);
|
|
|
|
return w.AppCard(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Text(truck.placas,
|
|
style: const TextStyle(
|
|
fontSize: 15, fontWeight: FontWeight.w600)),
|
|
),
|
|
truck.status.badge,
|
|
],
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text('${truck.modelo} · ${truck.conductor}',
|
|
style: const TextStyle(fontSize: 13)),
|
|
const SizedBox(height: 4),
|
|
Text('Ruta: ${route.nombre}',
|
|
style: const TextStyle(
|
|
fontSize: 13, color: AppTheme.textSecondary)),
|
|
const SizedBox(height: 12),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
children: [
|
|
TextButton.icon(
|
|
onPressed: () => _showTruckForm(truck: truck),
|
|
icon: const Icon(Icons.edit_outlined, size: 18),
|
|
label: const Text('Editar'),
|
|
),
|
|
const SizedBox(width: 8),
|
|
TextButton.icon(
|
|
onPressed: () => _confirmDeleteTruck(truck),
|
|
icon: const Icon(Icons.delete_outline, size: 18),
|
|
label: const Text('Eliminar'),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _buildEmptyState(String message) {
|
|
return Center(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(24),
|
|
child: Text(message,
|
|
textAlign: TextAlign.center,
|
|
style: const TextStyle(
|
|
fontSize: 15,
|
|
color: AppTheme.textSecondary,
|
|
)),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _confirmDeleteUser(AdminUser user) {
|
|
showDialog(
|
|
context: context,
|
|
builder: (ctx) => AlertDialog(
|
|
backgroundColor: AppTheme.surface,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(AppTheme.radiusLg)),
|
|
title: const Text('Eliminar usuario'),
|
|
content: const Text('¿Deseas eliminar este usuario?'),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(ctx),
|
|
style:
|
|
TextButton.styleFrom(foregroundColor: AppTheme.textSecondary),
|
|
child: const Text('Cancelar'),
|
|
),
|
|
TextButton(
|
|
onPressed: () {
|
|
setState(
|
|
() => _usuarios.removeWhere((item) => item.id == user.id));
|
|
Navigator.pop(ctx);
|
|
},
|
|
style: TextButton.styleFrom(foregroundColor: AppTheme.danger),
|
|
child: const Text('Eliminar'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
void _confirmDeleteRoute(AdminRoute route) {
|
|
showDialog(
|
|
context: context,
|
|
builder: (ctx) => AlertDialog(
|
|
backgroundColor: AppTheme.surface,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(AppTheme.radiusLg)),
|
|
title: const Text('Eliminar ruta'),
|
|
content: const Text('¿Deseas eliminar esta ruta?'),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(ctx),
|
|
style:
|
|
TextButton.styleFrom(foregroundColor: AppTheme.textSecondary),
|
|
child: const Text('Cancelar'),
|
|
),
|
|
TextButton(
|
|
onPressed: () {
|
|
setState(() => _rutas.removeWhere((item) => item.id == route.id));
|
|
Navigator.pop(ctx);
|
|
},
|
|
style: TextButton.styleFrom(foregroundColor: AppTheme.danger),
|
|
child: const Text('Eliminar'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
void _confirmDeleteTruck(AdminTruck truck) {
|
|
showDialog(
|
|
context: context,
|
|
builder: (ctx) => AlertDialog(
|
|
backgroundColor: AppTheme.surface,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(AppTheme.radiusLg)),
|
|
title: const Text('Eliminar camión'),
|
|
content: const Text('¿Deseas eliminar este camión?'),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(ctx),
|
|
style:
|
|
TextButton.styleFrom(foregroundColor: AppTheme.textSecondary),
|
|
child: const Text('Cancelar'),
|
|
),
|
|
TextButton(
|
|
onPressed: () {
|
|
setState(
|
|
() => _camiones.removeWhere((item) => item.id == truck.id));
|
|
Navigator.pop(ctx);
|
|
},
|
|
style: TextButton.styleFrom(foregroundColor: AppTheme.danger),
|
|
child: const Text('Eliminar'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showUserForm({AdminUser? user}) {
|
|
final formKey = GlobalKey<FormState>();
|
|
final nombreCtrl = TextEditingController(text: user?.nombre);
|
|
final apellidoCtrl = TextEditingController(text: user?.apellido);
|
|
final emailCtrl = TextEditingController(text: user?.email);
|
|
final telefonoCtrl = TextEditingController(text: user?.telefono);
|
|
|
|
showDialog(
|
|
context: context,
|
|
builder: (ctx) => AlertDialog(
|
|
backgroundColor: AppTheme.surface,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(AppTheme.radiusLg),
|
|
),
|
|
title: Text(user == null ? 'Nuevo usuario' : 'Editar usuario'),
|
|
content: Form(
|
|
key: formKey,
|
|
child: SingleChildScrollView(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
TextFormField(
|
|
controller: nombreCtrl,
|
|
decoration: const InputDecoration(labelText: 'Nombre'),
|
|
validator: (value) =>
|
|
value?.trim().isEmpty == true ? 'Requerido' : null,
|
|
),
|
|
TextFormField(
|
|
controller: apellidoCtrl,
|
|
decoration: const InputDecoration(labelText: 'Apellido'),
|
|
validator: (value) =>
|
|
value?.trim().isEmpty == true ? 'Requerido' : null,
|
|
),
|
|
TextFormField(
|
|
controller: emailCtrl,
|
|
decoration: const InputDecoration(labelText: 'Correo'),
|
|
keyboardType: TextInputType.emailAddress,
|
|
validator: (value) =>
|
|
value?.trim().isEmpty == true ? 'Requerido' : null,
|
|
),
|
|
TextFormField(
|
|
controller: telefonoCtrl,
|
|
decoration: const InputDecoration(labelText: 'Teléfono'),
|
|
keyboardType: TextInputType.phone,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(ctx),
|
|
style:
|
|
TextButton.styleFrom(foregroundColor: AppTheme.textSecondary),
|
|
child: const Text('Cancelar'),
|
|
),
|
|
TextButton(
|
|
onPressed: () {
|
|
if (!formKey.currentState!.validate()) return;
|
|
final nuevo = AdminUser(
|
|
id: user?.id ?? 'user-${DateTime.now().millisecondsSinceEpoch}',
|
|
nombre: nombreCtrl.text.trim(),
|
|
apellido: apellidoCtrl.text.trim(),
|
|
email: emailCtrl.text.trim(),
|
|
telefono: telefonoCtrl.text.trim(),
|
|
);
|
|
setState(() {
|
|
if (user == null) {
|
|
_usuarios.add(nuevo);
|
|
} else {
|
|
final index =
|
|
_usuarios.indexWhere((item) => item.id == user.id);
|
|
if (index >= 0) _usuarios[index] = nuevo;
|
|
}
|
|
});
|
|
Navigator.pop(ctx);
|
|
},
|
|
child: Text(user == null ? 'Crear' : 'Guardar'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showRouteForm({AdminRoute? route}) {
|
|
final formKey = GlobalKey<FormState>();
|
|
final nombreCtrl = TextEditingController(text: route?.nombre);
|
|
final zonaCtrl = TextEditingController(text: route?.zona);
|
|
bool activa = route?.activa ?? true;
|
|
|
|
showDialog(
|
|
context: context,
|
|
builder: (ctx) => AlertDialog(
|
|
backgroundColor: AppTheme.surface,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(AppTheme.radiusLg),
|
|
),
|
|
title: Text(route == null ? 'Nueva ruta' : 'Editar ruta'),
|
|
content: Form(
|
|
key: formKey,
|
|
child: SingleChildScrollView(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
TextFormField(
|
|
controller: nombreCtrl,
|
|
decoration:
|
|
const InputDecoration(labelText: 'Nombre de ruta'),
|
|
validator: (value) =>
|
|
value?.trim().isEmpty == true ? 'Requerido' : null,
|
|
),
|
|
TextFormField(
|
|
controller: zonaCtrl,
|
|
decoration: const InputDecoration(labelText: 'Zona'),
|
|
validator: (value) =>
|
|
value?.trim().isEmpty == true ? 'Requerido' : null,
|
|
),
|
|
const SizedBox(height: 12),
|
|
Row(
|
|
children: [
|
|
const Expanded(child: Text('Ruta activa')),
|
|
Switch.adaptive(
|
|
value: activa,
|
|
onChanged: (value) => setState(() {
|
|
activa = value;
|
|
}),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(ctx),
|
|
style:
|
|
TextButton.styleFrom(foregroundColor: AppTheme.textSecondary),
|
|
child: const Text('Cancelar'),
|
|
),
|
|
TextButton(
|
|
onPressed: () {
|
|
if (!formKey.currentState!.validate()) return;
|
|
final nueva = AdminRoute(
|
|
id: route?.id ??
|
|
'ruta-${DateTime.now().millisecondsSinceEpoch}',
|
|
nombre: nombreCtrl.text.trim(),
|
|
zona: zonaCtrl.text.trim(),
|
|
activa: activa,
|
|
);
|
|
setState(() {
|
|
if (route == null) {
|
|
_rutas.add(nueva);
|
|
} else {
|
|
final index =
|
|
_rutas.indexWhere((item) => item.id == route.id);
|
|
if (index >= 0) _rutas[index] = nueva;
|
|
}
|
|
});
|
|
Navigator.pop(ctx);
|
|
},
|
|
child: Text(route == null ? 'Crear' : 'Guardar'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showTruckForm({AdminTruck? truck}) {
|
|
final formKey = GlobalKey<FormState>();
|
|
final placasCtrl = TextEditingController(text: truck?.placas);
|
|
final modeloCtrl = TextEditingController(text: truck?.modelo);
|
|
final conductorCtrl = TextEditingController(text: truck?.conductor);
|
|
TruckStatus status = truck?.status ?? TruckStatus.disponible;
|
|
String selectedRuta =
|
|
truck?.rutaId ?? (_rutas.isNotEmpty ? _rutas.first.id : '');
|
|
|
|
showDialog(
|
|
context: context,
|
|
builder: (ctx) => AlertDialog(
|
|
backgroundColor: AppTheme.surface,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(AppTheme.radiusLg),
|
|
),
|
|
title: Text(truck == null ? 'Nuevo camión' : 'Editar camión'),
|
|
content: Form(
|
|
key: formKey,
|
|
child: SingleChildScrollView(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
TextFormField(
|
|
controller: placasCtrl,
|
|
decoration: const InputDecoration(labelText: 'Placas'),
|
|
validator: (value) =>
|
|
value?.trim().isEmpty == true ? 'Requerido' : null,
|
|
),
|
|
TextFormField(
|
|
controller: modeloCtrl,
|
|
decoration: const InputDecoration(labelText: 'Modelo'),
|
|
validator: (value) =>
|
|
value?.trim().isEmpty == true ? 'Requerido' : null,
|
|
),
|
|
TextFormField(
|
|
controller: conductorCtrl,
|
|
decoration: const InputDecoration(labelText: 'Conductor'),
|
|
validator: (value) =>
|
|
value?.trim().isEmpty == true ? 'Requerido' : null,
|
|
),
|
|
const SizedBox(height: 12),
|
|
DropdownButtonFormField<String>(
|
|
value: selectedRuta.isEmpty ? null : selectedRuta,
|
|
decoration: const InputDecoration(labelText: 'Ruta'),
|
|
items: _rutas
|
|
.map((ruta) => DropdownMenuItem(
|
|
value: ruta.id,
|
|
child: Text(ruta.nombre),
|
|
))
|
|
.toList(),
|
|
onChanged: (value) {
|
|
if (value != null) {
|
|
selectedRuta = value;
|
|
}
|
|
},
|
|
validator: (value) =>
|
|
value == null || value.isEmpty ? 'Requerido' : null,
|
|
),
|
|
const SizedBox(height: 12),
|
|
DropdownButtonFormField<TruckStatus>(
|
|
value: status,
|
|
decoration: const InputDecoration(labelText: 'Estatus'),
|
|
items: TruckStatus.values
|
|
.map((item) => DropdownMenuItem(
|
|
value: item,
|
|
child: Text(item.label),
|
|
))
|
|
.toList(),
|
|
onChanged: (value) {
|
|
if (value != null) {
|
|
status = value;
|
|
}
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(ctx),
|
|
style:
|
|
TextButton.styleFrom(foregroundColor: AppTheme.textSecondary),
|
|
child: const Text('Cancelar'),
|
|
),
|
|
TextButton(
|
|
onPressed: () {
|
|
if (!formKey.currentState!.validate()) return;
|
|
final nuevo = AdminTruck(
|
|
id: truck?.id ??
|
|
'truck-${DateTime.now().millisecondsSinceEpoch}',
|
|
placas: placasCtrl.text.trim(),
|
|
modelo: modeloCtrl.text.trim(),
|
|
conductor: conductorCtrl.text.trim(),
|
|
status: status,
|
|
rutaId: selectedRuta,
|
|
);
|
|
setState(() {
|
|
if (truck == null) {
|
|
_camiones.add(nuevo);
|
|
} else {
|
|
final index =
|
|
_camiones.indexWhere((item) => item.id == truck.id);
|
|
if (index >= 0) _camiones[index] = nuevo;
|
|
}
|
|
});
|
|
Navigator.pop(ctx);
|
|
},
|
|
child: Text(truck == null ? 'Crear' : 'Guardar'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|