211 lines
No EOL
7.9 KiB
Dart
211 lines
No EOL
7.9 KiB
Dart
// Version: 1.0 - 担当者マスタ画面(簡易実装)
|
|
|
|
import 'package:flutter/material.dart';
|
|
import '../models/employee.dart';
|
|
import '../widgets/employee_edit_dialog.dart';
|
|
|
|
/// 担当者マスタ管理画面
|
|
class EmployeeMasterScreen extends StatefulWidget {
|
|
const EmployeeMasterScreen({super.key});
|
|
|
|
@override
|
|
State<EmployeeMasterScreen> createState() => _EmployeeMasterScreenState();
|
|
}
|
|
|
|
class _EmployeeMasterScreenState extends State<EmployeeMasterScreen> {
|
|
List<Employee> _employees = [];
|
|
bool _loading = true;
|
|
|
|
/// 検索機能用フィールド
|
|
String _searchKeyword = '';
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_loadEmployees();
|
|
}
|
|
|
|
/// 従業員データをロード(デモデータ)
|
|
Future<void> _loadEmployees() async {
|
|
setState(() => _loading = true);
|
|
try {
|
|
// サンプルデータを初期化
|
|
final demoData = [
|
|
Employee(id: 1, name: '山田太郎', email: 'tanaka@company.com', tel: '03-1234-5678', department: '営業部', role: '営業担当'),
|
|
Employee(id: 2, name: '田中花子', email: 'tanahana@company.com', tel: '03-2345-6789', department: '総務部', role: '総務担当'),
|
|
Employee(id: 3, name: '鈴木一郎', email: 'suzuki@company.com', tel: '03-3456-7890', department: '経理部', role: '経理担当'),
|
|
];
|
|
setState(() => _employees = demoData);
|
|
} catch (e) {
|
|
if (mounted) ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text('読み込みエラー:$e'), backgroundColor: Colors.red),
|
|
);
|
|
} finally {
|
|
setState(() => _loading = false);
|
|
}
|
|
}
|
|
|
|
/// 検索機能(フィルタリング)
|
|
List<Employee> get _filteredEmployees {
|
|
if (_searchKeyword.isEmpty) {
|
|
return _employees;
|
|
}
|
|
final keyword = _searchKeyword.toLowerCase();
|
|
return _employees.where((e) =>
|
|
e.name?.toLowerCase().contains(keyword) ||
|
|
e.department.toLowerCase().contains(keyword) ||
|
|
e.role.toLowerCase().contains(keyword)).toList();
|
|
}
|
|
|
|
/// 新規従業員追加
|
|
Future<void> _addEmployee() async {
|
|
final edited = await showDialog<Employee>(
|
|
context: context,
|
|
builder: (ctx) => EmployeeEditDialog(
|
|
title: '担当者登録',
|
|
initialData: null,
|
|
onSave: (employee) => setState(() => _employees.add(employee)),
|
|
),
|
|
);
|
|
|
|
if (edited != null && mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('担当者登録完了'), backgroundColor: Colors.green),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 従業員編集
|
|
Future<void> _editEmployee(Employee employee) async {
|
|
final edited = await showDialog<Employee>(
|
|
context: context,
|
|
builder: (ctx) => EmployeeEditDialog(
|
|
title: '担当者編集',
|
|
initialData: employee,
|
|
onSave: (updated) {
|
|
setState(() {
|
|
_employees = _employees.map((e) => e.id == updated.id ? updated : e).toList();
|
|
});
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('担当者更新完了'), backgroundColor: Colors.green),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
// 変更があった場合のみ処理
|
|
if (edited != null && mounted) {
|
|
_loadEmployees();
|
|
}
|
|
}
|
|
|
|
/// 従業員削除
|
|
Future<void> _deleteEmployee(Employee employee) async {
|
|
final confirmed = await showDialog<bool>(
|
|
context: context,
|
|
builder: (ctx) => AlertDialog(
|
|
title: const Text('担当者削除'),
|
|
content: Text('この担当者を実際に削除しますか?'),
|
|
actions: [
|
|
TextButton(onPressed: () => Navigator.pop(ctx), child: const Text('キャンセル')),
|
|
ElevatedButton(
|
|
onPressed: () => Navigator.pop(ctx, true),
|
|
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
|
|
child: const Text('削除'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
if (confirmed == true && mounted) {
|
|
setState(() {
|
|
_employees.removeWhere((e) => e.id == employee.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: Column(
|
|
children: [
|
|
// 検索バー
|
|
Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: TextField(
|
|
decoration: InputDecoration(
|
|
hintText: '担当者名で検索...',
|
|
prefixIcon: const Icon(Icons.search),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
),
|
|
onChanged: (value) => setState(() => _searchKeyword = value),
|
|
),
|
|
),
|
|
// 一覧リスト
|
|
Expanded(
|
|
child: _loading ? const Center(child: CircularProgressIndicator()) :
|
|
_filteredEmployees.isEmpty ? Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(Icons.person_outline, size: 64, color: Colors.grey[300]),
|
|
SizedBox(height: 16),
|
|
Text('担当者データがありません', style: TextStyle(color: Colors.grey)),
|
|
SizedBox(height: 16),
|
|
ElevatedButton.icon(
|
|
onPressed: _addEmployee,
|
|
icon: const Icon(Icons.add),
|
|
label: const Text('新規登録'),
|
|
),
|
|
],
|
|
),
|
|
) : ListView.builder(
|
|
padding: const EdgeInsets.all(8),
|
|
itemCount: _filteredEmployees.length,
|
|
itemBuilder: (context, index) {
|
|
final employee = _filteredEmployees[index];
|
|
return Card(
|
|
margin: EdgeInsets.zero,
|
|
child: ListTile(
|
|
leading: CircleAvatar(
|
|
backgroundColor: Colors.purple.shade100,
|
|
child: Text('${employee.department.substring(0, 1)}', style: const TextStyle(fontWeight: FontWeight.bold)),
|
|
),
|
|
title: Text(employee.name ?? '未入力', style: const TextStyle(fontWeight: FontWeight.w500)),
|
|
subtitle: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
if (employee.department.isNotEmpty) Text('部署:${employee.department}', style: const TextStyle(fontSize: 12)),
|
|
if (employee.role.isNotEmpty) Text('役職:${employee.role}', style: const TextStyle(fontSize: 12)),
|
|
if (employee.tel.isNotEmpty) Text('TEL: ${employee.tel}', style: const TextStyle(fontSize: 10, color: Colors.grey)),
|
|
],
|
|
),
|
|
trailing: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
IconButton(icon: const Icon(Icons.edit), onPressed: () => _editEmployee(employee)),
|
|
IconButton(icon: const Icon(Icons.delete_outline), onPressed: () => _deleteEmployee(employee)),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
} |