uto_manager/lib/presentation/screens/dialogs/add_edit_slot_dialog.dart

222 lines
7.5 KiB
Dart

import 'package:flutter/material.dart';
import 'package:uto_manager/domain/entities/authorized_rank.dart';
import 'package:uto_manager/domain/entities/position_slot.dart';
import 'package:uto_manager/domain/value_objects/duty_mos.dart';
import 'package:uuid/uuid.dart';
/// Dialog for creating or editing a [PositionSlot] within a unit.
///
/// Pass [existing] to edit; leave null to create a new slot.
/// [unitId] is required and identifies which unit owns this slot.
class AddEditSlotDialog extends StatefulWidget {
const AddEditSlotDialog({
required this.unitId,
super.key,
this.existing,
});
final String unitId;
final PositionSlot? existing;
@override
State<AddEditSlotDialog> createState() => _AddEditSlotDialogState();
}
class _AddEditSlotDialogState extends State<AddEditSlotDialog> {
final _formKey = GlobalKey<FormState>();
late final TextEditingController _titleCtrl;
late final TextEditingController _mosCtrl;
late AuthorizedRank _rank;
late bool _isKeyLeader;
List<String> _mosSuggestions = [];
bool get _isEditing => widget.existing != null;
@override
void initState() {
super.initState();
final e = widget.existing;
_titleCtrl = TextEditingController(text: e?.positionTitle ?? '');
_mosCtrl = TextEditingController(text: e?.dutyMos.code ?? '');
_rank = e?.authorizedRank ?? AuthorizedRank.e4;
_isKeyLeader = e?.isKeyLeader ?? false;
}
@override
void dispose() {
_titleCtrl.dispose();
_mosCtrl.dispose();
super.dispose();
}
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 slot = PositionSlot(
slotId: widget.existing?.slotId ?? const Uuid().v4(),
unitId: widget.unitId,
positionTitle: _titleCtrl.text.trim(),
dutyMos: mos,
authorizedRank: _rank,
isKeyLeader: _isKeyLeader,
assignedPersonnelId: widget.existing?.assignedPersonnelId,
);
Navigator.of(context).pop(slot);
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return AlertDialog(
title: Row(
children: [
Icon(_isEditing ? Icons.edit : Icons.add_circle_outline, size: 20),
const SizedBox(width: 8),
Text(_isEditing ? 'Edit Position Slot' : 'Add Position Slot'),
],
),
content: SizedBox(
width: 400,
child: Form(
key: _formKey,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Position Title
TextFormField(
controller: _titleCtrl,
decoration: const InputDecoration(
labelText: 'Position Title',
hintText: 'e.g. Platoon Leader, Squad Leader, Medic',
prefixIcon: Icon(Icons.work_history_outlined),
),
textCapitalization: TextCapitalization.words,
validator: (v) {
if (v == null || v.trim().isEmpty) return 'Position title is required';
return null;
},
),
const SizedBox(height: 12),
// Authorized Rank
DropdownButtonFormField<AuthorizedRank>(
initialValue: _rank,
decoration: const InputDecoration(
labelText: 'Authorized Rank',
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(() => _rank = v!),
),
const SizedBox(height: 12),
// Duty MOS with autocomplete
TextFormField(
controller: _mosCtrl,
decoration: const InputDecoration(
labelText: 'Duty MOS',
hintText: 'e.g. 11B, 68W, 25U',
prefixIcon: Icon(Icons.badge_outlined),
),
textCapitalization: TextCapitalization.characters,
onChanged: _updateMosSuggestions,
validator: (v) {
if (v == null || v.trim().isEmpty) return 'Duty MOS is required';
final code = v.trim().toUpperCase();
if (!DutyMos.isKnown(code)) {
return 'Unknown MOS code (e.g. 11B, 68W, 25U)';
}
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: 8),
// Key Leader toggle
SwitchListTile(
title: const Text('Key Leader Position'),
subtitle: const Text('e.g. Commander, XO, Platoon Leader, PSG'),
value: _isKeyLeader,
onChanged: (v) => setState(() => _isKeyLeader = v),
contentPadding: EdgeInsets.zero,
dense: true,
),
],
),
),
),
),
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 Slot'),
onPressed: _submit,
),
],
);
}
}