import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../core/api_client.dart'; import '../core/api_service.dart'; import '../models/device.dart'; import '../models/device_type.dart'; import '../models/zone.dart'; /// Create/edit device form. Pass an existing [device] to edit it in place; /// omit it to create a new one. Pops with `true` on success. class DeviceFormScreen extends StatefulWidget { const DeviceFormScreen({super.key, this.device}); final Device? device; bool get isEditing => device != null; @override State createState() => _DeviceFormScreenState(); } class _DeviceFormScreenState extends State { final _formKey = GlobalKey(); late final _nameController = TextEditingController(text: widget.device?.name); late final _externalIdController = TextEditingController(text: widget.device?.externalId); late final _protocolController = TextEditingController(text: widget.device?.protocol ?? 'mqtt'); late Future<(List, List)> _optionsFuture; int? _zoneId; int? _deviceTypeId; bool _isSubmitting = false; Map> _fieldErrors = {}; @override void initState() { super.initState(); _zoneId = widget.device?.zone?.id; _deviceTypeId = widget.device?.deviceType?.id; final apiService = context.read(); _optionsFuture = (apiService.zones(), apiService.deviceTypes()).wait; } @override void dispose() { _nameController.dispose(); _externalIdController.dispose(); _protocolController.dispose(); super.dispose(); } Future _submit() async { if (!_formKey.currentState!.validate()) return; if (_zoneId == null || _deviceTypeId == null) { setState(() => _fieldErrors = { if (_zoneId == null) 'zone_id': ['Выберите зону'], if (_deviceTypeId == null) 'device_type_id': ['Выберите тип устройства'], }); return; } setState(() { _isSubmitting = true; _fieldErrors = {}; }); final apiService = context.read(); final name = _nameController.text.trim(); final externalId = _externalIdController.text.trim(); final protocol = _protocolController.text.trim().isEmpty ? 'mqtt' : _protocolController.text.trim(); try { if (widget.isEditing) { await apiService.updateDevice( widget.device!.id, zoneId: _zoneId!, deviceTypeId: _deviceTypeId!, name: name, externalId: externalId, protocol: protocol, ); } else { await apiService.createDevice( zoneId: _zoneId!, deviceTypeId: _deviceTypeId!, name: name, externalId: externalId, protocol: protocol, ); } if (mounted) Navigator.of(context).pop(true); } on ApiException catch (e) { setState(() => _fieldErrors = e.errors ?? {}); if (e.errors == null && mounted) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.message))); } } finally { if (mounted) setState(() => _isSubmitting = false); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(widget.isEditing ? 'Редактировать устройство' : 'Новое устройство')), body: FutureBuilder<(List, List)>( future: _optionsFuture, builder: (context, snapshot) { if (snapshot.connectionState != ConnectionState.done) { return const Center(child: CircularProgressIndicator()); } if (snapshot.hasError) { return Center(child: Text('${snapshot.error}')); } final (zones, deviceTypes) = snapshot.data!; return Form( key: _formKey, child: ListView( padding: const EdgeInsets.all(16), children: [ DropdownButtonFormField( initialValue: _zoneId, decoration: InputDecoration(labelText: 'Зона', errorText: _fieldErrors['zone_id']?.first), items: zones.map((z) => DropdownMenuItem(value: z.id, child: Text(z.name))).toList(), onChanged: (value) => setState(() => _zoneId = value), ), const SizedBox(height: 16), DropdownButtonFormField( initialValue: _deviceTypeId, decoration: InputDecoration(labelText: 'Тип устройства', errorText: _fieldErrors['device_type_id']?.first), items: deviceTypes .map((t) => DropdownMenuItem(value: t.id, child: Text('${t.code} (${t.category})'))) .toList(), onChanged: (value) => setState(() => _deviceTypeId = value), ), const SizedBox(height: 16), TextFormField( controller: _nameController, decoration: InputDecoration(labelText: 'Название', errorText: _fieldErrors['name']?.first), validator: (value) => (value == null || value.trim().isEmpty) ? 'Введите название' : null, ), const SizedBox(height: 16), TextFormField( controller: _externalIdController, decoration: InputDecoration( labelText: 'External ID', helperText: 'Идентификатор физического устройства (ESP32 chip id и т.п.)', errorText: _fieldErrors['external_id']?.first, ), validator: (value) => (value == null || value.trim().isEmpty) ? 'Введите external_id' : null, ), const SizedBox(height: 16), TextFormField( controller: _protocolController, decoration: InputDecoration(labelText: 'Протокол', errorText: _fieldErrors['protocol']?.first), ), const SizedBox(height: 24), FilledButton( onPressed: _isSubmitting ? null : _submit, child: _isSubmitting ? const SizedBox(height: 20, width: 20, child: CircularProgressIndicator(strokeWidth: 2)) : Text(widget.isEditing ? 'Сохранить' : 'Создать'), ), ], ), ); }, ), ); } }