Agrego frontend app

This commit is contained in:
Pablo
2026-08-22 19:08:19 -03:00
parent 824e092b29
commit 2d0797e627
254 changed files with 37231 additions and 0 deletions
@@ -0,0 +1,218 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:gimnasio_soma/core/theme/soma_colors.dart';
import 'package:gimnasio_soma/core/widgets/soma_text_field.dart';
import 'package:gimnasio_soma/features/actividades/domain/entities/actividad.dart';
class ActividadFormDialog extends StatefulWidget {
final Actividad? actividad;
const ActividadFormDialog({super.key, this.actividad});
@override
State<ActividadFormDialog> createState() => _ActividadFormDialogState();
}
class _ActividadFormDialogState extends State<ActividadFormDialog> {
final _formKey = GlobalKey<FormState>();
late final TextEditingController _nombreCtrl;
late final TextEditingController _duracionCtrl;
late final TextEditingController _capacidadCtrl;
late bool _libre;
bool get _isEditing => widget.actividad != null;
@override
void initState() {
super.initState();
_nombreCtrl = TextEditingController(text: widget.actividad?.nombre ?? '');
_duracionCtrl = TextEditingController(
text: widget.actividad != null ? widget.actividad!.duracion.toString() : '',
);
_capacidadCtrl = TextEditingController(
text: widget.actividad != null
? widget.actividad!.capacidadPorDefecto.toString()
: '',
);
_libre = widget.actividad?.libre ?? false;
}
@override
void dispose() {
_nombreCtrl.dispose();
_duracionCtrl.dispose();
_capacidadCtrl.dispose();
super.dispose();
}
void _submit() {
if (!_formKey.currentState!.validate()) return;
final data = <String, dynamic>{
'nombre': _nombreCtrl.text.trim(),
'duracion': int.tryParse(_duracionCtrl.text.trim()) ?? 0,
'capacidad_por_defecto': int.tryParse(_capacidadCtrl.text.trim()) ?? 0,
'libre': _libre,
};
Navigator.of(context).pop(data);
}
@override
Widget build(BuildContext context) {
final width = MediaQuery.of(context).size.width;
final isWide = width >= 600;
return Dialog(
insetPadding: EdgeInsets.symmetric(
horizontal: isWide ? (width - 440) / 2 : 20,
vertical: 24,
),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 440),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Header
Padding(
padding: const EdgeInsets.fromLTRB(24, 20, 16, 0),
child: Row(
children: [
Text(
_isEditing ? 'Editar Actividad' : 'Nueva Actividad',
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.w700),
),
const Spacer(),
IconButton(
icon: const Icon(Icons.close, size: 20),
onPressed: () => Navigator.of(context).pop(),
),
],
),
),
const Divider(height: 20),
// Form
Flexible(
child: SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(24, 0, 24, 8),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SomaTextField(
controller: _nombreCtrl,
labelText: 'Nombre *',
prefixIcon: Icons.fitness_center,
validator: (v) => v == null || v.trim().isEmpty
? 'Nombre requerido'
: null,
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: SomaTextField(
controller: _duracionCtrl,
labelText: 'Duración (min) *',
prefixIcon: Icons.timer_outlined,
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(4),
],
validator: (v) {
if (v == null || v.trim().isEmpty) {
return 'Requerido';
}
final n = int.tryParse(v.trim());
if (n == null || n <= 0) return 'Inválido';
return null;
},
),
),
const SizedBox(width: 12),
Expanded(
child: SomaTextField(
controller: _capacidadCtrl,
labelText: 'Capacidad *',
prefixIcon: Icons.group_outlined,
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(4),
],
validator: (v) {
if (v == null || v.trim().isEmpty) {
return 'Requerido';
}
final n = int.tryParse(v.trim());
if (n == null || n <= 0) return 'Inválido';
return null;
},
),
),
],
),
const SizedBox(height: 16),
SwitchListTile(
title: const Text(
'Actividad libre',
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
),
subtitle: const Text(
'No requiere turno previo',
style: TextStyle(fontSize: 12),
),
value: _libre,
activeThumbColor: SomaColors.primary,
contentPadding: EdgeInsets.zero,
onChanged: (v) => setState(() => _libre = v),
),
const SizedBox(height: 8),
],
),
),
),
),
const Divider(height: 1),
// Actions
Padding(
padding: const EdgeInsets.fromLTRB(24, 12, 24, 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(
'Cancelar',
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onSurface
.withAlpha(178),
),
),
),
const SizedBox(width: 12),
ElevatedButton(
onPressed: _submit,
style: ElevatedButton.styleFrom(
minimumSize: const Size(0, 42),
),
child: Text(_isEditing ? 'Guardar' : 'Crear'),
),
],
),
),
],
),
),
);
}
}