From d64d3716c34e107735aaa8fe872accd0e51e7d28 Mon Sep 17 00:00:00 2001 From: a2nr Date: Sat, 8 Nov 2025 13:38:24 +0700 Subject: [PATCH] feat: tambahan fitur swipe untuk hapus item dan tap untuk webview transaksi --- lib/models/local_receipt.dart | 13 ++ lib/screens/local_receipts_screen.dart | 242 ++++++++++++------------ lib/services/local_receipt_service.dart | 12 ++ 3 files changed, 147 insertions(+), 120 deletions(-) diff --git a/lib/models/local_receipt.dart b/lib/models/local_receipt.dart index c24197b..6de12b8 100644 --- a/lib/models/local_receipt.dart +++ b/lib/models/local_receipt.dart @@ -10,6 +10,8 @@ class LocalReceipt { final DateTime transactionDate; final String? transactionDescription; // Deskripsi transaksi untuk tampilan di daftar + final String? fireflyTransactionId; // ID transaksi di FireFly III + final String? fireflyTransactionUrl; // URL transaksi di FireFly III final bool isSubmitted; final String? submissionError; final DateTime? submittedAt; @@ -24,6 +26,8 @@ class LocalReceipt { this.destinationAccountName, required this.transactionDate, this.transactionDescription, + this.fireflyTransactionId, + this.fireflyTransactionUrl, this.isSubmitted = false, this.submissionError, this.submittedAt, @@ -42,6 +46,8 @@ class LocalReceipt { 'destinationAccountName': destinationAccountName, 'transactionDate': transactionDate.toIso8601String(), 'transactionDescription': transactionDescription, + 'fireflyTransactionId': fireflyTransactionId, + 'fireflyTransactionUrl': fireflyTransactionUrl, 'isSubmitted': isSubmitted, 'submissionError': submissionError, 'submittedAt': submittedAt?.toIso8601String(), @@ -61,6 +67,8 @@ class LocalReceipt { destinationAccountName: json['destinationAccountName'], transactionDate: DateTime.parse(json['transactionDate']), transactionDescription: json['transactionDescription'], + fireflyTransactionId: json['fireflyTransactionId'], + fireflyTransactionUrl: json['fireflyTransactionUrl'], isSubmitted: json['isSubmitted'] ?? false, submissionError: json['submissionError'], submittedAt: json['submittedAt'] != null @@ -79,6 +87,8 @@ class LocalReceipt { String? destinationAccountName, DateTime? transactionDate, String? transactionDescription, + String? fireflyTransactionId, + String? fireflyTransactionUrl, bool? isSubmitted, String? submissionError, DateTime? submittedAt, @@ -95,6 +105,9 @@ class LocalReceipt { transactionDate: transactionDate ?? this.transactionDate, transactionDescription: transactionDescription ?? this.transactionDescription, + fireflyTransactionId: fireflyTransactionId ?? this.fireflyTransactionId, + fireflyTransactionUrl: + fireflyTransactionUrl ?? this.fireflyTransactionUrl, isSubmitted: isSubmitted ?? this.isSubmitted, submissionError: submissionError ?? this.submissionError, submittedAt: submittedAt ?? this.submittedAt, diff --git a/lib/screens/local_receipts_screen.dart b/lib/screens/local_receipts_screen.dart index 368371b..550586f 100644 --- a/lib/screens/local_receipts_screen.dart +++ b/lib/screens/local_receipts_screen.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:cashumit/models/local_receipt.dart'; import 'package:cashumit/services/local_receipt_service.dart'; import 'package:cashumit/services/receipt_service.dart'; +import 'package:cashumit/screens/webview_screen.dart'; import 'package:intl/intl.dart'; class LocalReceiptsScreen extends StatefulWidget { @@ -116,7 +117,13 @@ class _LocalReceiptsScreenState extends State { symbol: 'Rp ', decimalDigits: 0, ); - return formatter.format(amount).replaceAll('.00', ''); + // 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 @@ -262,108 +269,125 @@ class _LocalReceiptsScreenState extends State { itemCount: receipts.length, itemBuilder: (context, index) { final receipt = receipts[index]; - return 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), + 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, + ), ), - 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, + ), + ), + ], ), - ), - 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) + subtitle: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + const SizedBox(height: 4), Text( - 'Dikirim: ${DateFormat('dd/MM/yyyy HH:mm').format(receipt.submittedAt ?? receipt.createdAt)}', - style: const TextStyle( - fontSize: 12, - color: Colors.green, - ), + 'Dibuat: ${DateFormat('dd/MM/yyyy HH:mm').format(receipt.createdAt)}', + style: const TextStyle(fontSize: 12), ), - if (receipt.submissionError != null) - Text( - 'Error: ${receipt.submissionError}', - style: const TextStyle( - fontSize: 12, - color: Colors.red, + if (receipt.isSubmitted) + Text( + 'Dikirim: ${DateFormat('dd/MM/yyyy HH:mm').format(receipt.submittedAt ?? receipt.createdAt)}', + style: const TextStyle( + fontSize: 12, + color: Colors.green, + ), ), - ), - ], - ), - trailing: PopupMenuButton( - onSelected: (String action) { - if (action == 'delete') { - _showDeleteConfirmation(receipt.id); - } - }, - itemBuilder: (BuildContext context) { - return [ - const PopupMenuItem( - value: 'delete', - child: Row( - children: [ - Icon(Icons.delete, size: 18), - SizedBox(width: 8), - Text('Hapus'), - ], + if (receipt.submissionError != null) + Text( + 'Error: ${receipt.submissionError}', + style: const TextStyle( + fontSize: 12, + color: Colors.red, + ), ), - ), - ]; - }, + ], + ), + onTap: receipt.isSubmitted && + receipt.fireflyTransactionUrl != null + ? () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + WebViewScreen( + url: receipt + .fireflyTransactionUrl!, + title: 'Detail Transaksi', + ), + ), + ); + } + : null, + tileColor: receipt.isSubmitted && + receipt.fireflyTransactionUrl != null + ? Colors.blue.shade50 + : null, ), ), ); @@ -376,27 +400,5 @@ class _LocalReceiptsScreenState extends State { ); } - Future _showDeleteConfirmation(String receiptId) async { - final result = await showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Konfirmasi Hapus'), - content: const Text('Apakah Anda yakin ingin menghapus nota ini?'), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context, false), - child: const Text('Batal'), - ), - TextButton( - onPressed: () => Navigator.pop(context, true), - child: const Text('Hapus'), - ), - ], - ), - ); - - if (result == true) { - _deleteReceipt(receiptId); - } - } + // Fungsi _showDeleteConfirmation dihapus karena sudah menggunakan swipe gesture } diff --git a/lib/services/local_receipt_service.dart b/lib/services/local_receipt_service.dart index 581e98e..0e4664e 100644 --- a/lib/services/local_receipt_service.dart +++ b/lib/services/local_receipt_service.dart @@ -200,6 +200,18 @@ class LocalReceiptService { amount: receipt.total.toStringAsFixed(2), ); + // Jika transaksi berhasil dikirim, simpan transactionId dan URL + if (transactionId != null) { + // Update receipt dengan transactionId dan URL + final updatedReceipt = receipt.copyWith( + fireflyTransactionId: transactionId, + fireflyTransactionUrl: '$baseUrl/transactions/show/$transactionId', + ); + + // Simpan kembali receipt yang diperbarui ke storage + await saveReceipt(updatedReceipt); + } + return transactionId; } catch (e) { print('Error submitting receipt to FireFly III: $e');