feat: 担当マスタリッチ化(M5 担当マスタ)
This commit is contained in:
parent
bd1e2be03e
commit
d036f43e2f
3 changed files with 570 additions and 231 deletions
70
@workspace/docs/short_term_plan.md
Normal file
70
@workspace/docs/short_term_plan.md
Normal 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*
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
// Version: 2.0 - 担当者マスタ画面(リッチ編集ダイアログ統合)
|
// Version: 1.0 - 担当者マスタ画面(リッチリスト実装)
|
||||||
// ※ EmployeeEditDialog を使用した簡易実装
|
// ※ EmployeeEditDialog を使用した完全機能版
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import '../models/employee.dart';
|
import '../models/employee.dart';
|
||||||
import '../widgets/employee_edit_dialog.dart';
|
import '../widgets/employee_edit_dialog.dart';
|
||||||
|
|
||||||
/// 担当者マスタ管理画面
|
/// 担当者マスタ管理画面(リッチリスト)
|
||||||
class EmployeeMasterScreen extends StatefulWidget {
|
class EmployeeMasterScreen extends StatefulWidget {
|
||||||
const EmployeeMasterScreen({super.key});
|
const EmployeeMasterScreen({super.key});
|
||||||
|
|
||||||
|
|
@ -15,12 +15,13 @@ class EmployeeMasterScreen extends StatefulWidget {
|
||||||
|
|
||||||
class _EmployeeMasterScreenState extends State<EmployeeMasterScreen> {
|
class _EmployeeMasterScreenState extends State<EmployeeMasterScreen> {
|
||||||
List<Employee> _employees = [];
|
List<Employee> _employees = [];
|
||||||
bool _loading = true;
|
String _searchKeyword = '';
|
||||||
|
|
||||||
// 検索キーワード
|
// セクション定義
|
||||||
String get _filteredEmployees => _searchKeyword.isEmpty ? _employees :
|
const static List<String> _sections = [
|
||||||
_employees.where((e) => e.name.toLowerCase().contains(_searchKeyword.toLowerCase()) ||
|
'営業部', '総務部', '経理部', '開発部', '製造部',
|
||||||
(e.department.isNotEmpty && e.department.toLowerCase().contains(_searchKeyword.toLowerCase()))).toList();
|
'品質管理', 'HR', '法務', '物流部', 'IT'
|
||||||
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
@ -28,17 +29,25 @@ class _EmployeeMasterScreenState extends State<EmployeeMasterScreen> {
|
||||||
_loadEmployees();
|
_loadEmployees();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 従業員データをロード(デモデータ)
|
/// 従業員データをロード(サンプルデータ)
|
||||||
Future<void> _loadEmployees() async {
|
Future<void> _loadEmployees() async {
|
||||||
setState(() => _loading = true);
|
setState(() => _loading = true);
|
||||||
try {
|
try {
|
||||||
// サンプルデータを初期化
|
// リッチなサンプルデータ(8 件)
|
||||||
final demoData = [
|
final demoData = [
|
||||||
Employee(id: 1, name: '山田太郎', email: 'tanaka@company.com', tel: '03-1234-5678', department: '営業部', role: '営業担当'),
|
Employee(id: 1, name: '山田太郎', email: 'tanaka@company.com', tel: '03-5234-5678', department: '営業部', role: '営業部長'),
|
||||||
Employee(id: 2, name: '田中花子', email: 'tanaka@company.com', tel: '03-2345-6789', 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-3456-7890', 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: '人事担当者'),
|
||||||
];
|
];
|
||||||
setState(() => _employees = demoData);
|
|
||||||
|
if (mounted) {
|
||||||
|
setState(() => _employees = demoData);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) ScaffoldMessenger.of(context).showSnackBar(
|
if (mounted) ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text('読み込みエラー:$e'), backgroundColor: Colors.red),
|
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 {
|
Future<void> _addEmployee() async {
|
||||||
final edited = await showDialog<Employee>(
|
final edited = await EmployeeEditDialog.show(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (ctx) => EmployeeEditDialog(
|
title: '担当者登録',
|
||||||
title: '担当者登録',
|
initialData: null,
|
||||||
initialData: null,
|
onSave: (employee) => setState(() => _employees.insert(0, employee)),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (edited != null && mounted) {
|
if (edited != null && mounted) {
|
||||||
setState(() => _employees.add(edited));
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
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 {
|
Future<void> _editEmployee(Employee employee) async {
|
||||||
final edited = await showDialog<Employee>(
|
final edited = await EmployeeEditDialog.show(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (ctx) => EmployeeEditDialog(
|
title: '担当者情報編集',
|
||||||
title: '担当者編集',
|
initialData: employee,
|
||||||
initialData: employee,
|
onSave: (updated) => setState(() {
|
||||||
),
|
_employees = _employees.map((e) => e.id == updated.id ? updated : e).toList();
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (edited != null && mounted) {
|
if (edited != null && mounted) {
|
||||||
setState(() {
|
|
||||||
_employees = _employees.map((e) => e.id == edited.id ? edited : e).toList();
|
|
||||||
});
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(content: Text('担当者更新完了'), backgroundColor: Colors.green),
|
const SnackBar(content: Text('担当者情報を更新しました'), backgroundColor: Colors.green),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 従業員削除
|
/// 従業員削除確認ダイアログ
|
||||||
Future<void> _deleteEmployee(Employee employee) async {
|
Future<void> _deleteEmployee(Employee employee) async {
|
||||||
final confirmed = await showDialog<bool>(
|
final confirmed = await showDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (ctx) => AlertDialog(
|
builder: (ctx) => AlertDialog(
|
||||||
title: const Text('担当者削除'),
|
title: const Text('担当者削除'),
|
||||||
content: Text('この担当者を実際に削除しますか?'),
|
content: Text('本当に "${employee.name}" を削除しますか?'),
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(onPressed: () => Navigator.pop(ctx), child: const Text('キャンセル')),
|
TextButton(onPressed: () => Navigator.pop(ctx), child: const Text('キャンセル')),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () => Navigator.pop(ctx, true),
|
onPressed: () => Navigator.pop(ctx, true),
|
||||||
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
|
style: ElevatedButton.styleFrom(backgroundColor: Colors.red.shade100),
|
||||||
child: const Text('削除'),
|
child: const Text('削除', style: TextStyle(color: Colors.red)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (confirmed == true && mounted) {
|
if (confirmed == true && mounted) {
|
||||||
setState(() {
|
setState(() => _employees.removeWhere((e) => e.id == employee.id));
|
||||||
_employees.removeWhere((e) => e.id == employee.id);
|
|
||||||
});
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
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. 担当者マスタ'),
|
title: const Text('/M5. 担当者マスタ'),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(icon: const Icon(Icons.refresh), onPressed: _loadEmployees),
|
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(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
// 検索バー
|
// 検索バー
|
||||||
Padding(
|
Container(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
child: TextField(
|
decoration: BoxDecoration(
|
||||||
decoration: InputDecoration(
|
color: Colors.grey.shade50,
|
||||||
hintText: '担当者名で検索...',
|
border: Border(
|
||||||
prefixIcon: const Icon(Icons.search),
|
bottom: BorderSide(color: Colors.grey.shade200),
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
onChanged: (value) => setState(() => _searchKeyword = value),
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.search, size: 18, color: Colors.grey.shade600),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: '担当者名・部署で検索...',
|
||||||
|
hintStyle: TextStyle(color: Colors.grey.shade400),
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
),
|
||||||
|
onChanged: (value) => setState(() => _searchKeyword = value),
|
||||||
|
style: const TextStyle(fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// 一覧リスト
|
// リッチリストエリア
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _loading ? const Center(child: CircularProgressIndicator()) :
|
child: _loading ? _buildLoadingIndicator() :
|
||||||
_filteredEmployees.isEmpty ? Center(
|
_filteredEmployees.isEmpty ? _buildEmptyState() :
|
||||||
child: Column(
|
_buildRichList(),
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
),
|
||||||
children: [
|
],
|
||||||
Icon(Icons.person_outline, size: 64, color: Colors.grey[300]),
|
),
|
||||||
SizedBox(height: 16),
|
floatingActionButton: FloatingActionButton.extended(
|
||||||
Text('担当者データがありません', style: TextStyle(color: Colors.grey)),
|
onPressed: _addEmployee,
|
||||||
SizedBox(height: 16),
|
icon: const Icon(Icons.person_add),
|
||||||
ElevatedButton.icon(
|
label: const Text('新規登録'),
|
||||||
onPressed: _addEmployee,
|
),
|
||||||
icon: const Icon(Icons.add),
|
);
|
||||||
label: const Text('新規登録'),
|
}
|
||||||
),
|
|
||||||
],
|
/// ローディングインジケーター
|
||||||
|
Widget _buildLoadingIndicator() {
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
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.person_add),
|
||||||
|
label: const Text('新規登録'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// リッチリストビルダー
|
||||||
|
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),
|
||||||
),
|
),
|
||||||
) : ListView.builder(
|
if (_getDepartmentIcon(employee.department) != Icons.business_rounded) ...[
|
||||||
padding: const EdgeInsets.all(8),
|
const SizedBox(width: 8),
|
||||||
itemCount: _filteredEmployees.length,
|
Container(
|
||||||
itemBuilder: (context, index) {
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||||
final employee = _filteredEmployees[index];
|
decoration: BoxDecoration(
|
||||||
return Card(
|
color: Colors.grey.shade100,
|
||||||
margin: EdgeInsets.zero,
|
borderRadius: BorderRadius.circular(6),
|
||||||
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)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
// 従業員編集ダイアログ(リッチ版)
|
// Version: 1.0 - 担当従業員編集ダイアログ(リッチ実装)
|
||||||
// ※ Employee モデルに特化した編集用ダイアログ
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import '../models/employee.dart';
|
import '../models/employee.dart';
|
||||||
|
|
||||||
|
|
@ -8,13 +6,15 @@ import '../models/employee.dart';
|
||||||
class EmployeeEditDialog extends StatefulWidget {
|
class EmployeeEditDialog extends StatefulWidget {
|
||||||
final String title;
|
final String title;
|
||||||
final Employee? initialData; // null = 新規作成
|
final Employee? initialData; // null = 新規作成
|
||||||
final void Function(Employee) onSave; // 保存コールバック
|
|
||||||
|
/// 保存時のコールバック(Employee のデータを返す)
|
||||||
|
final void Function(Employee)? onSave;
|
||||||
|
|
||||||
const EmployeeEditDialog({
|
const EmployeeEditDialog({
|
||||||
super.key,
|
super.key,
|
||||||
required this.title,
|
required this.title,
|
||||||
this.initialData,
|
this.initialData,
|
||||||
required this.onSave,
|
this.onSave,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -32,11 +32,19 @@ class _EmployeeEditDialogState extends State<EmployeeEditDialog> {
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
final data = widget.initialData;
|
final data = widget.initialData;
|
||||||
nameController = TextEditingController(text: data?.name ?? '');
|
if (data == null) {
|
||||||
emailController = TextEditingController(text: data?.email ?? '');
|
nameController = TextEditingController(text: '');
|
||||||
telController = TextEditingController(text: data?.tel ?? '');
|
emailController = TextEditingController(text: '');
|
||||||
departmentController = TextEditingController(text: data?.department ?? '');
|
telController = TextEditingController(text: '');
|
||||||
roleController = TextEditingController(text: data?.role ?? '');
|
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
|
@override
|
||||||
|
|
@ -50,10 +58,10 @@ class _EmployeeEditDialogState extends State<EmployeeEditDialog> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// リッチな入力フィールドビルダー(共通)
|
/// リッチな入力フィールドビルダー(共通)
|
||||||
Widget _buildRichTextField(
|
Widget _buildRichTextField({
|
||||||
String label,
|
required String label,
|
||||||
TextEditingController controller, {
|
required TextEditingController controller, {
|
||||||
TextInputType? keyboard,
|
TextInputType? keyboardType,
|
||||||
IconData? icon,
|
IconData? icon,
|
||||||
String hint = '',
|
String hint = '',
|
||||||
}) {
|
}) {
|
||||||
|
|
@ -64,25 +72,38 @@ class _EmployeeEditDialogState extends State<EmployeeEditDialog> {
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
label,
|
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),
|
const SizedBox(height: 4),
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).cardColor.withOpacity(0.3),
|
||||||
border: Border.all(color: Theme.of(context).dividerColor),
|
border: Border.all(color: Theme.of(context).dividerColor),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
child: TextField(
|
child: Row(
|
||||||
controller: controller,
|
children: [
|
||||||
keyboardType: keyboard,
|
if (icon != null)
|
||||||
style: const TextStyle(fontSize: 14),
|
Icon(icon, size: 16, color: Theme.of(context).primaryColor),
|
||||||
decoration: InputDecoration(
|
const SizedBox(width: 8),
|
||||||
hintText: hint.isEmpty ? null : hint,
|
Expanded(
|
||||||
prefixIcon: Icon(icon, size: 16, color: Theme.of(context).primaryColor),
|
child: TextField(
|
||||||
border: InputBorder.none,
|
controller: controller,
|
||||||
contentPadding: EdgeInsets.zero,
|
keyboardType: keyboardType,
|
||||||
),
|
style: const TextStyle(fontSize: 14),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: hint.isEmpty ? null : hint,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -90,10 +111,30 @@ 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),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Dialog(
|
return Dialog(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||||
child: Container(
|
child: Container(
|
||||||
constraints: const BoxConstraints(maxWidth: 420),
|
constraints: const BoxConstraints(maxWidth: 420),
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
|
|
@ -102,151 +143,204 @@ class _EmployeeEditDialogState extends State<EmployeeEditDialog> {
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
// タイトル
|
// タイトルバー
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.person, size: 20, color: Theme.of(context).primaryColor),
|
Container(
|
||||||
const SizedBox(width: 8),
|
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(
|
Expanded(child: Text(
|
||||||
widget.title,
|
widget.title,
|
||||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
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(
|
IconButton(
|
||||||
icon: Icon(Icons.close, color: Colors.grey),
|
icon: Icon(Icons.close, color: Colors.grey),
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// ヒントテキスト
|
// ヒントテキスト
|
||||||
Center(
|
Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'新規作成の場合は「空白」から入力して OK を押してください',
|
widget.initialData == null
|
||||||
style: TextStyle(fontSize: 12, color: Colors.grey.shade500, fontStyle: FontStyle.italic),
|
? '担当者情報を登録してください'
|
||||||
|
: '担当者情報を更新してください',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.grey.shade500,
|
||||||
|
fontStyle: FontStyle.italic,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// リッチな編集フォーム
|
// ヒーダーセクション
|
||||||
Container(
|
Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).cardColor,
|
color: Theme.of(context).primaryColor.withOpacity(0.05),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
border: Border.all(color: Theme.of(context).dividerColor),
|
border: Border.all(color: Theme.of(context).dividerColor),
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.all(12),
|
child: Row(
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
// 基本情報セクション
|
Icon(Icons.business, size: 20, color: Theme.of(context).primaryColor),
|
||||||
Text(
|
const SizedBox(width: 8),
|
||||||
'■ 基本情報',
|
Expanded(
|
||||||
style: TextStyle(
|
child: Text(
|
||||||
fontSize: 14,
|
'担当者情報を入力してください',
|
||||||
fontWeight: FontWeight.bold,
|
style: TextStyle(fontWeight: FontWeight.w500, fontSize: 14),
|
||||||
color: Theme.of(context).primaryColor,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
|
||||||
|
|
||||||
// 名前フィールド
|
|
||||||
_buildRichTextField(
|
|
||||||
'氏名 *',
|
|
||||||
nameController,
|
|
||||||
keyboard: TextInputType.name,
|
|
||||||
icon: Icons.person,
|
|
||||||
hint: e.g., '山田太郎',
|
|
||||||
),
|
|
||||||
|
|
||||||
// メールアドレスフィールド
|
|
||||||
_buildRichTextField(
|
|
||||||
'E メール *',
|
|
||||||
emailController,
|
|
||||||
keyboard: TextInputType.emailAddress,
|
|
||||||
icon: Icons.email,
|
|
||||||
hint: 'example@company.com',
|
|
||||||
),
|
|
||||||
|
|
||||||
// 電話番号フィールド
|
|
||||||
_buildRichTextField(
|
|
||||||
'電話番号 *',
|
|
||||||
telController,
|
|
||||||
keyboard: TextInputType.phone,
|
|
||||||
icon: Icons.phone,
|
|
||||||
hint: '03-1234-5678',
|
|
||||||
),
|
|
||||||
|
|
||||||
const Divider(),
|
|
||||||
|
|
||||||
// 部署情報セクション
|
|
||||||
Text(
|
|
||||||
'■ 部署・役職',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Theme.of(context).primaryColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
|
|
||||||
// 部門フィールド
|
|
||||||
_buildRichTextField(
|
|
||||||
'部署 *',
|
|
||||||
departmentController,
|
|
||||||
keyboard: TextInputType.text,
|
|
||||||
icon: Icons.business,
|
|
||||||
hint: '営業部',
|
|
||||||
),
|
|
||||||
|
|
||||||
// 役職フィールド
|
|
||||||
_buildRichTextField(
|
|
||||||
'役職 *',
|
|
||||||
roleController,
|
|
||||||
keyboard: TextInputType.text,
|
|
||||||
icon: Icons.badge,
|
|
||||||
hint: '営業担当',
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
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: 12),
|
||||||
|
|
||||||
|
// 名前フィールド
|
||||||
|
_buildRichTextField(
|
||||||
|
label: '氏名 *',
|
||||||
|
controller: nameController,
|
||||||
|
keyboardType: TextInputType.name,
|
||||||
|
icon: Icons.person,
|
||||||
|
hint: '山田太郎',
|
||||||
|
),
|
||||||
|
|
||||||
|
// メールアドレスフィールド
|
||||||
|
_buildRichTextField(
|
||||||
|
label: 'E メール *',
|
||||||
|
controller: emailController,
|
||||||
|
keyboardType: TextInputType.emailAddress,
|
||||||
|
icon: Icons.email,
|
||||||
|
hint: 'tanaka@company.com',
|
||||||
|
),
|
||||||
|
|
||||||
|
// 電話番号フィールド
|
||||||
|
_buildRichTextField(
|
||||||
|
label: '電話番号 *',
|
||||||
|
controller: telController,
|
||||||
|
keyboardType: TextInputType.phone,
|
||||||
|
icon: Icons.phone,
|
||||||
|
hint: '03-1234-5678',
|
||||||
|
),
|
||||||
|
|
||||||
|
const Divider(height: 24),
|
||||||
|
|
||||||
|
// ──────────────── 部署情報 ────────────────
|
||||||
|
Text(
|
||||||
|
'■ 部署・役職',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
|
// 部門フィールド
|
||||||
|
_buildRichTextField(
|
||||||
|
label: '部署 *',
|
||||||
|
controller: departmentController,
|
||||||
|
keyboardType: TextInputType.text,
|
||||||
|
icon: Icons.business,
|
||||||
|
hint: '営業部',
|
||||||
|
),
|
||||||
|
|
||||||
|
// 役職フィールド
|
||||||
|
_buildRichTextField(
|
||||||
|
label: '役職 *',
|
||||||
|
controller: roleController,
|
||||||
|
keyboardType: TextInputType.text,
|
||||||
|
icon: Icons.badge,
|
||||||
|
hint: '営業担当',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// アクションボタン(Flex で配置)
|
// アクションボタン(Flex で配置)
|
||||||
Row(
|
Container(
|
||||||
children: [
|
padding: const EdgeInsets.all(12),
|
||||||
Expanded(
|
decoration: BoxDecoration(
|
||||||
child: OutlinedButton(
|
color: Colors.grey.shade50,
|
||||||
onPressed: () => Navigator.pop(context),
|
borderRadius: BorderRadius.circular(12),
|
||||||
style: OutlinedButton.styleFrom(
|
),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
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: 12),
|
||||||
|
side: BorderSide(color: Colors.grey.shade300),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Text(' キャンセル ', textAlign: TextAlign.center, style: TextStyle(fontSize: 15)),
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(width: 8),
|
||||||
const SizedBox(width: 8),
|
Expanded(
|
||||||
Expanded(
|
child: ElevatedButton.icon(
|
||||||
flex: 3, // より広いボタン
|
onPressed: () {
|
||||||
child: ElevatedButton(
|
if (widget.onSave != null) {
|
||||||
onPressed: () {
|
final employee = Employee(
|
||||||
final employee = Employee(
|
id: widget.initialData?.id ?? -1,
|
||||||
id: widget.initialData?.id ?? -1,
|
name: nameController.text.isEmpty ? widget.initialData?.name ?? '未入力' : nameController.text,
|
||||||
name: nameController.text.isEmpty ? widget.initialData?.name ?? '未入力' : nameController.text,
|
email: emailController.text.isEmpty ? widget.initialData?.email ?? '未入力' : emailController.text,
|
||||||
email: emailController.text.isEmpty ? widget.initialData?.email ?? '未入力' : emailController.text,
|
tel: telController.text.isEmpty ? widget.initialData?.tel ?? '未入力' : telController.text,
|
||||||
tel: telController.text.isEmpty ? widget.initialData?.tel ?? '未入力' : telController.text,
|
department: departmentController.text.isEmpty ? widget.initialData?.department ?? '未入力' : departmentController.text,
|
||||||
department: departmentController.text.isEmpty ? widget.initialData?.department ?? '未入力' : departmentController.text,
|
role: roleController.text.isEmpty ? widget.initialData?.role ?? '未入力' : roleController.text,
|
||||||
role: roleController.text.isEmpty ? widget.initialData?.role ?? '未入力' : roleController.text,
|
);
|
||||||
);
|
widget.onSave(employee);
|
||||||
widget.onSave(employee);
|
}
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
icon: Icon(Icons.save, size: 18),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
label: const Text(' 保存 ', style: TextStyle(fontSize: 14)),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
backgroundColor: Theme.of(context).primaryColor,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Text(' 保存 ', textAlign: TextAlign.center, style: TextStyle(fontSize: 15)),
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -254,4 +348,9 @@ class _EmployeeEditDialogState extends State<EmployeeEditDialog> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 公開用ビルドメソッド(static メソッドから使用)
|
||||||
|
Widget _build(BuildContext context) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue