h-1.flutter.0/lib/screens/product_picker_modal.dart

177 lines
8.5 KiB
Dart

import 'package:flutter/material.dart';
import '../models/invoice_models.dart';
import '../models/product_model.dart';
import '../services/product_repository.dart';
import '../widgets/keyboard_inset_wrapper.dart';
import '../widgets/screen_id_title.dart';
import 'product_master_screen.dart';
/// 商品マスターから項目を選択するためのモーダル(スタブ実装)
class ProductPickerModal extends StatefulWidget {
final Function(InvoiceItem) onItemSelected;
final ValueChanged<Product>? onProductSelected;
const ProductPickerModal({super.key, required this.onItemSelected, this.onProductSelected});
@override
State<ProductPickerModal> createState() => _ProductPickerModalState();
}
class _ProductPickerModalState extends State<ProductPickerModal> {
final ProductRepository _productRepo = ProductRepository();
final TextEditingController _searchController = TextEditingController();
List<Product> _products = [];
bool _isLoading = true;
@override
void initState() {
super.initState();
_onSearch(""); // 初期表示
}
Future<void> _onSearch(String val) async {
setState(() => _isLoading = true);
final products = await _productRepo.searchProducts(val);
setState(() {
_products = products;
_isLoading = false;
});
}
@override
Widget build(BuildContext 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: 'P6', title: '商品・サービス選択'),
),
body: KeyboardInsetWrapper(
safeAreaTop: false,
basePadding: const EdgeInsets.fromLTRB(16, 12, 16, 16),
extraBottom: 24,
child: Column(
children: [
TextField(
controller: _searchController,
autofocus: true,
decoration: InputDecoration(
hintText: "商品名・カテゴリ・バーコードで検索",
prefixIcon: const Icon(Icons.search),
suffixIcon: IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
_searchController.clear();
_onSearch("");
},
),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
contentPadding: const EdgeInsets.symmetric(vertical: 0),
),
onChanged: _onSearch,
),
const SizedBox(height: 12),
Expanded(
child: _isLoading
? const Center(child: CircularProgressIndicator())
: _products.isEmpty
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text("商品が見つかりません"),
TextButton(
onPressed: () async {
await Navigator.push(context, MaterialPageRoute(builder: (context) => const ProductMasterScreen()));
_onSearch(_searchController.text);
},
child: const Text("マスターに追加する"),
),
],
),
)
: ListView.builder(
itemCount: _products.length,
itemBuilder: (context, index) {
final product = _products[index];
return ListTile(
leading: const Icon(Icons.inventory_2_outlined),
title: Text(product.name),
subtitle: Text("${product.defaultUnitPrice} (在庫: ${product.stockQuantity})"),
onTap: () {
widget.onProductSelected?.call(product);
widget.onItemSelected(
InvoiceItem(
productId: product.id,
description: product.name,
quantity: 1,
unitPrice: product.defaultUnitPrice,
),
);
Navigator.pop(context);
},
onLongPress: () async {
await showModalBottomSheet(
context: context,
builder: (ctx) => SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: const Icon(Icons.edit),
title: const Text("編集"),
onTap: () async {
Navigator.pop(ctx);
await Navigator.push(
context,
MaterialPageRoute(builder: (_) => const ProductMasterScreen()),
);
_onSearch(_searchController.text);
},
),
ListTile(
leading: const Icon(Icons.delete_outline, color: Colors.redAccent),
title: const Text("削除", style: TextStyle(color: Colors.redAccent)),
onTap: () async {
Navigator.pop(ctx);
final confirmed = await showDialog<bool>(
context: context,
builder: (_) => AlertDialog(
title: const Text("削除の確認"),
content: Text("${product.name} を削除しますか?"),
actions: [
TextButton(onPressed: () => Navigator.pop(context, false), child: const Text("キャンセル")),
TextButton(onPressed: () => Navigator.pop(context, true), child: const Text("削除", style: TextStyle(color: Colors.red))),
],
),
);
if (confirmed == true) {
await _productRepo.deleteProduct(product.id);
if (mounted) _onSearch(_searchController.text);
}
},
),
],
),
),
);
},
);
},
),
),
],
),
),
),
),
);
}
}