356 lines
No EOL
13 KiB
Dart
356 lines
No EOL
13 KiB
Dart
// Version: 1.0 - 担当従業員編集ダイアログ(リッチ実装)
|
||
import 'package:flutter/material.dart';
|
||
import '../models/employee.dart';
|
||
|
||
/// 従業員用のリッチな編集ダイアログ
|
||
class EmployeeEditDialog extends StatefulWidget {
|
||
final String title;
|
||
final Employee? initialData; // null = 新規作成
|
||
|
||
/// 保存時のコールバック(Employee のデータを返す)
|
||
final void Function(Employee)? onSave;
|
||
|
||
const EmployeeEditDialog({
|
||
super.key,
|
||
required this.title,
|
||
this.initialData,
|
||
this.onSave,
|
||
});
|
||
|
||
@override
|
||
State<EmployeeEditDialog> createState() => _EmployeeEditDialogState();
|
||
}
|
||
|
||
class _EmployeeEditDialogState extends State<EmployeeEditDialog> {
|
||
late TextEditingController nameController;
|
||
late TextEditingController emailController;
|
||
late TextEditingController telController;
|
||
late TextEditingController departmentController;
|
||
late TextEditingController roleController;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
final data = widget.initialData;
|
||
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
|
||
void dispose() {
|
||
nameController.dispose();
|
||
emailController.dispose();
|
||
telController.dispose();
|
||
departmentController.dispose();
|
||
roleController.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
/// リッチな入力フィールドビルダー(共通)
|
||
Widget _buildRichTextField({
|
||
required String label,
|
||
required TextEditingController controller, {
|
||
TextInputType? keyboardType,
|
||
IconData? icon,
|
||
String hint = '',
|
||
}) {
|
||
return Padding(
|
||
padding: const EdgeInsets.only(bottom: 12),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
label,
|
||
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(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: keyboardType,
|
||
style: const TextStyle(fontSize: 14),
|
||
decoration: InputDecoration(
|
||
hintText: hint.isEmpty ? null : hint,
|
||
border: InputBorder.none,
|
||
contentPadding: EdgeInsets.zero,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
/// ダイアログ表示用 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
|
||
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),
|
||
child: SingleChildScrollView(
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||
children: [
|
||
// タイトルバー
|
||
Row(
|
||
children: [
|
||
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: 16),
|
||
|
||
// ヒントテキスト
|
||
Center(
|
||
child: Text(
|
||
widget.initialData == null
|
||
? '担当者情報を登録してください'
|
||
: '担当者情報を更新してください',
|
||
style: TextStyle(
|
||
fontSize: 12,
|
||
color: Colors.grey.shade500,
|
||
fontStyle: FontStyle.italic,
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(height: 16),
|
||
|
||
// ヒーダーセクション
|
||
Container(
|
||
padding: const EdgeInsets.all(12),
|
||
decoration: BoxDecoration(
|
||
color: Theme.of(context).primaryColor.withOpacity(0.05),
|
||
borderRadius: BorderRadius.circular(12),
|
||
border: Border.all(color: Theme.of(context).dividerColor),
|
||
),
|
||
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: 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),
|
||
|
||
// アクションボタン(Flex で配置)
|
||
Container(
|
||
padding: const EdgeInsets.all(12),
|
||
decoration: BoxDecoration(
|
||
color: Colors.grey.shade50,
|
||
borderRadius: BorderRadius.circular(12),
|
||
),
|
||
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),
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(width: 8),
|
||
Expanded(
|
||
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,
|
||
email: emailController.text.isEmpty ? widget.initialData?.email ?? '未入力' : emailController.text,
|
||
tel: telController.text.isEmpty ? widget.initialData?.tel ?? '未入力' : telController.text,
|
||
department: departmentController.text.isEmpty ? widget.initialData?.department ?? '未入力' : departmentController.text,
|
||
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: 12),
|
||
backgroundColor: Theme.of(context).primaryColor,
|
||
foregroundColor: Colors.white,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 公開用ビルドメソッド(static メソッドから使用)
|
||
Widget _build(BuildContext context) {
|
||
return this;
|
||
}
|
||
} |