import 'package:flutter/material.dart'; import 'package:cashumit/models/local_receipt.dart'; import 'package:cashumit/models/group_submission_result.dart'; import 'package:cashumit/services/local_receipt_service.dart'; import 'package:cashumit/services/receipt_service.dart'; import 'package:cashumit/widgets/transaction_submission_dialog.dart'; import 'package:cashumit/widgets/transaction_links_dialog.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; import 'package:cashumit/providers/receipt_provider.dart'; class LocalReceiptsScreen extends StatefulWidget { const LocalReceiptsScreen({super.key}); @override State createState() => _LocalReceiptsScreenState(); } class _LocalReceiptsScreenState extends State { List receipts = []; bool isLoading = true; @override void initState() { super.initState(); _loadReceipts(); } Future _loadReceipts() async { setState(() { isLoading = true; }); try { final loadedReceipts = await LocalReceiptService.getReceipts(); setState(() { receipts = loadedReceipts; isLoading = false; }); } catch (e) { setState(() { isLoading = false; }); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Gagal memuat daftar nota: $e')), ); } } } Future _submitAllReceipts() async { final credentials = await ReceiptService.loadCredentials(); if (credentials == null) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( 'Silakan konfigurasi kredensial FireFly III terlebih dahulu'), ), ); } return; } final unsubmitted = receipts.where((r) => !r.isSubmitted).toList(); if (unsubmitted.isEmpty) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Tidak ada nota yang belum dikirim')), ); } return; } // Bangun tasks dari semua nota yang belum tersubmit final tasks = ReceiptService.buildTasksFromAllUnsubmittedReceipts(unsubmitted); if (tasks.isEmpty) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Tidak ada task valid untuk dikirim')), ); } return; } final baseUrl = credentials['url']!; final accessToken = credentials['token']!; // Tampilkan popup pengiriman final results = await showDialog>( context: context, barrierDismissible: false, builder: (context) => TransactionSubmissionDialog( tasks: tasks, baseUrl: baseUrl, accessToken: accessToken, ), ); if (results == null || !mounted) return; // Post-process: kelompokkan hasil per receipt, update status await _processBatchResults(results, unsubmitted, baseUrl); await _loadReceipts(); } /// Proses hasil batch: update tiap receipt berdasarkan task results. Future _processBatchResults( List results, List unsubmitted, String baseUrl, ) async { int totalSuccess = 0; int totalFailure = 0; for (final receipt in unsubmitted) { // Ambil semua hasil untuk receipt ini final prefix = '${receipt.id}_'; final receiptResults = results.where((r) => r.groupId.startsWith(prefix)).toList(); if (receiptResults.isEmpty) continue; final successes = receiptResults.where((r) => r.isSuccess).toList(); final failures = receiptResults.where((r) => r.isFailed).toList(); // Kumpulkan transaction IDs yang berhasil final txIds = successes .where((r) => r.transactionId != null) .map((r) => r.transactionId!) .toList(); final txUrls = txIds.map((id) => '$baseUrl/transactions/show/$id').toList(); if (failures.isEmpty && successes.isNotEmpty) { // Semua sukses → mark as submitted totalSuccess++; final updated = receipt.copyWith( isSubmitted: true, submittedAt: DateTime.now(), submissionError: null, fireflyTransactionIds: txIds, fireflyTransactionUrls: txUrls, fireflyTransactionId: txIds.isNotEmpty ? txIds.first : null, fireflyTransactionUrl: txUrls.isNotEmpty ? txUrls.first : null, ); await LocalReceiptService.saveReceipt(updated); } else { // Ada yang gagal → simpan ID parsial, tetap unsubmitted totalFailure++; final errorMsg = failures .map((r) => r.error?.replaceFirst('Exception: ', '') ?? 'Gagal') .join('; '); final updated = receipt.copyWith( isSubmitted: false, submissionError: errorMsg, fireflyTransactionIds: txIds, fireflyTransactionUrls: txUrls, ); await LocalReceiptService.saveReceipt(updated); } } if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( '$totalSuccess nota berhasil, $totalFailure nota masih gagal'), duration: const Duration(seconds: 3), ), ); } } Future _deleteReceipt(String receiptId) async { try { await LocalReceiptService.removeReceipt(receiptId); await _loadReceipts(); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Nota berhasil dihapus')), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Gagal menghapus nota: $e')), ); } } } String _formatCurrency(double amount) { final formatter = NumberFormat.currency( locale: 'id_ID', symbol: 'Rp ', decimalDigits: 0, ); // Jangan hapus .00 karena ini penting untuk format rupiah String formatted = formatter.format(amount); // Hapus .00 hanya jika muncul di akhir if (formatted.endsWith('.00')) { formatted = formatted.substring(0, formatted.length - 3); } return formatted; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Nota Tersimpan'), actions: [ // Tombol Kirim Semua Nota (batch send) IconButton( icon: const Icon(Icons.cloud_upload), tooltip: 'Kirim Semua Nota', onPressed: isLoading ? null : _submitAllReceipts, ), IconButton( icon: const Icon(Icons.refresh), onPressed: isLoading ? null : _loadReceipts, ), ], ), body: Column( children: [ // Receipts list Expanded( child: isLoading ? const Center(child: CircularProgressIndicator()) : receipts.isEmpty ? const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.receipt_outlined, size: 64, color: Colors.grey, ), SizedBox(height: 16), Text( 'Belum ada nota tersimpan', style: TextStyle( fontSize: 16, color: Colors.grey, ), ), ], ), ) : RefreshIndicator( onRefresh: _loadReceipts, child: ListView.builder( padding: const EdgeInsets.symmetric(horizontal: 16), itemCount: receipts.length, itemBuilder: (context, index) { final receipt = receipts[index]; return Dismissible( key: Key(receipt.id), direction: DismissDirection.endToStart, onDismissed: (direction) { _deleteReceipt(receipt.id); }, background: Container( color: Colors.red, alignment: Alignment.centerRight, padding: const EdgeInsets.only(right: 16), child: const Icon( Icons.delete, color: Colors.white, size: 24, ), ), child: Card( margin: const EdgeInsets.only(bottom: 8), child: ListTile( contentPadding: const EdgeInsets.all(16), leading: Container( width: 40, height: 40, decoration: BoxDecoration( color: receipt.isSubmitted ? Colors.green.shade100 : Colors.orange.shade100, borderRadius: BorderRadius.circular(20), ), child: Icon( receipt.isSubmitted ? Icons.check_circle : Icons.access_time, color: receipt.isSubmitted ? Colors.green : Colors.orange, size: 20, ), ), title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ // Transaction description di kiri Expanded( flex: 2, child: Text( receipt.transactionDescription ?? 'Transaksi Struk Belanja', style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 14, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ), const SizedBox(width: 8), // Total di kanan Expanded( child: Text( _formatCurrency(receipt.total), style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 14, color: Colors.blue, ), textAlign: TextAlign.right, ), ), ], ), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 4), Text( 'Dibuat: ${DateFormat('dd/MM/yyyy HH:mm').format(receipt.createdAt)}', style: const TextStyle(fontSize: 12), ), if (receipt.isSubmitted) Text( 'Dikirim: ${DateFormat('dd/MM/yyyy HH:mm').format(receipt.submittedAt ?? receipt.createdAt)}', style: const TextStyle( fontSize: 12, color: Colors.green, ), ), if (receipt.submissionError != null) Text( 'Error: ${receipt.submissionError}', style: const TextStyle( fontSize: 12, color: Colors.red, ), ), ], ), onTap: () { if (receipt.isSubmitted && receipt.allFireflyUrls.isNotEmpty) { // Tampilkan dialog daftar link showDialog( context: context, builder: (context) => TransactionLinksDialog( urls: receipt.allFireflyUrls, title: 'Detail Transaksi', ), ); } else { // Jika belum dikirim, edit nota _editReceipt(receipt); } }, tileColor: receipt.isSubmitted && receipt.allFireflyUrls.isNotEmpty ? Colors.blue.shade50 : null, ), ), ); }, ), ), ), ], ), ); } // Fungsi _showDeleteConfirmation dihapus karena sudah menggunakan swipe gesture Future _editReceipt(LocalReceipt receipt) async { // Navigate ke receipt screen dan muat data receipt final receiptProvider = context.read(); receiptProvider.loadReceiptForEdit(receipt); // Hapus receipt dari daftar sebelum navigasi await LocalReceiptService.removeReceipt(receipt.id); // Navigasi ke receipt screen await Navigator.pushNamed(context, '/receipt'); // Refresh daftar setelah kembali dari edit await _loadReceipts(); } }