214 lines
No EOL
7.9 KiB
Dart
214 lines
No EOL
7.9 KiB
Dart
// Version: 1.7 - 担当者マスタ画面(DB 連携実装)
|
||
import 'package:flutter/material.dart';
|
||
|
||
/// 担当者マスタ管理画面(CRUD 機能付き)
|
||
class EmployeeMasterScreen extends StatefulWidget {
|
||
const EmployeeMasterScreen({super.key});
|
||
|
||
@override
|
||
State<EmployeeMasterScreen> createState() => _EmployeeMasterScreenState();
|
||
}
|
||
|
||
final _employeeDialogKey = GlobalKey();
|
||
|
||
class _EmployeeMasterScreenState extends State<EmployeeMasterScreen> {
|
||
List<Map<String, dynamic>> _employees = [];
|
||
bool _loading = true;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_loadEmployees();
|
||
}
|
||
|
||
Future<void> _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<void> _addEmployee() async {
|
||
final employee = <String, dynamic>{
|
||
'id': DateTime.now().millisecondsSinceEpoch,
|
||
'name': '',
|
||
'department': '',
|
||
'email': '',
|
||
'phone': '',
|
||
};
|
||
|
||
final result = await showDialog<Map<String, dynamic>>(
|
||
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<void> _editEmployee(int id) async {
|
||
final employee = _employees.firstWhere((e) => e['id'] == id);
|
||
|
||
final edited = await showDialog<Map<String, dynamic>>(
|
||
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<void> _deleteEmployee(int id) async {
|
||
final confirmed = await showDialog<bool>(
|
||
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<String, dynamic> 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<String>(
|
||
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;
|
||
}
|
||
} |