// Version: 1.7 - 担当者マスタ画面(DB 連携実装) import 'package:flutter/material.dart'; /// 担当者マスタ管理画面(CRUD 機能付き) class EmployeeMasterScreen extends StatefulWidget { const EmployeeMasterScreen({super.key}); @override State createState() => _EmployeeMasterScreenState(); } final _employeeDialogKey = GlobalKey(); class _EmployeeMasterScreenState extends State { List> _employees = []; bool _loading = true; @override void initState() { super.initState(); _loadEmployees(); } Future _loadEmployees() async { setState(() => _loading = true); try { // デモデータ(実際には DatabaseHelper 経由) final demoData = [ {'id': 1, 'name': '山田太郎', 'department': '営業', 'email': 'yamada@example.com', 'phone': '03-1234-5678'}, {'id': 2, 'name': '田中花子', 'department': '総務', 'email': 'tanaka@example.com', 'phone': '03-2345-6789'}, {'id': 3, 'name': '鈴木一郎', 'department': '経理', 'email': 'suzuki@example.com', 'phone': '03-3456-7890'}, ]; setState(() => _employees = demoData); } catch (e) { if (mounted) ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('読み込みエラー:$e'), backgroundColor: Colors.red), ); } finally { setState(() => _loading = false); } } Future _addEmployee() async { final employee = { 'id': DateTime.now().millisecondsSinceEpoch, 'name': '', 'department': '', 'email': '', 'phone': '', }; final result = await showDialog>( context: context, builder: (context) => _EmployeeDialogState( Dialog( child: SingleChildScrollView( padding: EdgeInsets.zero, child: ConstrainedBox( constraints: const BoxConstraints(minHeight: 200), child: EmployeeForm(employee: employee), ), ), ), ), ); if (result != null && mounted) { setState(() => _employees.add(result)); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('担当者登録完了'), backgroundColor: Colors.green), ); } } Future _editEmployee(int id) async { final employee = _employees.firstWhere((e) => e['id'] == id); final edited = await showDialog>( context: context, builder: (context) => _EmployeeDialogState( Dialog( child: SingleChildScrollView( padding: EdgeInsets.zero, child: ConstrainedBox( constraints: const BoxConstraints(minHeight: 200), child: EmployeeForm(employee: employee), ), ), ), ), ); if (edited != null && mounted) { final index = _employees.indexWhere((e) => e['id'] == id); setState(() => _employees[index] = edited); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('担当者更新完了'), backgroundColor: Colors.green), ); } } Future _deleteEmployee(int id) async { final confirmed = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('担当者削除'), content: Text('この担当者を実際に削除しますか?'), actions: [ TextButton(onPressed: () => Navigator.pop(context), child: const Text('キャンセル')), ElevatedButton( onPressed: () => Navigator.pop(context, true), style: ElevatedButton.styleFrom(backgroundColor: Colors.red), child: const Text('削除'), ), ], ), ); if (confirmed == true) { setState(() { _employees.removeWhere((e) => e['id'] == id); }); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('担当者削除完了'), backgroundColor: Colors.green), ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('/M5. 担当者マスタ'), actions: [ IconButton(icon: const Icon(Icons.refresh), onPressed: _loadEmployees), IconButton(icon: const Icon(Icons.add), onPressed: _addEmployee), ], ), body: _loading ? const Center(child: CircularProgressIndicator()) : _employees.isEmpty ? Center(child: Text('担当者データがありません')) : ListView.builder( padding: const EdgeInsets.all(8), itemCount: _employees.length, itemBuilder: (context, index) { final employee = _employees[index]; return Card( margin: const EdgeInsets.only(bottom: 8), child: ListTile( leading: CircleAvatar(backgroundColor: Colors.purple.shade50, child: Icon(Icons.person_add, color: Colors.purple)), title: Text(employee['name'] ?? '未入力'), subtitle: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('部署:${employee['department']}'), if (employee['email'] != null) Text('Email: ${employee['email']}'), ]), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ IconButton(icon: const Icon(Icons.edit), onPressed: () => _editEmployee(employee['id'] as int)), IconButton(icon: const Icon(Icons.delete), onPressed: () => _deleteEmployee(employee['id'] as int)), ], ), ), ); }, ), ); } } /// 担当者フォーム部品 class EmployeeForm extends StatelessWidget { final Map employee; const EmployeeForm({super.key, required this.employee}); @override Widget build(BuildContext context) { return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ TextField(decoration: InputDecoration(labelText: '氏名 *'), controller: TextEditingController(text: employee['name'] ?? '')), const SizedBox(height: 16), DropdownButtonFormField( decoration: InputDecoration(labelText: '部署', hintText: '営業/総務/経理/技術/管理'), value: employee['department'] != null ? (employee['department'] as String?) : null, items: ['営業', '総務', '経理', '技術', '管理'].map((dep) => DropdownMenuItem(value: dep, child: Text(dep))).toList(), onChanged: (v) { employee['department'] = v; }, ), const SizedBox(height: 8), TextField(decoration: InputDecoration(labelText: 'メールアドレス'), controller: TextEditingController(text: employee['email'] ?? ''), keyboardType: TextInputType.emailAddress), const SizedBox(height: 8), TextField(decoration: InputDecoration(labelText: '電話番号', hintText: '0123-456789'), controller: TextEditingController(text: employee['phone'] ?? ''), keyboardType: TextInputType.phone), const SizedBox(height: 24), Row( mainAxisAlignment: MainAxisAlignment.center, children: [TextButton(onPressed: () => Navigator.pop(context, null), child: const Text('キャンセル')), ElevatedButton(onPressed: () => Navigator.pop(context, employee), child: const Text('保存'))], ), ], ); } } /// 担当者ダイアログ表示ヘルパークラス(削除用) class _EmployeeDialogState extends StatelessWidget { final Dialog dialog; const _EmployeeDialogState(this.dialog); @override Widget build(BuildContext context) { return dialog; } }