329 lines
No EOL
12 KiB
Dart
329 lines
No EOL
12 KiB
Dart
// Version: 1.2 - 従業員編集ダイアログ(簡易実装)
|
||
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(
|
||
String label,
|
||
TextEditingController controller, {
|
||
TextInputType? keyboard,
|
||
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(
|
||
border: Border.all(color: Theme.of(context).dividerColor),
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: TextField(
|
||
controller: controller,
|
||
keyboardType: keyboard,
|
||
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,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Dialog(
|
||
backgroundColor: Colors.white,
|
||
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: [
|
||
Icon(Icons.person, size: 20, color: Theme.of(context).primaryColor),
|
||
const SizedBox(width: 8),
|
||
Expanded(child: Text(
|
||
widget.title,
|
||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||
)),
|
||
IconButton(
|
||
icon: Icon(Icons.close, color: Colors.grey),
|
||
onPressed: () => Navigator.pop(context),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 12),
|
||
|
||
// ヒントテキスト
|
||
Center(
|
||
child: Text(
|
||
'新規作成の場合は「空白」から入力して OK を押してください',
|
||
style: TextStyle(fontSize: 12, color: Colors.grey.shade500, fontStyle: FontStyle.italic),
|
||
),
|
||
),
|
||
const SizedBox(height: 8),
|
||
|
||
// リッチな編集フォーム
|
||
Container(
|
||
decoration: BoxDecoration(
|
||
color: Theme.of(context).cardColor,
|
||
borderRadius: BorderRadius.circular(12),
|
||
border: Border.all(color: Theme.of(context).dividerColor),
|
||
),
|
||
padding: const EdgeInsets.all(12),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
// 基本情報セクション
|
||
Text(
|
||
'■ 基本情報',
|
||
style: TextStyle(
|
||
fontSize: 14,
|
||
fontWeight: FontWeight.bold,
|
||
color: Theme.of(context).primaryColor,
|
||
),
|
||
),
|
||
const SizedBox(height: 8),
|
||
|
||
// 名前フィールド
|
||
_buildRichTextField(
|
||
'氏名 *',
|
||
nameController,
|
||
keyboard: TextInputType.name,
|
||
icon: Icons.person,
|
||
hint: '山田太郎',
|
||
),
|
||
|
||
// メールアドレスフィールド
|
||
_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),
|
||
|
||
// アクションボタン(Flex で配置)
|
||
Row(
|
||
children: [
|
||
Expanded(
|
||
child: OutlinedButton(
|
||
onPressed: () => Navigator.pop(context),
|
||
style: OutlinedButton.styleFrom(
|
||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||
),
|
||
child: Text(' キャンセル ', textAlign: TextAlign.center, style: TextStyle(fontSize: 15)),
|
||
),
|
||
),
|
||
const SizedBox(width: 8),
|
||
Expanded(
|
||
flex: 3, // より広いボタン
|
||
child: ElevatedButton(
|
||
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);
|
||
}
|
||
},
|
||
style: ElevatedButton.styleFrom(
|
||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||
),
|
||
child: Text(' 保存 ', textAlign: TextAlign.center, style: TextStyle(fontSize: 15)),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
/// サンプル従業員選択ダイアログ(簡素版)
|
||
class EmployeeChoiceDialog extends StatelessWidget {
|
||
final List<Employee> employees;
|
||
final Function(Employee) onSelected;
|
||
|
||
const EmployeeChoiceDialog({super.key, required this.employees, required this.onSelected});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
if (employees.isEmpty) return Dialog(
|
||
child: Padding(
|
||
padding: const EdgeInsets.all(32),
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
Icon(Icons.search_off, size: 64, color: Colors.grey[300]),
|
||
const SizedBox(height: 16),
|
||
Text('検索結果がありません', style: TextStyle(color: Colors.grey, fontSize: 18)),
|
||
const SizedBox(height: 8),
|
||
Text('担当者データが登録されていないため\n選択できません', textAlign: TextAlign.center, style: TextStyle(color: Colors.grey.shade500)),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
|
||
return Dialog(
|
||
child: Container(
|
||
constraints: const BoxConstraints(maxWidth: 400),
|
||
padding: const EdgeInsets.all(12),
|
||
child: ListView.separated(
|
||
shrinkWrap: true,
|
||
itemCount: employees.length,
|
||
separatorBuilder: (_, __) => const Divider(height: 1),
|
||
itemBuilder: (ctx, index) {
|
||
final employee = employees[index];
|
||
return ListTile(
|
||
contentPadding: EdgeInsets.zero,
|
||
leading: CircleAvatar(
|
||
backgroundColor: Theme.of(context).primaryColor.withOpacity(0.1),
|
||
child: Icon(Icons.person, color: Theme.of(context).primaryColor),
|
||
),
|
||
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)),
|
||
],
|
||
),
|
||
trailing: employee.email.isNotEmpty ? Text(employee.email, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 10)) : null,
|
||
onTap: () => onSelected(employee),
|
||
);
|
||
},
|
||
),
|
||
),
|
||
);
|
||
}
|
||
} |