// Version: 3.5 - リッチマスター編集ダイアログ(改良版) // ※ 汎用性の高いリッチなマスター編集ダイアログ(全てのマスタで共通使用) import 'package:flutter/material.dart'; import '../models/product.dart'; /// 汎用性の高いリッチなマスター編集ダイアログ class MasterEditDialog extends StatefulWidget { final String title; final T? initialData; final bool showStatusFields; final Function(T)? onSave; const MasterEditDialog({ super.key, required this.title, this.initialData, this.showStatusFields = false, this.onSave, }); @override State createState() => _MasterEditDialogState(); } class _MasterEditDialogState extends State { late TextEditingController codeController; late TextEditingController nameController; late TextEditingController addressController; late TextEditingController phoneController; late TextEditingController emailController; @override void initState() { super.initState(); final data = widget.initialData; // デフォルトのサンプル値 codeController = TextEditingController(text: data?.productCode ?? ''); nameController = TextEditingController(text: data?.name ?? ''); addressController = TextEditingController(text: data?.address ?? ''); phoneController = TextEditingController(text: data?.phone ?? ''); emailController = TextEditingController(text: data?.email ?? ''); } @override void dispose() { codeController.dispose(); nameController.dispose(); addressController.dispose(); phoneController.dispose(); emailController.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.edit, 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( 'コード *', codeController, keyboard: TextInputType.text, icon: Icons.code, hint: e.g., 'P001', ), // 名称フィールド _buildRichTextField( '商品名 / 会社名 *', nameController, keyboard: TextInputType.name, icon: Icons.business, hint: e.g., 'サンプル製品 A', ), // アドレスフィールド(オプション) _buildRichTextField( '住所', addressController, keyboard: TextInputType.text, icon: Icons.location_on, hint: '省略可', ), if (widget.showStatusFields) ...[ const SizedBox(height: 8), const Divider(), const SizedBox(height: 4), // ステータス情報セクション Text( '■ ステータス情報', style: TextStyle( fontSize: 13, fontWeight: FontWeight.bold, color: Theme.of(context).primaryColor, ), ), const SizedBox(height: 8), _buildRichTextField( '電話番号', phoneController, keyboard: TextInputType.phone, icon: Icons.phone, hint: '03-1234-5678', ), _buildRichTextField( 'E メール', emailController, keyboard: TextInputType.emailAddress, icon: Icons.email, hint: 'example@example.com', ), ], ], ), ), 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: () { // TODO: onSave コールバックを実装 if (widget.onSave != null) { widget.onSave!(widget.initialData as T); } else { Navigator.pop(context); } }, style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 14), ), child: Text(' 保存 ', textAlign: TextAlign.center, style: TextStyle(fontSize: 15)), ), ), ], ), ], ), ), ), ); } } /// 参照マスタ選択ダイアログ(リッチ版) class SingleChoiceDialog extends StatelessWidget { final List items; final Function() onCancel; final Function(Product) onSelected; const SingleChoiceDialog({super.key, required this.items, required this.onCancel, required this.onSelected}); @override Widget build(BuildContext context) { if (items.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: items.length, separatorBuilder: (_, __) => const Divider(height: 1), itemBuilder: (ctx, index) { final item = items[index]; return ListTile( contentPadding: EdgeInsets.zero, leading: CircleAvatar( backgroundColor: Theme.of(context).primaryColor.withOpacity(0.1), child: Icon(Icons.inventory_2, color: Theme.of(context).primaryColor), ), title: Text( item.name ?? '未入力', style: const TextStyle(fontWeight: FontWeight.w500), ), subtitle: Text(item.productCode ?? '', style: TextStyle(fontSize: 12)), onTap: () => onSelected(item), ); }, ), ), ); } }