import 'package:flutter/material.dart'; import 'package:uto_manager/domain/entities/authorized_rank.dart'; import 'package:uto_manager/domain/entities/personnel_record.dart'; import 'package:uto_manager/domain/entities/soldier_position.dart'; import 'package:uto_manager/domain/value_objects/duty_mos.dart'; import 'package:uto_manager/infrastructure/repositories/drift_soldier_positions_repository.dart'; import 'package:uuid/uuid.dart'; /// Dialog for creating or editing a [PersonnelRecord]. /// /// Pass [existing] to edit; leave null to create a new record. /// [positionsRepository] is used to load and manage the position list. class AddEditPersonnelDialog extends StatefulWidget { const AddEditPersonnelDialog({ required this.positionsRepository, super.key, this.existing, }); final DriftSoldierPositionsRepository positionsRepository; final PersonnelRecord? existing; @override State createState() => _AddEditPersonnelDialogState(); } class _AddEditPersonnelDialogState extends State { final _formKey = GlobalKey(); late final TextEditingController _nameCtrl; late final TextEditingController _mosCtrl; late AuthorizedRank _payGrade; late ReadinessState _readiness; /// The currently selected position, or null if none chosen. SoldierPosition? _selectedPosition; /// All positions loaded from the repository. List _positions = []; List _mosSuggestions = []; bool get _isEditing => widget.existing != null; @override void initState() { super.initState(); final e = widget.existing; _nameCtrl = TextEditingController(text: e?.name ?? ''); _mosCtrl = TextEditingController(text: e?.primaryMos.code ?? ''); _payGrade = e?.payGrade ?? AuthorizedRank.e4; _readiness = e?.readinessState ?? ReadinessState.fullyMissionCapable; _loadPositions(existingTitle: e?.positionTitle); } @override void dispose() { _nameCtrl.dispose(); _mosCtrl.dispose(); super.dispose(); } Future _loadPositions({String? existingTitle}) async { await widget.positionsRepository.seedBuiltIns(); final positions = await widget.positionsRepository.getAll(); positions.sort((a, b) => a.title.compareTo(b.title)); final selected = existingTitle != null ? positions.where((p) => p.title == existingTitle).firstOrNull : null; if (mounted) { setState(() { _positions = positions; _selectedPosition = selected; }); } } void _updateMosSuggestions(String query) { if (query.isEmpty) { setState(() => _mosSuggestions = []); return; } final q = query.toUpperCase(); final matches = DutyMos.allCodes.where((c) => c.startsWith(q) || c.contains(q)).take(12).toList()..sort(); setState(() => _mosSuggestions = matches); } void _submit() { if (!_formKey.currentState!.validate()) return; final mosCode = _mosCtrl.text.trim().toUpperCase(); DutyMos mos; try { mos = DutyMos(mosCode); } catch (_) { mos = DutyMos.unvalidated(mosCode); } final nameText = _nameCtrl.text.trim(); final record = PersonnelRecord( soldierId: widget.existing?.soldierId ?? const Uuid().v4(), name: nameText.isEmpty ? null : nameText, positionTitle: _selectedPosition?.title, primaryMos: mos, payGrade: _payGrade, readinessState: _readiness, qualifications: widget.existing?.qualifications ?? const [], assignedSlotId: widget.existing?.assignedSlotId, ); Navigator.of(context).pop(record); } // --------------------------------------------------------------------------- // Position CRUD helpers // --------------------------------------------------------------------------- Future _showAddPositionDialog() async { final title = await _showPositionTitleDialog(context, initial: ''); if (title == null || title.isEmpty) return; final pos = SoldierPosition(positionId: const Uuid().v4(), title: title); await widget.positionsRepository.upsert(pos); await _loadPositions(); if (mounted) { setState(() => _selectedPosition = _positions.where((p) => p.title == title).firstOrNull); } } Future _showEditPositionDialog(SoldierPosition pos) async { if (pos.isBuiltIn) return; // built-ins cannot be renamed final title = await _showPositionTitleDialog(context, initial: pos.title); if (title == null || title.isEmpty || title == pos.title) return; await widget.positionsRepository.upsert(pos.copyWith(title: title)); await _loadPositions(); if (mounted) { setState( () => _selectedPosition = _positions.where((p) => p.positionId == pos.positionId).firstOrNull, ); } } Future _confirmDeletePosition(SoldierPosition pos) async { if (pos.isBuiltIn) return; final confirmed = await showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('Delete Position?'), content: Text('Remove the "${pos.title}" position? This cannot be undone.'), actions: [ TextButton(onPressed: () => Navigator.of(ctx).pop(false), child: const Text('Cancel')), FilledButton( style: FilledButton.styleFrom(backgroundColor: Colors.red.shade700), onPressed: () => Navigator.of(ctx).pop(true), child: const Text('Delete'), ), ], ), ); if (confirmed ?? false) { await widget.positionsRepository.delete(pos.positionId); await _loadPositions(); if (mounted && _selectedPosition?.positionId == pos.positionId) { setState(() => _selectedPosition = null); } } } /// Shows a simple text-input dialog for a position title. Future _showPositionTitleDialog(BuildContext context, {required String initial}) { final ctrl = TextEditingController(text: initial); return showDialog( context: context, builder: (ctx) => AlertDialog( title: Text(initial.isEmpty ? 'Add Position' : 'Rename Position'), content: TextField( controller: ctrl, autofocus: true, textCapitalization: TextCapitalization.words, decoration: const InputDecoration( labelText: 'Position Title', hintText: 'e.g. Anti-Tank Gunner', ), onSubmitted: (_) => Navigator.of(ctx).pop(ctrl.text.trim()), ), actions: [ TextButton(onPressed: () => Navigator.of(ctx).pop(), child: const Text('Cancel')), FilledButton( onPressed: () => Navigator.of(ctx).pop(ctrl.text.trim()), child: const Text('Save'), ), ], ), ); } // --------------------------------------------------------------------------- // Build // --------------------------------------------------------------------------- @override Widget build(BuildContext context) { final theme = Theme.of(context); return AlertDialog( title: Row( children: [ Icon(_isEditing ? Icons.edit : Icons.person_add, size: 20), const SizedBox(width: 8), Text(_isEditing ? 'Edit Soldier' : 'Add Soldier'), ], ), content: SizedBox( width: 420, child: Form( key: _formKey, child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ // ── Position ───────────────────────────────────────────────── _buildPositionRow(theme), const SizedBox(height: 12), // ── Name (optional) ────────────────────────────────────────── TextFormField( controller: _nameCtrl, decoration: const InputDecoration( labelText: 'Full Name (optional)', hintText: 'e.g. SMITH, John A.', prefixIcon: Icon(Icons.badge_outlined), helperText: 'Leave blank to identify by position only.', ), textCapitalization: TextCapitalization.characters, ), const SizedBox(height: 12), // ── Pay Grade ──────────────────────────────────────────────── DropdownButtonFormField( initialValue: _payGrade, isExpanded: true, decoration: const InputDecoration( labelText: 'Pay Grade', prefixIcon: Icon(Icons.military_tech_outlined), ), items: AuthorizedRank.values.map((r) { return DropdownMenuItem( value: r, child: Text('${r.payGrade} — ${r.rankTitle}'), ); }).toList(), onChanged: (v) => setState(() => _payGrade = v!), ), const SizedBox(height: 12), // ── MOS with autocomplete ──────────────────────────────────── TextFormField( controller: _mosCtrl, decoration: const InputDecoration( labelText: 'Primary MOS', hintText: 'e.g. 11B, 68W, 25U', prefixIcon: Icon(Icons.work_outline), ), textCapitalization: TextCapitalization.characters, onChanged: _updateMosSuggestions, validator: (v) { if (v == null || v.trim().isEmpty) return 'MOS is required'; final code = v.trim().toUpperCase(); if (!DutyMos.isKnown(code)) { return 'Unknown MOS code. Use a valid Army MOS (e.g. 11B, 68W)'; } return null; }, ), if (_mosSuggestions.isNotEmpty) ...[ const SizedBox(height: 4), Container( padding: const EdgeInsets.all(4), decoration: BoxDecoration( color: theme.colorScheme.surfaceContainerHigh, borderRadius: BorderRadius.circular(6), border: Border.all(color: theme.colorScheme.outlineVariant), ), child: Wrap( spacing: 4, runSpacing: 4, children: _mosSuggestions.map((code) { return ActionChip( label: Text( code, style: theme.textTheme.labelSmall?.copyWith( fontWeight: FontWeight.bold, ), ), onPressed: () { _mosCtrl.text = code; setState(() => _mosSuggestions = []); }, padding: const EdgeInsets.symmetric(horizontal: 4), visualDensity: VisualDensity.compact, ); }).toList(), ), ), ], const SizedBox(height: 12), // ── Readiness State ────────────────────────────────────────── DropdownButtonFormField( initialValue: _readiness, isExpanded: true, decoration: const InputDecoration( labelText: 'Readiness State', prefixIcon: Icon(Icons.health_and_safety_outlined), ), items: ReadinessState.values.map((s) { return DropdownMenuItem( value: s, child: Text(s.displayName), ); }).toList(), onChanged: (v) => setState(() => _readiness = v!), ), ], ), ), ), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Cancel'), ), FilledButton.icon( icon: Icon(_isEditing ? Icons.save : Icons.add, size: 16), label: Text(_isEditing ? 'Save Changes' : 'Add Soldier'), onPressed: _submit, ), ], ); } /// Builds the position dropdown row with add / edit / delete action buttons. Widget _buildPositionRow(ThemeData theme) { return Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ Expanded( child: _positions.isEmpty ? const LinearProgressIndicator() : DropdownButtonFormField( initialValue: _selectedPosition, isExpanded: true, decoration: const InputDecoration( labelText: 'Position', prefixIcon: Icon(Icons.work_history_outlined), ), hint: const Text('Select position…'), items: _positions.map((p) { return DropdownMenuItem( value: p, child: Row( children: [ Expanded(child: Text(p.title, overflow: TextOverflow.ellipsis)), if (p.isBuiltIn) Icon( Icons.lock_outline, size: 12, color: theme.colorScheme.onSurfaceVariant.withValues(alpha: 0.5), ), ], ), ); }).toList(), onChanged: (v) => setState(() => _selectedPosition = v), ), ), // Add custom position IconButton( icon: const Icon(Icons.add_circle_outline, size: 18), tooltip: 'Add custom position', onPressed: _showAddPositionDialog, ), // Edit selected position (only if custom) if (_selectedPosition != null && !_selectedPosition!.isBuiltIn) IconButton( icon: const Icon(Icons.edit_outlined, size: 18), tooltip: 'Rename position', onPressed: () => _showEditPositionDialog(_selectedPosition!), ), // Delete selected position (only if custom) if (_selectedPosition != null && !_selectedPosition!.isBuiltIn) IconButton( icon: Icon(Icons.delete_outline, size: 18, color: theme.colorScheme.error), tooltip: 'Delete position', onPressed: () => _confirmDeletePosition(_selectedPosition!), ), ], ); } }