import 'package:flutter/material.dart'; import 'package:uuid/uuid.dart'; import '../models/supplier_model.dart'; import '../services/supplier_repository.dart'; import '../widgets/keyboard_inset_wrapper.dart'; import '../widgets/modal_utils.dart'; import '../widgets/screen_id_title.dart'; class SupplierPickerModal extends StatefulWidget { const SupplierPickerModal({super.key, required this.onSupplierSelected}); final ValueChanged onSupplierSelected; @override State createState() => _SupplierPickerModalState(); } class _SupplierPickerModalState extends State { final SupplierRepository _repository = SupplierRepository(); final TextEditingController _searchController = TextEditingController(); final Uuid _uuid = const Uuid(); List _suppliers = const []; bool _isLoading = true; @override void initState() { super.initState(); _loadSuppliers(); } Future _loadSuppliers([String keyword = '']) async { setState(() => _isLoading = true); final all = await _repository.fetchSuppliers(includeHidden: true); final filtered = keyword.trim().isEmpty ? all : all.where((s) => s.name.toLowerCase().contains(keyword.toLowerCase())).toList(); if (!mounted) return; setState(() { _suppliers = filtered; _isLoading = false; }); } Future _openEditor({Supplier? supplier}) async { final result = await showFeatureModalBottomSheet( context: context, builder: (ctx) => _SupplierFormSheet( supplier: supplier, onSubmit: (data) => Navigator.of(ctx).pop(data), ), ); if (result == null) return; final saving = result.copyWith(id: result.id.isEmpty ? _uuid.v4() : result.id, updatedAt: DateTime.now()); await _repository.saveSupplier(saving); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('仕入先を保存しました'))); await _loadSuppliers(_searchController.text); if (!mounted) return; widget.onSupplierSelected(saving); } Future _deleteSupplier(Supplier supplier) async { final confirmed = await showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('仕入先を削除'), content: Text('${supplier.name} を削除しますか?'), actions: [ TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('キャンセル')), TextButton(onPressed: () => Navigator.pop(ctx, true), child: const Text('削除')), ], ), ); if (confirmed != true) return; await _repository.deleteSupplier(supplier.id); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('仕入先を削除しました'))); await _loadSuppliers(_searchController.text); } @override Widget build(BuildContext context) { final theme = Theme.of(context); return SafeArea( child: ClipRRect( borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), child: Scaffold( backgroundColor: Colors.white, appBar: AppBar( automaticallyImplyLeading: false, leading: IconButton( icon: const Icon(Icons.close), onPressed: () => Navigator.pop(context), ), title: const ScreenAppBarTitle(screenId: 'P5', title: '仕入先選択'), actions: [ IconButton( tooltip: '仕入先を追加', onPressed: _openEditor, icon: const Icon(Icons.add_circle_outline), ), ], ), body: KeyboardInsetWrapper( safeAreaTop: false, basePadding: const EdgeInsets.fromLTRB(16, 12, 16, 16), extraBottom: 24, child: Column( children: [ TextField( controller: _searchController, decoration: InputDecoration( hintText: '仕入先名で検索', prefixIcon: const Icon(Icons.search), suffixIcon: _searchController.text.isEmpty ? null : IconButton( icon: const Icon(Icons.clear), onPressed: () { _searchController.clear(); _loadSuppliers(''); }, ), border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), isDense: true, ), onChanged: _loadSuppliers, ), const SizedBox(height: 12), Expanded( child: _isLoading ? const Center(child: CircularProgressIndicator()) : _suppliers.isEmpty ? Center( child: Text( '仕入先が見つかりません。右上の + から追加できます。', style: theme.textTheme.bodyMedium, textAlign: TextAlign.center, ), ) : ListView.builder( itemCount: _suppliers.length, itemBuilder: (context, index) { final supplier = _suppliers[index]; return Card( child: ListTile( title: Text(supplier.name, style: const TextStyle(fontWeight: FontWeight.bold)), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (supplier.contactPerson?.isNotEmpty == true) Text('担当: ${supplier.contactPerson}'), if (supplier.tel?.isNotEmpty == true) Text('TEL: ${supplier.tel}'), ], ), onTap: () { widget.onSupplierSelected(supplier); Navigator.pop(context); }, trailing: PopupMenuButton( onSelected: (value) { switch (value) { case 'edit': _openEditor(supplier: supplier); break; case 'delete': _deleteSupplier(supplier); break; } }, itemBuilder: (context) => const [ PopupMenuItem(value: 'edit', child: Text('編集')), PopupMenuItem(value: 'delete', child: Text('削除')), ], ), ), ); }, ), ), ], ), ), ), ), ); } } class _SupplierFormSheet extends StatefulWidget { const _SupplierFormSheet({required this.onSubmit, this.supplier}); final Supplier? supplier; final ValueChanged onSubmit; @override State<_SupplierFormSheet> createState() => _SupplierFormSheetState(); } class _SupplierFormSheetState extends State<_SupplierFormSheet> { late final TextEditingController _nameController; late final TextEditingController _contactController; late final TextEditingController _telController; late final TextEditingController _emailController; late final TextEditingController _notesController; final _formKey = GlobalKey(); bool _isSaving = false; @override void initState() { super.initState(); final supplier = widget.supplier; _nameController = TextEditingController(text: supplier?.name ?? ''); _contactController = TextEditingController(text: supplier?.contactPerson ?? ''); _telController = TextEditingController(text: supplier?.tel ?? ''); _emailController = TextEditingController(text: supplier?.email ?? ''); _notesController = TextEditingController(text: supplier?.notes ?? ''); } @override void dispose() { _nameController.dispose(); _contactController.dispose(); _telController.dispose(); _emailController.dispose(); _notesController.dispose(); super.dispose(); } void _handleSubmit() { if (_isSaving) return; if (!_formKey.currentState!.validate()) return; setState(() => _isSaving = true); widget.onSubmit( Supplier( id: widget.supplier?.id ?? '', name: _nameController.text.trim(), contactPerson: _contactController.text.trim().isEmpty ? null : _contactController.text.trim(), tel: _telController.text.trim().isEmpty ? null : _telController.text.trim(), email: _emailController.text.trim().isEmpty ? null : _emailController.text.trim(), notes: _notesController.text.trim().isEmpty ? null : _notesController.text.trim(), updatedAt: DateTime.now(), ), ); } @override Widget build(BuildContext context) { final title = widget.supplier == null ? '仕入先を追加' : '仕入先を編集'; return SafeArea( child: ClipRRect( borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), child: Material( color: Colors.white, child: Column( children: [ Padding( padding: const EdgeInsets.fromLTRB(8, 8, 8, 0), child: Row( children: [ IconButton(onPressed: () => Navigator.pop(context), icon: const Icon(Icons.close)), const SizedBox(width: 4), Text(title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), const Spacer(), TextButton( onPressed: _isSaving ? null : _handleSubmit, child: _isSaving ? const SizedBox(width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2)) : const Text('保存'), ), ], ), ), Expanded( child: KeyboardInsetWrapper( safeAreaTop: false, basePadding: const EdgeInsets.fromLTRB(16, 8, 16, 16), extraBottom: 24, child: Form( key: _formKey, child: ListView( children: [ TextFormField( controller: _nameController, decoration: const InputDecoration(labelText: '仕入先名 *'), validator: (value) => value == null || value.trim().isEmpty ? '必須項目です' : null, ), const SizedBox(height: 12), TextFormField(controller: _contactController, decoration: const InputDecoration(labelText: '担当者')), const SizedBox(height: 12), TextFormField(controller: _telController, decoration: const InputDecoration(labelText: '電話番号')), const SizedBox(height: 12), TextFormField(controller: _emailController, decoration: const InputDecoration(labelText: 'メール')), const SizedBox(height: 12), TextFormField(controller: _notesController, decoration: const InputDecoration(labelText: '備考'), maxLines: 3), ], ), ), ), ), ], ), ), ), ); } }