import 'package:flutter/material.dart'; import 'package:uuid/uuid.dart'; import '../models/supplier_model.dart'; import '../services/supplier_repository.dart'; class SupplierMasterScreen extends StatefulWidget { const SupplierMasterScreen({super.key}); @override State createState() => _SupplierMasterScreenState(); } class _SupplierMasterScreenState extends State { final SupplierRepository _repository = SupplierRepository(); final Uuid _uuid = const Uuid(); bool _isLoading = true; bool _showHidden = false; List _suppliers = const []; @override void initState() { super.initState(); _loadSuppliers(); } Future _loadSuppliers() async { setState(() => _isLoading = true); final suppliers = await _repository.fetchSuppliers(includeHidden: _showHidden); if (!mounted) return; setState(() { _suppliers = suppliers; _isLoading = false; }); } Future _openForm({Supplier? supplier}) async { final result = await showDialog( context: context, barrierDismissible: false, builder: (ctx) => _SupplierFormDialog( 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('仕入先を保存しました'))); _loadSuppliers(); } 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('削除', style: TextStyle(color: Colors.red))), ], ), ); if (confirmed != true) return; await _repository.deleteSupplier(supplier.id); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('仕入先を削除しました'))); _loadSuppliers(); } String _closingLabel(Supplier supplier) { if (supplier.closingDay == null) return '締日未設定'; return '毎月${supplier.closingDay}日締め / ${supplier.paymentSiteDays}日サイト'; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: const BackButton(), title: const Text('M5:仕入先マスター'), actions: [ Switch.adaptive( value: _showHidden, onChanged: (value) { setState(() => _showHidden = value); _loadSuppliers(); }, ), ], ), floatingActionButton: FloatingActionButton( onPressed: () => _openForm(), child: const Icon(Icons.add), ), body: _isLoading ? const Center(child: CircularProgressIndicator()) : _suppliers.isEmpty ? const _EmptyState(message: '仕入先が登録されていません') : RefreshIndicator( onRefresh: _loadSuppliers, child: ListView.builder( physics: const AlwaysScrollableScrollPhysics(), itemCount: _suppliers.length, itemBuilder: (context, index) { final supplier = _suppliers[index]; return Card( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: ListTile( title: Text(supplier.name, style: const TextStyle(fontWeight: FontWeight.bold)), subtitle: Text([ if (supplier.contactPerson?.isNotEmpty == true) '担当: ${supplier.contactPerson}', if (supplier.tel?.isNotEmpty == true) 'TEL: ${supplier.tel}', _closingLabel(supplier), ].where((e) => e.isNotEmpty).join('\n')), trailing: PopupMenuButton( onSelected: (value) { switch (value) { case 'edit': _openForm(supplier: supplier); break; case 'delete': _deleteSupplier(supplier); break; } }, itemBuilder: (context) => const [ PopupMenuItem(value: 'edit', child: Text('編集')), PopupMenuItem(value: 'delete', child: Text('削除')), ], ), ), ); }, ), ), ); } } class _SupplierFormDialog extends StatefulWidget { const _SupplierFormDialog({required this.onSubmit, this.supplier}); final Supplier? supplier; final ValueChanged onSubmit; @override State<_SupplierFormDialog> createState() => _SupplierFormDialogState(); } class _SupplierFormDialogState extends State<_SupplierFormDialog> { late final TextEditingController _nameController; late final TextEditingController _contactController; late final TextEditingController _emailController; late final TextEditingController _telController; late final TextEditingController _addressController; late final TextEditingController _paymentSiteController; late final TextEditingController _notesController; int? _closingDay; bool _isHidden = false; @override void initState() { super.initState(); final supplier = widget.supplier; _nameController = TextEditingController(text: supplier?.name ?? ''); _contactController = TextEditingController(text: supplier?.contactPerson ?? ''); _emailController = TextEditingController(text: supplier?.email ?? ''); _telController = TextEditingController(text: supplier?.tel ?? ''); _addressController = TextEditingController(text: supplier?.address ?? ''); _paymentSiteController = TextEditingController(text: (supplier?.paymentSiteDays ?? 30).toString()); _notesController = TextEditingController(text: supplier?.notes ?? ''); _closingDay = supplier?.closingDay; _isHidden = supplier?.isHidden ?? false; } void _submit() { if (_nameController.text.trim().isEmpty) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('仕入先名は必須です'))); return; } final paymentSite = int.tryParse(_paymentSiteController.text) ?? 30; widget.onSubmit( Supplier( id: widget.supplier?.id ?? '', name: _nameController.text.trim(), contactPerson: _contactController.text.trim().isEmpty ? null : _contactController.text.trim(), email: _emailController.text.trim().isEmpty ? null : _emailController.text.trim(), tel: _telController.text.trim().isEmpty ? null : _telController.text.trim(), address: _addressController.text.trim().isEmpty ? null : _addressController.text.trim(), closingDay: _closingDay, paymentSiteDays: paymentSite, notes: _notesController.text.trim().isEmpty ? null : _notesController.text.trim(), isHidden: _isHidden, updatedAt: DateTime.now(), ), ); } @override Widget build(BuildContext context) { final closingOptions = [null, ...List.generate(31, (index) => index + 1)]; return AlertDialog( title: Text(widget.supplier == null ? '仕入先を追加' : '仕入先を編集'), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ TextField(controller: _nameController, decoration: const InputDecoration(labelText: '仕入先名 *')), TextField(controller: _contactController, decoration: const InputDecoration(labelText: '担当者')), TextField(controller: _emailController, decoration: const InputDecoration(labelText: 'メール'), keyboardType: TextInputType.emailAddress), TextField(controller: _telController, decoration: const InputDecoration(labelText: '電話番号'), keyboardType: TextInputType.phone), TextField(controller: _addressController, decoration: const InputDecoration(labelText: '住所')), DropdownButtonFormField( initialValue: _closingDay, items: closingOptions .map((day) => DropdownMenuItem( value: day, child: Text(day == null ? '締日未設定' : '$day日締め'), )) .toList(), onChanged: (val) => setState(() => _closingDay = val), decoration: const InputDecoration(labelText: '締日'), ), TextField( controller: _paymentSiteController, decoration: const InputDecoration(labelText: '支払サイト(日)'), keyboardType: TextInputType.number, ), TextField(controller: _notesController, decoration: const InputDecoration(labelText: '備考'), maxLines: 2), SwitchListTile( title: const Text('非表示にする'), value: _isHidden, onChanged: (val) => setState(() => _isHidden = val), ), ], ), ), actions: [ TextButton(onPressed: () => Navigator.pop(context), child: const Text('キャンセル')), FilledButton(onPressed: _submit, child: const Text('保存')), ], ); } } class _EmptyState extends StatelessWidget { const _EmptyState({required this.message}); final String message; @override Widget build(BuildContext context) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.inbox, size: 64, color: Colors.grey), const SizedBox(height: 16), Text(message), ], ), ); } }