import 'package:flutter/material.dart'; import 'package:uuid/uuid.dart'; import '../models/department_model.dart'; import '../models/staff_model.dart'; import '../services/department_repository.dart'; import '../services/staff_repository.dart'; class StaffMasterScreen extends StatefulWidget { const StaffMasterScreen({super.key}); @override State createState() => _StaffMasterScreenState(); } class _StaffMasterScreenState extends State { final StaffRepository _staffRepository = StaffRepository(); final DepartmentRepository _departmentRepository = DepartmentRepository(); final Uuid _uuid = const Uuid(); bool _isLoading = true; bool _includeInactive = false; List _staff = const []; List _departments = const []; @override void initState() { super.initState(); _loadData(); } Future _loadData() async { setState(() => _isLoading = true); final results = await Future.wait([ _staffRepository.fetchStaff(includeInactive: _includeInactive), _departmentRepository.fetchDepartments(includeInactive: true), ]); if (!mounted) return; setState(() { _staff = results.first as List; _departments = results.last as List; _isLoading = false; }); } Future _openForm({StaffMember? staff}) async { final result = await showDialog( context: context, builder: (ctx) => _StaffFormDialog( staff: staff, departments: _departments, onSubmit: (member) => Navigator.of(ctx).pop(member), ), ); if (result == null) return; final saving = result.copyWith(id: result.id.isEmpty ? _uuid.v4() : result.id, updatedAt: DateTime.now()); await _staffRepository.saveStaff(saving); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('担当者を保存しました'))); _loadData(); } Future _deleteStaff(StaffMember staff) async { final confirmed = await showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('担当者を削除'), content: Text('${staff.name} を削除しますか?'), actions: [ TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('キャンセル')), TextButton(onPressed: () => Navigator.pop(ctx, true), child: const Text('削除', style: TextStyle(color: Colors.red))), ], ), ); if (confirmed != true) return; await _staffRepository.deleteStaff(staff.id); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('担当者を削除しました'))); _loadData(); } String _departmentName(String? departmentId) { if (departmentId == null) return '部門未設定'; return _departments.firstWhere( (d) => d.id == departmentId, orElse: () => Department(id: departmentId, name: '不明部門', updatedAt: DateTime.now()), ).name; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: const BackButton(), title: const Text('M6:担当者マスター'), actions: [ SwitchListTile.adaptive( value: _includeInactive, onChanged: (value) { setState(() => _includeInactive = value); _loadData(); }, title: const Text('退職者も表示'), contentPadding: const EdgeInsets.only(right: 12), ), ], ), floatingActionButton: FloatingActionButton( onPressed: () => _openForm(), child: const Icon(Icons.add), ), body: _isLoading ? const Center(child: CircularProgressIndicator()) : _staff.isEmpty ? const _EmptyState(message: '担当者が登録されていません') : RefreshIndicator( onRefresh: _loadData, child: ListView.builder( padding: const EdgeInsets.symmetric(vertical: 8), itemCount: _staff.length, itemBuilder: (context, index) { final staff = _staff[index]; final subtitleLines = [ _departmentName(staff.departmentId), if (staff.email?.isNotEmpty == true) 'メール: ${staff.email}', if (staff.tel?.isNotEmpty == true) 'TEL: ${staff.tel}', if (staff.role?.isNotEmpty == true) '役割: ${staff.role}', if (staff.permissionLevel?.isNotEmpty == true) '権限: ${staff.permissionLevel}', staff.isActive ? '稼働中' : '退職/無効', ]; return Card( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), child: ListTile( title: Text(staff.name, style: const TextStyle(fontWeight: FontWeight.bold)), subtitle: Text(subtitleLines.where((line) => line.isNotEmpty).join('\n')), trailing: PopupMenuButton( onSelected: (value) { switch (value) { case 'edit': _openForm(staff: staff); break; case 'delete': _deleteStaff(staff); break; } }, itemBuilder: (context) => const [ PopupMenuItem(value: 'edit', child: Text('編集')), PopupMenuItem(value: 'delete', child: Text('削除')), ], ), ), ); }, ), ), ); } } class _StaffFormDialog extends StatefulWidget { const _StaffFormDialog({required this.onSubmit, required this.departments, this.staff}); final StaffMember? staff; final List departments; final ValueChanged onSubmit; @override State<_StaffFormDialog> createState() => _StaffFormDialogState(); } class _StaffFormDialogState extends State<_StaffFormDialog> { late final TextEditingController _nameController; late final TextEditingController _emailController; late final TextEditingController _telController; late final TextEditingController _roleController; late final TextEditingController _permissionController; String? _departmentId; bool _isActive = true; @override void initState() { super.initState(); final staff = widget.staff; _nameController = TextEditingController(text: staff?.name ?? ''); _emailController = TextEditingController(text: staff?.email ?? ''); _telController = TextEditingController(text: staff?.tel ?? ''); _roleController = TextEditingController(text: staff?.role ?? ''); _permissionController = TextEditingController(text: staff?.permissionLevel ?? ''); _departmentId = staff?.departmentId; _isActive = staff?.isActive ?? true; } void _submit() { if (_nameController.text.trim().isEmpty) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('氏名は必須です'))); return; } widget.onSubmit( StaffMember( id: widget.staff?.id ?? '', name: _nameController.text.trim(), email: _emailController.text.trim().isEmpty ? null : _emailController.text.trim(), tel: _telController.text.trim().isEmpty ? null : _telController.text.trim(), role: _roleController.text.trim().isEmpty ? null : _roleController.text.trim(), departmentId: _departmentId, permissionLevel: _permissionController.text.trim().isEmpty ? null : _permissionController.text.trim(), isActive: _isActive, updatedAt: DateTime.now(), ), ); } @override Widget build(BuildContext context) { return AlertDialog( title: Text(widget.staff == null ? '担当者を追加' : '担当者を編集'), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ TextField(controller: _nameController, decoration: const InputDecoration(labelText: '氏名 *')), TextField(controller: _emailController, decoration: const InputDecoration(labelText: 'メール'), keyboardType: TextInputType.emailAddress), TextField(controller: _telController, decoration: const InputDecoration(labelText: '電話番号'), keyboardType: TextInputType.phone), DropdownButtonFormField( initialValue: _departmentId, decoration: const InputDecoration(labelText: '所属部門'), items: [ const DropdownMenuItem(value: null, child: Text('未設定')), ...widget.departments.map((dept) => DropdownMenuItem(value: dept.id, child: Text(dept.name))), ], onChanged: (value) => setState(() => _departmentId = value), ), TextField(controller: _roleController, decoration: const InputDecoration(labelText: '役割/職位')), TextField(controller: _permissionController, decoration: const InputDecoration(labelText: '権限レベル')), SwitchListTile( title: const Text('稼働中'), value: _isActive, onChanged: (value) => setState(() => _isActive = value), ), ], ), ), actions: [ TextButton(onPressed: () => Navigator.pop(context), child: const Text('キャンセル')), FilledButton(onPressed: _submit, child: const Text('保存')), ], ); } } class _EmptyState extends StatelessWidget { const _EmptyState({required this.message}); final String message; @override Widget build(BuildContext context) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.person_off, size: 64, color: Colors.grey), const SizedBox(height: 16), Text(message), ], ), ); } }