import 'package:flutter/material.dart'; import 'package:uuid/uuid.dart'; import '../models/product_model.dart'; import '../services/product_repository.dart'; import 'barcode_scanner_screen.dart'; class ProductMasterScreen extends StatefulWidget { const ProductMasterScreen({Key? key}) : super(key: key); @override State createState() => _ProductMasterScreenState(); } class _ProductMasterScreenState extends State { final ProductRepository _productRepo = ProductRepository(); final TextEditingController _searchController = TextEditingController(); List _products = []; List _filteredProducts = []; bool _isLoading = true; String _searchQuery = ""; @override void initState() { super.initState(); _loadProducts(); } Future _loadProducts() async { setState(() => _isLoading = true); final products = await _productRepo.getAllProducts(); setState(() { _products = products; _isLoading = false; _applyFilter(); }); } void _applyFilter() { setState(() { _filteredProducts = _products.where((p) { final query = _searchQuery.toLowerCase(); return p.name.toLowerCase().contains(query) || (p.barcode?.toLowerCase().contains(query) ?? false) || (p.category?.toLowerCase().contains(query) ?? false); }).toList(); }); } Future _showEditDialog({Product? product}) async { final nameController = TextEditingController(text: product?.name ?? ""); final priceController = TextEditingController(text: (product?.defaultUnitPrice ?? 0).toString()); final barcodeController = TextEditingController(text: product?.barcode ?? ""); final categoryController = TextEditingController(text: product?.category ?? ""); final stockController = TextEditingController(text: (product?.stockQuantity ?? 0).toString()); final result = await showDialog( context: context, builder: (context) => StatefulBuilder( builder: (context, setDialogState) => AlertDialog( title: Text(product == null ? "商品追加" : "商品編集"), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ TextField(controller: nameController, decoration: const InputDecoration(labelText: "商品名")), TextField(controller: categoryController, decoration: const InputDecoration(labelText: "カテゴリ")), TextField(controller: priceController, decoration: const InputDecoration(labelText: "初期単価"), keyboardType: TextInputType.number), TextField(controller: stockController, decoration: const InputDecoration(labelText: "在庫数"), keyboardType: TextInputType.number), const SizedBox(height: 8), Row( children: [ Expanded( child: TextField(controller: barcodeController, decoration: const InputDecoration(labelText: "バーコード")), ), IconButton( icon: const Icon(Icons.qr_code_scanner), onPressed: () async { final code = await Navigator.push( context, MaterialPageRoute(builder: (context) => const BarcodeScannerScreen()), ); if (code != null) { setDialogState(() => barcodeController.text = code); } }, ), ], ), ], ), ), actions: [ TextButton(onPressed: () => Navigator.pop(context), child: const Text("キャンセル")), ElevatedButton( onPressed: () { if (nameController.text.isEmpty) return; Navigator.pop( context, Product( id: product?.id ?? const Uuid().v4(), name: nameController.text.trim(), defaultUnitPrice: int.tryParse(priceController.text) ?? 0, barcode: barcodeController.text.isEmpty ? null : barcodeController.text.trim(), category: categoryController.text.isEmpty ? null : categoryController.text.trim(), stockQuantity: int.tryParse(stockController.text) ?? 0, odooId: product?.odooId, ), ); }, child: const Text("保存"), ), ], ), ), ); if (result != null) { await _productRepo.saveProduct(result); _loadProducts(); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: const BackButton(), title: const Text("商品マスター"), backgroundColor: Colors.blueGrey, bottom: PreferredSize( preferredSize: const Size.fromHeight(60), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: TextField( controller: _searchController, decoration: InputDecoration( hintText: "商品名・バーコード・カテゴリで検索", prefixIcon: const Icon(Icons.search), filled: true, fillColor: Colors.white, border: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none), contentPadding: EdgeInsets.zero, ), onChanged: (val) { _searchQuery = val; _applyFilter(); }, ), ), ), ), body: _isLoading ? const Center(child: CircularProgressIndicator()) : _filteredProducts.isEmpty ? const Center(child: Text("商品が見つかりません")) : ListView.builder( itemCount: _filteredProducts.length, itemBuilder: (context, index) { final p = _filteredProducts[index]; return ListTile( leading: const CircleAvatar(child: Icon(Icons.inventory_2)), title: Text(p.name), subtitle: Text("${p.category ?? '未分類'} - ¥${p.defaultUnitPrice} (在庫: ${p.stockQuantity})"), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ IconButton(icon: const Icon(Icons.edit), onPressed: () => _showEditDialog(product: p)), IconButton( icon: const Icon(Icons.delete_outline, color: Colors.redAccent), onPressed: () { showDialog( context: context, builder: (context) => AlertDialog( title: const Text("削除の確認"), content: Text("${p.name}を削除してよろしいですか?"), actions: [ TextButton(onPressed: () => Navigator.pop(context), child: const Text("キャンセル")), TextButton( onPressed: () async { await _productRepo.deleteProduct(p.id); Navigator.pop(context); _loadProducts(); }, child: const Text("削除", style: TextStyle(color: Colors.red)), ), ], ), ); }, ), ], ), ); }, ), floatingActionButton: FloatingActionButton( onPressed: () => _showEditDialog(), child: const Icon(Icons.add), backgroundColor: Colors.blueGrey.shade800, foregroundColor: Colors.white, ), ); } }