cashumit/lib/screens/local_receipts_screen.dart

405 lines
16 KiB
Dart

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 {
const LocalReceiptsScreen({super.key});
@override
State<LocalReceiptsScreen> createState() => _LocalReceiptsScreenState();
}
class _LocalReceiptsScreenState extends State<LocalReceiptsScreen> {
List<LocalReceipt> receipts = [];
bool isLoading = true;
bool isSubmitting = false;
@override
void initState() {
super.initState();
_loadReceipts();
}
Future<void> _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<void> _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;
}
setState(() {
isSubmitting = true;
});
try {
final result = await LocalReceiptService.submitAllUnsubmittedReceipts(
credentials['url']!,
credentials['token']!,
);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Berhasil: ${result['successCount']} berhasil, ${result['failureCount']} gagal dari ${result['totalCount']} total nota',
),
),
);
}
await _loadReceipts(); // Refresh daftar setelah submit
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Gagal mengirim nota: $e')),
);
}
} finally {
setState(() {
isSubmitting = false;
});
}
}
Future<void> _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: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: isLoading ? null : _loadReceipts,
),
],
),
body: Column(
children: [
// Summary card
Container(
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.3),
spreadRadius: 1,
blurRadius: 3,
offset: const Offset(0, 2),
),
],
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Column(
children: [
Text(
receipts
.where((r) => !r.isSubmitted)
.length
.toString(),
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.orange,
),
),
const Text(
'Menunggu',
style: TextStyle(color: Colors.grey),
),
],
),
Column(
children: [
Text(
receipts
.where((r) => r.isSubmitted)
.length
.toString(),
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.green,
),
),
const Text(
'Terkirim',
style: TextStyle(color: Colors.grey),
),
],
),
Column(
children: [
Text(
receipts.length.toString(),
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
const Text(
'Total',
style: TextStyle(color: Colors.grey),
),
],
),
],
),
const SizedBox(height: 16),
ElevatedButton.icon(
onPressed: isSubmitting ? null : _submitAllReceipts,
icon: isSubmitting
? const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor:
AlwaysStoppedAnimation<Color>(Colors.white),
),
)
: const Icon(Icons.sync),
label:
Text(isSubmitting ? 'Mengirim...' : 'Kirim Semua Nota'),
),
],
),
),
// 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: 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,
),
),
);
},
),
),
),
],
),
);
}
// Fungsi _showDeleteConfirmation dihapus karena sudah menggunakan swipe gesture
}