h-1.flutter.4/@workspace/lib/widgets/employee_edit_dialog.dart

356 lines
No EOL
13 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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;
}
}