// Version: 2.8 - 簡易仕入先マスタ(電話帳対応) import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import '../../models/product.dart'; import '../../services/database_helper.dart'; import '../../widgets/master_edit_fields.dart'; /// 簡易仕入先マスタ管理画面 + 電話帳連携対応 class RichSupplierMasterScreen extends StatefulWidget { const RichSupplierMasterScreen({super.key}); @override State createState() => _RichSupplierMasterScreenState(); } class _RichSupplierMasterScreenState extends State { final DatabaseHelper _db = DatabaseHelper.instance; List _suppliers = []; bool _isLoading = true; @override void initState() { super.initState(); _loadSuppliers(); } Future _loadSuppliers() async { try { final products = await _db.getProducts(); if (mounted) setState(() { _suppliers = products ?? const []; _isLoading = false; }); } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('仕入先データを読み込みませんでした:$e'), backgroundColor: Colors.red), ); } } Future _addSupplier(Product supplier) async { try { await _db.insertProduct(supplier); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('仕入先を登録しました'), backgroundColor: Colors.green), ); await _loadSuppliers(); } } catch (e) { if (mounted) ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('登録に失敗:$e'), backgroundColor: Colors.red), ); } } Future _editSupplier(Product supplier) async { if (!mounted) return; try { final updatedSupplier = await _showEditDialog(context, supplier); if (updatedSupplier != null && mounted) { await _db.updateProduct(updatedSupplier); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('仕入先を更新しました'), backgroundColor: Colors.green), ); await _loadSuppliers(); } } catch (e) { if (mounted) ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('更新に失敗:$e'), backgroundColor: Colors.red), ); } } Future _deleteSupplier(int id) async { final confirmed = await showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('仕入先削除'), content: Text('この仕入先を削除しますか?'), actions: [ TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('キャンセル')), ElevatedButton( onPressed: () => Navigator.pop(ctx, true), style: ElevatedButton.styleFrom(backgroundColor: Colors.red), child: const Text('削除'), ), ], ), ); if (confirmed == true) { try { // TODO: データベースから削除ロジックを実装(現在は簡易実装) if (mounted) _loadSuppliers(); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('仕入先を削除しました'), backgroundColor: Colors.green), ); } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('削除に失敗:$e'), backgroundColor: Colors.red), ); } } } Future _showEditDialog(BuildContext context, Product supplier) async { return showDialog( context: context, builder: (ctx) => Dialog( child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Padding( padding: const EdgeInsets.all(16), child: Text( '仕入先情報', style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), ), ), MasterTextField( label: '製品コード *', controller: TextEditingController(text: supplier.productCode ?? ''), hintText: '例:S-001, SAN-002 など(半角英数字)', ), const SizedBox(height: 16), MasterTextField( label: '会社名 *', controller: TextEditingController(text: supplier.name ?? ''), hintText: '例:株式会社〇〇商事、個人商社で可', ), const SizedBox(height: 16), MasterTextField( label: '担当者名', controller: TextEditingController(text: supplier.supplierContactName.isNotEmpty ? supplier.supplierContactName : ''), hintText: '例:田中太郎(日本語漢字可)', ), const SizedBox(height: 16), // 電話番号フィールド - 電話帳連携対応 MasterTextField( label: '電話番号', controller: TextEditingController(text: supplier.supplierPhoneNumber.isNotEmpty ? supplier.supplierPhoneNumber : ''), hintText: '例:03-1234-5678、区切り不要(0312345678)', keyboardType: TextInputType.phone, phoneField: 'supplierPhoneNumber', // 電話帳連携用 ), const SizedBox(height: 16), MasterTextField( label: 'メールアドレス', controller: TextEditingController(text: supplier.email.isNotEmpty ? supplier.email : ''), hintText: '@example.com の形式(例:order@ooshouki.co.jp)', keyboardType: TextInputType.emailAddress, ), const SizedBox(height: 16), MasterTextField( label: '住所', controller: TextEditingController(text: supplier.address.isNotEmpty ? supplier.address : ''), hintText: '〒000-0000 市区町村名・番地・建物名', ), const SizedBox(height: 16), // 評価ポイント(1-5) MasterNumberField( label: '評価ポイント *', controller: TextEditingController(text: supplier.quantity.toString()), hintText: '1-5 の範囲(例:5 は最高レベル)', ), const SizedBox(height: 16), MasterDateField( label: '登録日', controller: TextEditingController(text: DateFormat('yyyy/MM/dd').format(supplier.createdAt ?? DateTime.now())), ), const SizedBox(height: 24), Padding( padding: const EdgeInsets.all(16), child: Text( '保存', style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold), ), ), SizedBox(height: 24, width: double.infinity, child: ElevatedButton( onPressed: () => Navigator.pop(ctx, supplier), style: ElevatedButton.styleFrom(backgroundColor: Theme.of(context).primaryColor, padding: const EdgeInsets.symmetric(vertical: 16)), child: const Text('保存', style: TextStyle(fontSize: 16)), )), SizedBox(height: 8, width: double.infinity, child: OutlinedButton( onPressed: () => Navigator.pop(ctx), style: OutlinedButton.styleFrom(padding: const EdgeInsets.symmetric(vertical: 16)), child: const Text('キャンセル'), )), ], ), ), ), ); } Future _showSupplierDetail(BuildContext context, Product supplier) async { showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('仕入先詳細'), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ if (supplier.productCode.isNotEmpty) _detailRow('製品コード', supplier.productCode), if (supplier.name.isNotEmpty) _detailRow('会社名', supplier.name), if (supplier.supplierContactName.isNotEmpty) _detailRow('担当者名', supplier.supplierContactName), _detailRow('電話番号', supplier.supplierPhoneNumber.isNotEmpty ? supplier.supplierPhoneNumber : '-'), if (supplier.email.isNotEmpty) _detailRow('Email', supplier.email), if (supplier.address.isNotEmpty) _detailRow('住所', supplier.address), _detailRow('評価ポイント', '★'.repeat(supplier.quantity.toInt() > 0 ? supplier.quantity.toInt() : 1)), _detailRow('登録日', DateFormat('yyyy/MM/dd').format(supplier.createdAt)), ], ), ), actions: [TextButton(onPressed: () => Navigator.pop(ctx), child: const Text('閉じる'))], ), ); } Widget _detailRow(String label, String value) { return Padding( padding: const EdgeInsets.only(bottom: 12), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(width: 100), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: TextStyle(fontWeight: FontWeight.bold)), if (value != '-') Text(value), ], ), ), ], ), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('/M3. 仕入先マスタ'), actions: [IconButton(icon: const Icon(Icons.refresh), onPressed: _loadSuppliers)], ), body: _isLoading ? const Center(child: CircularProgressIndicator()) : _suppliers.isEmpty ? Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.inbox_outlined, size: 64, color: Colors.grey[300]), SizedBox(height: 16), Text('仕入先データがありません', style: TextStyle(color: Colors.grey)), SizedBox(height: 16), FloatingActionButton.extended( icon: Icon(Icons.add, color: Theme.of(context).primaryColor), label: const Text('新規登録'), onPressed: () => _showAddDialog(context), ), ], ), ) : ListView.builder( padding: const EdgeInsets.all(8), itemCount: _suppliers.length, itemBuilder: (context, index) { final supplier = _suppliers[index]; return Card( margin: const EdgeInsets.only(bottom: 8), elevation: 4, child: ListTile( leading: CircleAvatar(backgroundColor: Colors.orange.shade100, child: Icon(Icons.business, color: Colors.orange)), title: Text(supplier.name ?? '未入力'), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (supplier.productCode.isNotEmpty) Text('製品コード:${supplier.productCode}', style: const TextStyle(fontSize: 12)), if (supplier.supplierPhoneNumber.isNotEmpty) Text('電話:${supplier.supplierPhoneNumber}', style: const TextStyle(fontSize: 12)), if (supplier.email.isNotEmpty) Text('Email: ${supplier.email}', style: const TextStyle(fontSize: 12)), Text('評価:★'.repeat(supplier.quantity.toInt() > 0 ? supplier.quantity.toInt() : 1), style: const TextStyle(color: Colors.orange, fontSize: 12)), ], ), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ IconButton(icon: Icon(Icons.edit, color: Colors.blue), onPressed: () => _editSupplier(supplier)), PopupMenuButton( onSelected: (value) => value == 'delete' ? _deleteSupplier(supplier.id ?? 0) : null, itemBuilder: (ctx) => [ const PopupMenuItem(child: Text('詳細'), onPressed: () => _showSupplierDetail(context, supplier)), const PopupMenuItem(child: Text('削除'), onPressed: () => _deleteSupplier(supplier.id ?? 0)), ], ), ], ), ), ); }, ), floatingActionButton: FloatingActionButton.extended( icon: const Icon(Icons.add), label: const Text('新規登録'), onPressed: () => _showAddDialog(context), ), ); } void _showAddDialog(BuildContext context) { showDialog( context: context, builder: (ctx) => Dialog( child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Padding( padding: const EdgeInsets.all(16), child: Text( '新規仕入先登録', style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), ), ), MasterTextField( label: '製品コード *', controller: TextEditingController(), hintText: '例:S-001, SAN-002 など(半角英数字)', ), const SizedBox(height: 16), MasterTextField( label: '会社名 *', controller: TextEditingController(), hintText: '例:株式会社〇〇商事、個人商社で可', ), const SizedBox(height: 16), MasterTextField( label: '担当者名', controller: TextEditingController(), hintText: '例:田中太郎(日本語漢字可)', ), const SizedBox(height: 16), // 電話番号フィールド - 電話帳連携対応 MasterTextField( label: '電話番号', controller: TextEditingController(), keyboardType: TextInputType.phone, hintText: '例:03-1234-5678、区切り不要(0312345678)', phoneField: 'supplierPhoneNumber', // 電話帳連携用 ), const SizedBox(height: 16), MasterTextField( label: 'メールアドレス', controller: TextEditingController(), keyboardType: TextInputType.emailAddress, hintText: '@example.com の形式(例:order@ooshouki.co.jp)', ), const SizedBox(height: 16), MasterTextField( label: '住所', controller: TextEditingController(), hintText: '〒000-0000 市区町村名・番地・建物名', ), const SizedBox(height: 16), MasterNumberField( label: '評価ポイント *', controller: TextEditingController(), hintText: '1-5 の範囲(例:3)', ), const SizedBox(height: 16), MasterDateField( label: '登録日', controller: TextEditingController(text: DateFormat('yyyy/MM/dd').format(DateTime.now())), ), ], ), ), ), ); } } /// 仕入先マスタ用詳細表示ダイアログ(電話帳対応) class SupplierDetailDialog extends StatelessWidget { final Product supplier; const SupplierDetailDialog({super.key, required this.supplier}); @override Widget build(BuildContext context) { return AlertDialog( title: const Text('仕入先詳細'), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ _detailRow('製品コード', supplier.productCode ?? '-'), _detailRow('会社名', supplier.name ?? '-'), if (supplier.supplierContactName.isNotEmpty) _detailRow('担当者名', supplier.supplierContactName), _detailRow('電話番号', supplier.supplierPhoneNumber.isNotEmpty ? supplier.supplierPhoneNumber : '-'), if (supplier.email.isNotEmpty) _detailRow('Email', supplier.email), if (supplier.address.isNotEmpty) _detailRow('住所', supplier.address), _detailRow('評価ポイント', '★'.repeat(supplier.quantity.toInt() > 0 ? supplier.quantity.toInt() : 1)), _detailRow('登録日', DateFormat('yyyy/MM/dd').format(supplier.createdAt)), ], ), ), actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('閉じる'))], ); } Widget _detailRow(String label, String value) { return Padding( padding: const EdgeInsets.only(bottom: 12), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(width: 100), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: TextStyle(fontWeight: FontWeight.bold)), if (value != '-') Text(value), ], ), ), ], ), ); } }