import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import '../models/invoice_models.dart'; import '../services/invoice_repository.dart'; import '../services/customer_repository.dart'; import 'invoice_detail_page.dart'; import 'management_screen.dart'; import 'product_master_screen.dart'; import 'customer_master_screen.dart'; import 'invoice_input_screen.dart'; import 'settings_screen.dart'; import 'company_info_screen.dart'; import '../widgets/slide_to_unlock.dart'; import '../main.dart'; // InvoiceFlowScreen 用 import 'package:package_info_plus/package_info_plus.dart'; import '../widgets/invoice_pdf_preview_page.dart'; import 'invoice_history/invoice_history_list.dart'; class InvoiceHistoryScreen extends StatefulWidget { const InvoiceHistoryScreen({super.key}); @override State createState() => _InvoiceHistoryScreenState(); } class _InvoiceHistoryScreenState extends State { final InvoiceRepository _invoiceRepo = InvoiceRepository(); final CustomerRepository _customerRepo = CustomerRepository(); List _invoices = []; List _filteredInvoices = []; bool _isLoading = true; bool _isUnlocked = false; // 保護解除フラグ String _searchQuery = ""; String _sortBy = "date"; // "date", "amount", "customer" DateTime? _startDate; DateTime? _endDate; String _appVersion = "1.0.0"; @override void initState() { super.initState(); _loadData(); _loadVersion(); } Future _showInvoiceActions(Invoice invoice) async { if (!_requireUnlock()) return; if (invoice.isLocked) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("ロック中の伝票は操作できません"))); return; } await showModalBottomSheet( context: context, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), builder: (context) => SafeArea( child: Column( mainAxisSize: MainAxisSize.min, children: [ ListTile( leading: const Icon(Icons.picture_as_pdf), title: const Text("PDFプレビュー"), onTap: () async { Navigator.pop(context); await Navigator.push( context, MaterialPageRoute( builder: (context) => InvoicePdfPreviewPage( invoice: invoice, isUnlocked: _isUnlocked, isLocked: invoice.isLocked, allowFormalIssue: !invoice.isLocked, onFormalIssue: () async { final repo = InvoiceRepository(); final promoted = invoice.copyWith(isDraft: false); await repo.updateInvoice(promoted); _loadData(); return true; }, showShare: true, showEmail: true, showPrint: true, ), ), ); _loadData(); }, ), ListTile( leading: const Icon(Icons.edit), title: const Text("編集"), onTap: _isUnlocked ? () async { await Navigator.push( context, MaterialPageRoute( builder: (context) => InvoiceDetailPage( invoice: invoice, ), ), ); _loadData(); } : null, ), ListTile( leading: const Icon(Icons.delete, color: Colors.redAccent), title: const Text("削除", style: TextStyle(color: Colors.redAccent)), onTap: _isUnlocked ? () async { Navigator.pop(context); final confirm = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text("伝票の削除"), content: Text("「${invoice.customerNameForDisplay}」の伝票(${invoice.invoiceNumber})を削除しますか?\nこの操作は取り消せません。"), 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 (confirm == true) { await _invoiceRepo.deleteInvoice(invoice.id); _loadData(); } } : null, ), ], ), ), ); } bool _requireUnlock() { if (_isUnlocked) return true; ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("スライドでロック解除してください"))); return false; } Future _loadVersion() async { final packageInfo = await PackageInfo.fromPlatform(); setState(() { _appVersion = packageInfo.version; }); } Future _loadData() async { setState(() => _isLoading = true); final customers = await _customerRepo.getAllCustomers(); final invoices = await _invoiceRepo.getAllInvoices(customers); setState(() { _invoices = invoices; _applyFilterAndSort(); _isLoading = false; }); } void _applyFilterAndSort() { setState(() { _filteredInvoices = _invoices.where((inv) { final query = _searchQuery.toLowerCase(); final matchesQuery = inv.customerNameForDisplay.toLowerCase().contains(query) || inv.invoiceNumber.toLowerCase().contains(query) || (inv.notes?.toLowerCase().contains(query) ?? false); bool matchesDate = true; if (_startDate != null && inv.date.isBefore(_startDate!)) matchesDate = false; if (_endDate != null && inv.date.isAfter(_endDate!.add(const Duration(days: 1)))) matchesDate = false; return matchesQuery && matchesDate; }).toList(); if (_sortBy == "date") { _filteredInvoices.sort((a, b) => b.date.compareTo(a.date)); } else if (_sortBy == "amount") { _filteredInvoices.sort((a, b) => b.totalAmount.compareTo(a.totalAmount)); } else if (_sortBy == "customer") { _filteredInvoices.sort((a, b) => a.customerNameForDisplay.compareTo(b.customerNameForDisplay)); } }); } void _toggleUnlock() { setState(() { _isUnlocked = !_isUnlocked; }); if (!_isUnlocked) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text("編集プロテクトを有効にしました")), ); } } @override Widget build(BuildContext context) { final amountFormatter = NumberFormat("#,###"); final dateFormatter = DateFormat('yyyy/MM/dd'); return Scaffold( resizeToAvoidBottomInset: false, drawer: _isUnlocked ? Drawer( child: ListView( padding: EdgeInsets.zero, children: [ DrawerHeader( decoration: BoxDecoration(color: Colors.indigo.shade700), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.end, children: [ const Text("メニュー", style: TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold)), const SizedBox(height: 8), Text("v$_appVersion", style: const TextStyle(color: Colors.white70)), ], ), ), ListTile( leading: const Icon(Icons.receipt_long), title: const Text("伝票マスター"), onTap: () { Navigator.pop(context); }, ), ListTile( leading: const Icon(Icons.people), title: const Text("顧客マスター"), onTap: () { Navigator.pop(context); Navigator.push(context, MaterialPageRoute(builder: (_) => const CustomerMasterScreen())); }, ), ListTile( leading: const Icon(Icons.inventory_2), title: const Text("商品マスター"), onTap: () { Navigator.pop(context); Navigator.push(context, MaterialPageRoute(builder: (_) => const ProductMasterScreen())); }, ), ListTile( leading: const Icon(Icons.settings), title: const Text("設定"), onTap: () { Navigator.pop(context); Navigator.push(context, MaterialPageRoute(builder: (_) => const SettingsScreen())); }, ), const Divider(), ListTile( leading: const Icon(Icons.admin_panel_settings), title: const Text("管理メニュー"), onTap: () { Navigator.pop(context); Navigator.push(context, MaterialPageRoute(builder: (_) => const ManagementScreen())); }, ), ], ), ) : null, appBar: AppBar( // leading removed title: GestureDetector( onLongPress: () { Navigator.push( context, MaterialPageRoute(builder: (context) => const CompanyInfoScreen()), ).then((_) => _loadData()); }, child: Text("A2:履歴リスト v$_appVersion"), ), backgroundColor: _isUnlocked ? Colors.blueGrey : Colors.blueGrey.shade800, actions: [ if (_isUnlocked) IconButton( icon: const Icon(Icons.lock_open, color: Colors.orangeAccent), onPressed: _toggleUnlock, tooltip: "再度プロテクトする", ), IconButton( icon: const Icon(Icons.sort), onPressed: () { showMenu( context: context, position: const RelativeRect.fromLTRB(100, 80, 0, 0), items: [ const PopupMenuItem(value: "date", child: Text("日付順")), const PopupMenuItem(value: "amount", child: Text("金額順")), const PopupMenuItem(value: "customer", child: Text("顧客名順")), ], ).then((val) { if (val != null) { setState(() => _sortBy = val); _applyFilterAndSort(); } }); }, tooltip: "ソート切り替え", ), IconButton( icon: const Icon(Icons.refresh), onPressed: _loadData, ), ], bottom: PreferredSize( preferredSize: const Size.fromHeight(60), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), child: TextField( decoration: InputDecoration( hintText: "検索 (顧客名、伝票番号...)", prefixIcon: const Icon(Icons.search), filled: true, fillColor: Colors.white, border: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none), isDense: true, ), onChanged: (val) { _searchQuery = val; _applyFilterAndSort(); }, ), ), ), ), body: SafeArea( child: Column( children: [ Padding( padding: const EdgeInsets.all(16.0), child: SlideToUnlock( isLocked: !_isUnlocked, onUnlocked: _toggleUnlock, text: "スライドでロック解除", ), ), Expanded( child: _isLoading ? const Center(child: CircularProgressIndicator()) : InvoiceHistoryList( invoices: _filteredInvoices, isUnlocked: _isUnlocked, amountFormatter: amountFormatter, dateFormatter: dateFormatter, onTap: (invoice) async { await Navigator.push( context, MaterialPageRoute( builder: (context) => InvoiceDetailPage( invoice: invoice, ), ), ); _loadData(); }, onLongPress: (invoice) => _isUnlocked ? _showInvoiceActions(invoice) : _requireUnlock(), onEdit: (invoice) async { if (invoice.isLocked || !_isUnlocked) return; await Navigator.push( context, MaterialPageRoute( builder: (context) => InvoiceInputForm( existingInvoice: invoice, onInvoiceGenerated: (inv, path) {}, ), ), ); _loadData(); }, ), ), ], ), ), floatingActionButton: FloatingActionButton.extended( onPressed: _isUnlocked ? () async { await Navigator.push( context, MaterialPageRoute( builder: (context) => InvoiceFlowScreen(onComplete: _loadData), ), ); _loadData(); } : _requireUnlock, label: const Text("新規伝票作成"), icon: const Icon(Icons.add), backgroundColor: Colors.indigo, foregroundColor: Colors.white, ), ); } }