feat: 担当マスタリッチ化(M5 担当マスタ)

This commit is contained in:
joe 2026-03-11 20:48:01 +09:00
parent bd1e2be03e
commit d036f43e2f
3 changed files with 570 additions and 231 deletions

View file

@ -0,0 +1,70 @@
# 少プロジェクト計画書 - 担当マスタ機能リッチ化
## プロジェクト概要
| 項目 | 内容 |
|------|------|
| **プロジェクト名** | 担当マスタM5リッチ化改修 |
| **実装範囲** | Employee モデル・編集ダイアログ・画面の全面刷新 |
| **実装目標** | リッチなフォームコントロールによるユーザー体験向上 |
## 実装方針
### フェーズ 1: エンティティモデル定義(完了)
- [x] `lib/models/employee.dart` - Employee モデル
- [ ] `lib/models/sample_employee.dart` - サンプルデータ
### フェーズ 2: リッチ編集ダイアログ実装
- [ ] **リッチな入力フィールド**
- アイコン付き TextField
- ラベル付きセクション分け
- ヒント表示機能
- [ ] **フォームデザイン**
- カード型レイアウト
- セクションヘッダー(基本情報・部署情報)
- 適切な余白配置
- [ ] **ダイアログ UI**
- テーマカラー適用
- アクションボタンの Flex 配置
- キャンセル/保存ボタンの明確な区別
### フェーズ 3: リッチマスタ画面実装
- [ ] **リスト表示**
- データグリッド型レイアウト(または Cards
- アイコン付き操作ボタン
- スwipe-to-action機能検討
- [ ] **検索/フィルタリング**
- 複数フィールド同時検索
- ショートカットキーサポート
- [ ] **サンプルデータ充実**
- 5~10 件のテストデータ
- 多様な部署・役職設定
### フェーズ 4: テストとデプロイ
- [ ] エミュレータでの動作確認
- [ ] ビルド済み APK の生成
- [ ] データベース永続化の検証
## 実装スケジュール
| タスク | スコープ | 優先度 |
|--------|----------|--------|
| リッチ編集ダイアログ | high | P1 |
| リッチマスタ画面 | high | P1 |
| サンプルデータ拡張 | medium | P2 |
| ドキュメント更新 | low | P3 |
## 技術的考慮事項
- **Material Design 3**: テーマカラーの適切な使用
- **アクセシビリティ**: 適切なタッチターゲットサイズ
- **パフォーマンス**: リスト描画最適化ListView.builder
- **永続化**: SQLite データベースへの対応準備
---
*Version: 1.0 | Last Updated: 2026/3/11*

View file

@ -1,11 +1,11 @@
// Version: 2.0 -
// EmployeeEditDialog 使
// Version: 1.0 -
// EmployeeEditDialog 使
import 'package:flutter/material.dart';
import '../models/employee.dart';
import '../widgets/employee_edit_dialog.dart';
///
///
class EmployeeMasterScreen extends StatefulWidget {
const EmployeeMasterScreen({super.key});
@ -15,12 +15,13 @@ class EmployeeMasterScreen extends StatefulWidget {
class _EmployeeMasterScreenState extends State<EmployeeMasterScreen> {
List<Employee> _employees = [];
bool _loading = true;
String _searchKeyword = '';
//
String get _filteredEmployees => _searchKeyword.isEmpty ? _employees :
_employees.where((e) => e.name.toLowerCase().contains(_searchKeyword.toLowerCase()) ||
(e.department.isNotEmpty && e.department.toLowerCase().contains(_searchKeyword.toLowerCase()))).toList();
//
const static List<String> _sections = [
'営業部', '総務部', '経理部', '開発部', '製造部',
'品質管理', 'HR', '法務', '物流部', 'IT'
];
@override
void initState() {
@ -28,17 +29,25 @@ class _EmployeeMasterScreenState extends State<EmployeeMasterScreen> {
_loadEmployees();
}
///
///
Future<void> _loadEmployees() async {
setState(() => _loading = true);
try {
//
// 8
final demoData = [
Employee(id: 1, name: '山田太郎', email: 'tanaka@company.com', tel: '03-1234-5678', department: '営業部', role: '営業担当'),
Employee(id: 2, name: '田中花子', email: 'tanaka@company.com', tel: '03-2345-6789', department: '総務部', role: '総務担当'),
Employee(id: 3, name: '鈴木一郎', email: 'suzuki@company.com', tel: '03-3456-7890', department: '経理部', role: '経理担当'),
Employee(id: 1, name: '山田太郎', email: 'tanaka@company.com', tel: '03-5234-5678', department: '営業部', role: '営業部長'),
Employee(id: 2, name: '田中花子', email: 'hanako@company.com', tel: '03-5345-6789', department: '営業部', role: '営業担当'),
Employee(id: 3, name: '鈴木一郎', email: 'suzuki@company.com', tel: '03-5456-7890', department: '総務部', role: '総務主任'),
Employee(id: 4, name: '高橋美咲', email: 'misaki@company.com', tel: '03-5567-8901', department: '経理部', role: '経理担当者'),
Employee(id: 5, name: '伊藤健太', email: 'kenta@company.com', tel: '03-5678-9012', department: '開発部', role: '開発リーダー'),
Employee(id: 6, name: '渡辺愛', email: 'mana@company.com', tel: '03-5789-0123', department: '製造部', role: '品質管理担当'),
Employee(id: 7, name: '中村誠', email: 'makoto@company.com', tel: '03-5890-1234', department: '開発部', role: 'ソフトウェアエンジニア'),
Employee(id: 8, name: '小林裕子', email: 'yuko@company.com', tel: '03-5901-2345', department: 'HR', role: '人事担当者'),
];
if (mounted) {
setState(() => _employees = demoData);
}
} catch (e) {
if (mounted) ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('読み込みエラー:$e'), backgroundColor: Colors.red),
@ -48,68 +57,74 @@ class _EmployeeMasterScreenState extends State<EmployeeMasterScreen> {
}
}
///
///
List<Employee> get _filteredEmployees {
if (_searchKeyword.isEmpty) return _employees;
return _employees.where((e) =>
e.name.toLowerCase().contains(_searchKeyword.toLowerCase()) ||
(e.department.isNotEmpty && e.department.toLowerCase().contains(_searchKeyword.toLowerCase())) ||
(e.role.isNotEmpty && e.role.toLowerCase().contains(_searchKeyword.toLowerCase()))
).toList();
}
///
Future<void> _addEmployee() async {
final edited = await showDialog<Employee>(
final edited = await EmployeeEditDialog.show(
context: context,
builder: (ctx) => EmployeeEditDialog(
title: '担当者登録',
initialData: null,
),
onSave: (employee) => setState(() => _employees.insert(0, employee)),
);
if (edited != null && mounted) {
setState(() => _employees.add(edited));
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('担当者登録完了'), backgroundColor: Colors.green),
SnackBar(content: Text('担当者 "${edited.name}" を登録しました'), backgroundColor: Colors.green),
);
}
}
///
///
Future<void> _editEmployee(Employee employee) async {
final edited = await showDialog<Employee>(
final edited = await EmployeeEditDialog.show(
context: context,
builder: (ctx) => EmployeeEditDialog(
title: '担当者編集',
title: '担当者情報編集',
initialData: employee,
),
onSave: (updated) => setState(() {
_employees = _employees.map((e) => e.id == updated.id ? updated : e).toList();
}),
);
if (edited != null && mounted) {
setState(() {
_employees = _employees.map((e) => e.id == edited.id ? edited : e).toList();
});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('担当者更新完了'), backgroundColor: Colors.green),
const SnackBar(content: Text('担当者情報を更新しました'), backgroundColor: Colors.green),
);
}
}
///
///
Future<void> _deleteEmployee(Employee employee) async {
final confirmed = await showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('担当者削除'),
content: Text('この担当者を実際に削除しますか?'),
content: Text('本当に "${employee.name}" を削除しますか?'),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
actions: [
TextButton(onPressed: () => Navigator.pop(ctx), child: const Text('キャンセル')),
ElevatedButton(
onPressed: () => Navigator.pop(ctx, true),
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
child: const Text('削除'),
style: ElevatedButton.styleFrom(backgroundColor: Colors.red.shade100),
child: const Text('削除', style: TextStyle(color: Colors.red)),
),
],
),
);
if (confirmed == true && mounted) {
setState(() {
_employees.removeWhere((e) => e.id == employee.id);
});
setState(() => _employees.removeWhere((e) => e.id == employee.id));
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('担当者削除完了'), backgroundColor: Colors.green),
const SnackBar(content: Text('担当者を削除しました'), backgroundColor: Colors.green),
);
}
}
@ -121,78 +136,233 @@ class _EmployeeMasterScreenState extends State<EmployeeMasterScreen> {
title: const Text('/M5. 担当者マスタ'),
actions: [
IconButton(icon: const Icon(Icons.refresh), onPressed: _loadEmployees),
IconButton(icon: const Icon(Icons.add), onPressed: _addEmployee),
IconButton(
icon: const Icon(Icons.add_circle_outline),
onPressed: _addEmployee,
tooltip: '新規登録',
),
],
),
body: Column(
children: [
//
Padding(
padding: const EdgeInsets.all(8.0),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: Colors.grey.shade50,
border: Border(
bottom: BorderSide(color: Colors.grey.shade200),
),
),
child: Row(
children: [
Icon(Icons.search, size: 18, color: Colors.grey.shade600),
const SizedBox(width: 8),
Expanded(
child: TextField(
decoration: InputDecoration(
hintText: '担当者名で検索...',
prefixIcon: const Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
hintText: '担当者名・部署で検索...',
hintStyle: TextStyle(color: Colors.grey.shade400),
border: InputBorder.none,
contentPadding: EdgeInsets.zero,
),
onChanged: (value) => setState(() => _searchKeyword = value),
style: const TextStyle(fontSize: 14),
),
),
//
],
),
),
//
Expanded(
child: _loading ? const Center(child: CircularProgressIndicator()) :
_filteredEmployees.isEmpty ? Center(
child: _loading ? _buildLoadingIndicator() :
_filteredEmployees.isEmpty ? _buildEmptyState() :
_buildRichList(),
),
],
),
floatingActionButton: FloatingActionButton.extended(
onPressed: _addEmployee,
icon: const Icon(Icons.person_add),
label: const Text('新規登録'),
),
);
}
///
Widget _buildLoadingIndicator() {
return 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(
SizedBox(
width: 48,
height: 48,
child: CircularProgressIndicator(),
),
const SizedBox(height: 16),
const Text('担当者情報を取得中...', style: TextStyle(fontSize: 14)),
],
),
);
}
///
Widget _buildEmptyState() {
return Center(
child: Padding(
padding: const EdgeInsets.all(32),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.people_outline, size: 80, color: Colors.grey.shade300),
const SizedBox(height: 16),
const Text('担当者データがありません', style: TextStyle(fontSize: 18)),
const SizedBox(height: 24),
OutlinedButton.icon(
onPressed: _addEmployee,
icon: const Icon(Icons.add),
icon: const Icon(Icons.person_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)),
],
),
),
);
}
///
Widget _buildRichList() {
return ListView.separated(
padding: EdgeInsets.zero,
itemCount: _filteredEmployees.length,
separatorBuilder: (context, index) => Container(height: 1),
itemBuilder: (context, index) {
final employee = _filteredEmployees[index];
return _buildEmployeeCard(employee);
},
);
}
///
Widget _buildEmployeeCard(Employee employee) {
return Container(
margin: const EdgeInsets.all(2),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade200),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Row(
children: [
//
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.purple.shade50,
borderRadius: BorderRadius.circular(10),
),
child: Icon(
_getDepartmentIcon(employee.department),
size: 24,
color: Colors.purple.shade700,
),
),
const SizedBox(width: 12),
//
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
employee.name ?? '未入力',
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
),
if (_getDepartmentIcon(employee.department) != Icons.business_rounded) ...[
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(6),
),
child: Text(
employee.department.isEmpty ? '未入力' : employee.department,
style: const TextStyle(fontSize: 11, fontWeight: FontWeight.w500),
),
),
],
],
),
const SizedBox(height: 4),
Row(
children: [
if (employee.role.isNotEmpty)
Expanded(
child: Text(
employee.role,
style: TextStyle(fontSize: 12, color: Colors.grey.shade600),
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 4),
if (employee.tel.isNotEmpty)
Icon(Icons.phone, size: 14, color: Colors.grey.shade500),
],
),
],
),
),
//
Padding(
padding: const EdgeInsets.only(left: 8),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.edit, size: 18),
onPressed: () => _editEmployee(employee),
tooltip: '編集',
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
SizedBox(width: 2),
IconButton(
icon: const Icon(Icons.delete_outline, size: 18),
onPressed: () => _deleteEmployee(employee),
tooltip: '削除',
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
],
),
),
],
),
);
}
///
IconData _getDepartmentIcon(String department) {
if (department.contains('営業')) return Icons.work_outline;
if (department.contains('総務')) return Icons.settings_applications_outlined;
if (department.contains('経理')) return Icons.attach_money_outlined;
if (department.contains('開発')) return Icons.code_outlined;
if (department.contains('製造')) return Icons.construction_outlined;
if (department.contains('HR')) return Icons.groups_outlined;
if (department.contains('物流')) return Icons.local_shipping_outlined;
if (department.contains('IT')) return Icons.cloud_outlined;
return Icons.business_rounded;
}
}

View file

@ -1,6 +1,4 @@
//
// Employee
// Version: 1.0 -
import 'package:flutter/material.dart';
import '../models/employee.dart';
@ -8,13 +6,15 @@ import '../models/employee.dart';
class EmployeeEditDialog extends StatefulWidget {
final String title;
final Employee? initialData; // null =
final void Function(Employee) onSave; //
/// Employee
final void Function(Employee)? onSave;
const EmployeeEditDialog({
super.key,
required this.title,
this.initialData,
required this.onSave,
this.onSave,
});
@override
@ -32,11 +32,19 @@ class _EmployeeEditDialogState extends State<EmployeeEditDialog> {
void initState() {
super.initState();
final data = widget.initialData;
nameController = TextEditingController(text: data?.name ?? '');
emailController = TextEditingController(text: data?.email ?? '');
telController = TextEditingController(text: data?.tel ?? '');
departmentController = TextEditingController(text: data?.department ?? '');
roleController = TextEditingController(text: data?.role ?? '');
if (data == null) {
nameController = TextEditingController(text: '');
emailController = TextEditingController(text: '');
telController = TextEditingController(text: '');
departmentController = TextEditingController(text: '');
roleController = TextEditingController(text: '');
} else {
nameController = TextEditingController(text: data.name);
emailController = TextEditingController(text: data.email);
telController = TextEditingController(text: data.tel);
departmentController = TextEditingController(text: data.department);
roleController = TextEditingController(text: data.role);
}
}
@override
@ -50,10 +58,10 @@ class _EmployeeEditDialogState extends State<EmployeeEditDialog> {
}
///
Widget _buildRichTextField(
String label,
TextEditingController controller, {
TextInputType? keyboard,
Widget _buildRichTextField({
required String label,
required TextEditingController controller, {
TextInputType? keyboardType,
IconData? icon,
String hint = '',
}) {
@ -64,22 +72,32 @@ class _EmployeeEditDialogState extends State<EmployeeEditDialog> {
children: [
Text(
label,
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13, color: Colors.grey.shade700),
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 13,
color: Colors.grey.shade700,
),
),
const SizedBox(height: 4),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: Theme.of(context).cardColor.withOpacity(0.3),
border: Border.all(color: Theme.of(context).dividerColor),
borderRadius: BorderRadius.circular(8),
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
if (icon != null)
Icon(icon, size: 16, color: Theme.of(context).primaryColor),
const SizedBox(width: 8),
Expanded(
child: TextField(
controller: controller,
keyboardType: keyboard,
keyboardType: keyboardType,
style: const TextStyle(fontSize: 14),
decoration: InputDecoration(
hintText: hint.isEmpty ? null : hint,
prefixIcon: Icon(icon, size: 16, color: Theme.of(context).primaryColor),
border: InputBorder.none,
contentPadding: EdgeInsets.zero,
),
@ -87,6 +105,28 @@ class _EmployeeEditDialogState extends State<EmployeeEditDialog> {
),
],
),
),
],
),
);
}
/// static
static Future<Employee?> show({
required BuildContext context,
required String title,
required Employee? initialData,
required void Function(Employee) onSave,
}) async {
final dialog = EmployeeEditDialog(
title: title,
initialData: initialData,
);
//
showDialog<Employee>(
context: context,
builder: (ctx) => dialog._build(context),
);
}
@ -94,6 +134,7 @@ class _EmployeeEditDialogState extends State<EmployeeEditDialog> {
Widget build(BuildContext context) {
return Dialog(
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Container(
constraints: const BoxConstraints(maxWidth: 420),
padding: const EdgeInsets.all(16),
@ -102,134 +143,182 @@ class _EmployeeEditDialogState extends State<EmployeeEditDialog> {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
//
//
Row(
children: [
Icon(Icons.person, size: 20, color: Theme.of(context).primaryColor),
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Theme.of(context).primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(Icons.person, size: 24, color: Theme.of(context).primaryColor),
),
const SizedBox(width: 12),
Expanded(child: Text(
widget.title,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
)),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Colors.grey.shade200,
borderRadius: BorderRadius.circular(8),
),
child: Text(
widget.initialData == null ? '新規' : '編集',
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500),
),
),
IconButton(
icon: Icon(Icons.close, color: Colors.grey),
onPressed: () => Navigator.pop(context),
),
],
),
const SizedBox(height: 12),
const SizedBox(height: 16),
//
Center(
child: Text(
'新規作成の場合は「空白」から入力して OK を押してください',
style: TextStyle(fontSize: 12, color: Colors.grey.shade500, fontStyle: FontStyle.italic),
widget.initialData == null
? '担当者情報を登録してください'
: '担当者情報を更新してください',
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade500,
fontStyle: FontStyle.italic,
),
),
const SizedBox(height: 8),
),
const SizedBox(height: 16),
//
//
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
color: Theme.of(context).primaryColor.withOpacity(0.05),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Theme.of(context).dividerColor),
),
padding: const EdgeInsets.all(12),
child: Column(
child: Row(
children: [
Icon(Icons.business, size: 20, color: Theme.of(context).primaryColor),
const SizedBox(width: 8),
Expanded(
child: Text(
'担当者情報を入力してください',
style: TextStyle(fontWeight: FontWeight.w500, fontSize: 14),
),
),
],
),
),
const SizedBox(height: 16),
//
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//
//
Text(
'■ 基本情報',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Theme.of(context).primaryColor,
letterSpacing: 0.5,
),
),
const SizedBox(height: 8),
const SizedBox(height: 12),
//
_buildRichTextField(
'氏名 *',
nameController,
keyboard: TextInputType.name,
label: '氏名 *',
controller: nameController,
keyboardType: TextInputType.name,
icon: Icons.person,
hint: e.g., '山田太郎',
hint: '山田太郎',
),
//
_buildRichTextField(
'E メール *',
emailController,
keyboard: TextInputType.emailAddress,
label: 'E メール *',
controller: emailController,
keyboardType: TextInputType.emailAddress,
icon: Icons.email,
hint: 'example@company.com',
hint: 'tanaka@company.com',
),
//
_buildRichTextField(
'電話番号 *',
telController,
keyboard: TextInputType.phone,
label: '電話番号 *',
controller: telController,
keyboardType: TextInputType.phone,
icon: Icons.phone,
hint: '03-1234-5678',
),
const Divider(),
const Divider(height: 24),
//
//
Text(
'■ 部署・役職',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Theme.of(context).primaryColor,
letterSpacing: 0.5,
),
),
const SizedBox(height: 8),
const SizedBox(height: 12),
//
_buildRichTextField(
'部署 *',
departmentController,
keyboard: TextInputType.text,
label: '部署 *',
controller: departmentController,
keyboardType: TextInputType.text,
icon: Icons.business,
hint: '営業部',
),
//
_buildRichTextField(
'役職 *',
roleController,
keyboard: TextInputType.text,
label: '役職 *',
controller: roleController,
keyboardType: TextInputType.text,
icon: Icons.badge,
hint: '営業担当',
),
],
),
),
const SizedBox(height: 16),
// Flex
Row(
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey.shade50,
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Expanded(
child: OutlinedButton(
child: OutlinedButton.icon(
onPressed: () => Navigator.pop(context),
icon: Icon(Icons.close, size: 18),
label: const Text(' キャンセル ', style: TextStyle(fontSize: 14)),
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14),
padding: const EdgeInsets.symmetric(vertical: 12),
side: BorderSide(color: Colors.grey.shade300),
),
child: Text(' キャンセル ', textAlign: TextAlign.center, style: TextStyle(fontSize: 15)),
),
),
const SizedBox(width: 8),
Expanded(
flex: 3, //
child: ElevatedButton(
child: ElevatedButton.icon(
onPressed: () {
if (widget.onSave != null) {
final employee = Employee(
id: widget.initialData?.id ?? -1,
name: nameController.text.isEmpty ? widget.initialData?.name ?? '未入力' : nameController.text,
@ -239,19 +328,29 @@ class _EmployeeEditDialogState extends State<EmployeeEditDialog> {
role: roleController.text.isEmpty ? widget.initialData?.role ?? '未入力' : roleController.text,
);
widget.onSave(employee);
}
},
icon: Icon(Icons.save, size: 18),
label: const Text(' 保存 ', style: TextStyle(fontSize: 14)),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14),
padding: const EdgeInsets.symmetric(vertical: 12),
backgroundColor: Theme.of(context).primaryColor,
foregroundColor: Colors.white,
),
child: Text(' 保存 ', textAlign: TextAlign.center, style: TextStyle(fontSize: 15)),
),
),
],
),
),
],
),
),
),
);
}
/// static 使
Widget _build(BuildContext context) {
return this;
}
}