uto_manager/lib/presentation/widgets/position_slot_card.dart

129 lines
4.4 KiB
Dart

import 'package:flutter/material.dart';
import 'package:uto_manager/domain/entities/personnel_record.dart';
import 'package:uto_manager/domain/entities/position_slot.dart';
/// Displays a single position slot as a card in the inspector panel.
///
/// Shows the slot's position title, MOS, rank, and assigned soldier (or VACANT).
/// Supports drag-and-drop target for soldier assignment.
class PositionSlotCard extends StatelessWidget {
const PositionSlotCard({
super.key,
required this.slot,
this.assignedSoldier,
this.onUnassign,
this.onAcceptDrop,
});
final PositionSlot slot;
final PersonnelRecord? assignedSoldier;
final VoidCallback? onUnassign;
final void Function(PersonnelRecord soldier)? onAcceptDrop;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return DragTarget<PersonnelRecord>(
onWillAcceptWithDetails: (details) => slot.isVacant,
onAcceptWithDetails: (details) => onAcceptDrop?.call(details.data),
builder: (context, candidateData, rejectedData) {
final isDragOver = candidateData.isNotEmpty;
return AnimatedContainer(
duration: const Duration(milliseconds: 120),
margin: const EdgeInsets.symmetric(vertical: 3, horizontal: 8),
decoration: BoxDecoration(
color: isDragOver
? theme.colorScheme.primaryContainer.withValues(alpha: 0.6)
: slot.isFilled
? theme.colorScheme.surfaceContainerHighest
: theme.colorScheme.surfaceContainerLow,
border: Border.all(
color: isDragOver
? theme.colorScheme.primary
: slot.isKeyLeader
? const Color(0xFFFFD700) // Gold for key leaders
: theme.colorScheme.outlineVariant,
width: isDragOver ? 2 : 1,
),
borderRadius: BorderRadius.circular(6),
),
child: ListTile(
dense: true,
contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 2),
leading: _buildMosBadge(theme),
title: Text(
slot.positionTitle,
style: theme.textTheme.bodySmall?.copyWith(
fontWeight: slot.isKeyLeader ? FontWeight.bold : FontWeight.normal,
),
),
subtitle: Text(
slot.isFilled ? (assignedSoldier?.name ?? 'Assigned') : 'VACANT',
style: theme.textTheme.labelSmall?.copyWith(
color: slot.isFilled
? _readinessColor(assignedSoldier?.readinessState)
: theme.colorScheme.error,
fontWeight: slot.isVacant ? FontWeight.bold : FontWeight.normal,
),
),
trailing: slot.isFilled && onUnassign != null
? IconButton(
icon: const Icon(Icons.person_remove, size: 16),
onPressed: onUnassign,
tooltip: 'Unassign soldier',
padding: EdgeInsets.zero,
constraints: const BoxConstraints(minWidth: 28, minHeight: 28),
)
: null,
),
);
},
);
}
Widget _buildMosBadge(ThemeData theme) {
return Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: theme.colorScheme.primary.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(4),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
slot.dutyMos.code,
style: theme.textTheme.labelSmall?.copyWith(
fontWeight: FontWeight.bold,
fontSize: 10,
color: theme.colorScheme.primary,
),
),
Text(
slot.authorizedRank.payGrade,
style: theme.textTheme.labelSmall?.copyWith(
fontSize: 9,
color: theme.colorScheme.onSurfaceVariant,
),
),
],
),
);
}
Color _readinessColor(ReadinessState? state) {
switch (state) {
case ReadinessState.fullyMissionCapable:
return const Color(0xFF4CAF50);
case ReadinessState.partiallyMissionCapable:
return const Color(0xFFFF9800);
case ReadinessState.notMissionCapable:
return const Color(0xFFF44336);
default:
return Colors.grey;
}
}
}