From 07d5ea8fbe9677f65321077d5669f34a46da12b8 Mon Sep 17 00:00:00 2001 From: asif Date: Wed, 24 Dec 2025 18:06:12 +0530 Subject: [PATCH] chequemanagement #1 --- lib/api/services/branch_service.dart | 2 +- lib/api/services/cheque_service.dart | 96 +++++ lib/di/injection.dart | 6 +- .../screens/transaction_details_screen.dart | 2 +- .../screens/cheque_management_screen.dart | 175 ++++++--- .../cheque/screens/cheque_status_screen.dart | 333 ++++++++++++++++++ .../dashboard/screens/dashboard_screen.dart | 18 +- 7 files changed, 565 insertions(+), 67 deletions(-) create mode 100644 lib/api/services/cheque_service.dart create mode 100644 lib/features/cheque/screens/cheque_status_screen.dart diff --git a/lib/api/services/branch_service.dart b/lib/api/services/branch_service.dart index 9c0a5d1..54562e1 100644 --- a/lib/api/services/branch_service.dart +++ b/lib/api/services/branch_service.dart @@ -105,7 +105,7 @@ class BranchService { if (response.statusCode == 200) { return Branch.listFromJson(response.data); } else { - throw Exception("Failed to fetch beneficiaries"); + throw Exception("Failed to fetch"); } } catch (e) { return []; diff --git a/lib/api/services/cheque_service.dart b/lib/api/services/cheque_service.dart new file mode 100644 index 0000000..6082b1c --- /dev/null +++ b/lib/api/services/cheque_service.dart @@ -0,0 +1,96 @@ +import 'package:dio/dio.dart'; +class Cheque { + final String? type; + final String? InstrType; + final String? Date; + final String? branchCode; + final String? fromCheque; + final String? toCheque; + final String? Chequescount; + final String? ChequeNumber; + final String? transactionCode; + final String? amount; + final String? status; + final String? stopIssueDate; + final String? StopExpiryDate; + + Cheque({ +this.type, +this.InstrType, +this.Date, +this.branchCode, +this.fromCheque, +this.toCheque, +this.Chequescount, +this.ChequeNumber, +this.transactionCode, +this.amount, +this.status, +this.stopIssueDate, +this.StopExpiryDate, + }); + + factory Cheque.fromJson(Map json) { + return Cheque( + type: json['type'] ?? '', + InstrType: json['InstrType'] ?? '', + Date: json['Date'] ?? '', + branchCode: json['branchCode'] ?? '', + fromCheque: json['fromCheque'] ?? '', + toCheque: json['toCheque'] ?? '', + Chequescount: json['Chequescount'] ?? '', + ChequeNumber: json['ChequeNumber'] ?? '', + transactionCode: json['transactionCode'] ?? '', + amount: json['amount'] ?? '', + status: json['status'] ?? '', + stopIssueDate: json['stopIssueDate'] ?? '', + StopExpiryDate: json['StopExpiryDate'] ?? '', + ); + } + + static List listFromJson(List jsonList) { + final chequeList = + jsonList.map((cheque) => Cheque.fromJson(cheque)).toList(); + return chequeList; + } +} + +class ChequeService { + final Dio _dio; + ChequeService(this._dio); + + Future> ChequeEnquiry( + {required String accountNumber, + required String instrType, + }) async { + try { + final response = await _dio.get( + "/api/cheque", + queryParameters: { + 'accountNumber': accountNumber, + 'instrumentType': instrType, + }, + options: Options( + headers: { + "Content-Type": "application/json", + }, + ), + ); + + if (response.statusCode == 200) { + if (response.data is Map && response.data.containsKey('records')) { + final records = response.data['records']; + if (records is List) { + return Cheque.listFromJson(records); + } + } + throw Exception("Unexpected API response format: 'records' list not found or malformed"); + } else { + throw Exception("Failed to fetch"); + } + } catch (e) { + print('Error in ChequeEnquiry: $e'); + throw e; + } + } +} diff --git a/lib/di/injection.dart b/lib/di/injection.dart index d68bca6..d45673d 100644 --- a/lib/di/injection.dart +++ b/lib/di/injection.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:kmobile/api/services/branch_service.dart'; +import 'package:kmobile/api/services/cheque_service.dart'; import 'package:kmobile/api/services/limit_service.dart'; import 'package:kmobile/api/services/rtgs_service.dart'; import 'package:kmobile/api/services/neft_service.dart'; @@ -56,6 +57,7 @@ Future setupDependencies() async { getIt.registerSingleton(RtgsService(getIt())); getIt.registerSingleton(ImpsService(getIt())); getIt.registerSingleton(BranchService(getIt())); + getIt.registerSingleton(ChequeService(getIt())); getIt.registerLazySingleton( () => ChangePasswordService(getIt()), ); @@ -74,9 +76,9 @@ Dio _createDioClient() { final dio = Dio( BaseOptions( baseUrl: - //'http://lb-test-mobile-banking-app-192209417.ap-south-1.elb.amazonaws.com:8080', //test + 'http://lb-test-mobile-banking-app-192209417.ap-south-1.elb.amazonaws.com', //test //'http://lb-kccb-mobile-banking-app-848675342.ap-south-1.elb.amazonaws.com', //prod - 'https://kccbmbnk.net', //prod small + //'https://kccbmbnk.net', //prod small connectTimeout: const Duration(seconds: 60), receiveTimeout: const Duration(seconds: 60), headers: { diff --git a/lib/features/accounts/screens/transaction_details_screen.dart b/lib/features/accounts/screens/transaction_details_screen.dart index 010b0c6..824963b 100644 --- a/lib/features/accounts/screens/transaction_details_screen.dart +++ b/lib/features/accounts/screens/transaction_details_screen.dart @@ -132,4 +132,4 @@ class TransactionDetailsScreen extends StatelessWidget { ), ); } -} +} \ No newline at end of file diff --git a/lib/features/cheque/screens/cheque_management_screen.dart b/lib/features/cheque/screens/cheque_management_screen.dart index fa0a4c5..239f1a1 100644 --- a/lib/features/cheque/screens/cheque_management_screen.dart +++ b/lib/features/cheque/screens/cheque_management_screen.dart @@ -1,16 +1,25 @@ import 'package:flutter/material.dart'; -import 'package:kmobile/features/enquiry/screens/enquiry_screen.dart'; +import 'package:kmobile/data/models/user.dart'; +import 'package:kmobile/features/cheque/screens/cheque_status_screen.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; import '../../../l10n/app_localizations.dart'; class ChequeManagementScreen extends StatefulWidget { - const ChequeManagementScreen({super.key}); + final List users; + final int selectedIndex; + const ChequeManagementScreen({ + super.key, + required this.users, + required this.selectedIndex,}); @override State createState() => _ChequeManagementScreen(); } class _ChequeManagementScreen extends State { + List get users => widget.users; + int get selectedAccountIndex => widget.selectedIndex; + @override Widget build(BuildContext context) { return Scaffold( @@ -22,52 +31,55 @@ class _ChequeManagementScreen extends State { ), body: Stack( children: [ - ListView( - children: [ - const SizedBox(height: 15), - ChequeManagementTile( - icon: Symbols.add, - label: AppLocalizations.of(context).requestChequeBook, - onTap: () {}, - ), - Divider(height: 1, color: Theme.of(context).dividerColor), - ChequeManagementTile( - icon: Symbols.data_alert, - label: AppLocalizations.of(context).enquiry, - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const EnquiryScreen()), - ); - }, - ), - Divider(height: 1, color: Theme.of(context).dividerColor), - ChequeManagementTile( - icon: Symbols.approval_delegation, - label: AppLocalizations.of(context).chequeDeposit, - onTap: () {}, - ), - Divider(height: 1, color: Theme.of(context).dividerColor), - ChequeManagementTile( - icon: Symbols.front_hand, - label: AppLocalizations.of(context).stopCheque, - onTap: () {}, - ), - Divider(height: 1, color: Theme.of(context).dividerColor), - ChequeManagementTile( - icon: Symbols.cancel_presentation, - label: AppLocalizations.of(context).revokeStop, - onTap: () {}, - ), - Divider(height: 1, color: Theme.of(context).dividerColor), - ChequeManagementTile( - icon: Symbols.payments, - label: AppLocalizations.of(context).positivePay, - onTap: () {}, - ), - Divider(height: 1, color: Theme.of(context).dividerColor), - ], + Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: ChequeManagementCardTile( + icon: Symbols.add, + label: AppLocalizations.of(context).requestChequeBook, + subtitle: "Apply for a new cheque book", + onTap: () {}, + ), + ), + Expanded( + child: ChequeManagementCardTile( + icon: Symbols.data_alert, + label: "Cheque History", + subtitle: "View the status of your issued cheques", + onTap: () {}, + ), + ), + Expanded( + child: ChequeManagementCardTile( + icon: Symbols.front_hand, + label: AppLocalizations.of(context).stopCheque, + subtitle: "Stop payment for a cheque", + onTap: () {}, + ), + ), + Expanded( + child: ChequeManagementCardTile( + icon: Symbols.payments, + label: "Track Cheque Status", + subtitle: "Check the current status of your cheque", + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ChequeStatusScreen( + users: users, + selectedIndex: selectedAccountIndex, + ), + ), + ); + }, + ), + ), + ], + ), ), IgnorePointer( child: Center( @@ -89,25 +101,78 @@ class _ChequeManagementScreen extends State { } } -class ChequeManagementTile extends StatelessWidget { +class ChequeManagementCardTile extends StatelessWidget { final IconData icon; final String label; + final String? subtitle; final VoidCallback onTap; + final bool disable; - const ChequeManagementTile({ + const ChequeManagementCardTile({ super.key, required this.icon, required this.label, + this.subtitle, required this.onTap, + this.disable = false, }); @override Widget build(BuildContext context) { - return ListTile( - leading: Icon(icon), - title: Text(label), - trailing: const Icon(Symbols.arrow_right, size: 20), - onTap: onTap, + final theme = Theme.of(context); + return Card( + margin: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.0), + ), + elevation: 4, // Add some elevation for better visual separation + child: InkWell( + onTap: + disable ? null : onTap, // Disable InkWell if the tile is disabled + borderRadius: BorderRadius.circular(12.0), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 24.0, horizontal: 16.0), + child: Center( + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + icon, + size: 48, // Make icon larger + color: + disable ? theme.disabledColor : theme.colorScheme.primary, + ), + const SizedBox(height: 12), + Text( + label, + textAlign: TextAlign.center, + style: theme.textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.bold, + color: disable + ? theme.disabledColor + : theme.colorScheme.onSurface, + ), + ), + if (subtitle != null) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + subtitle!, + textAlign: TextAlign.center, + style: theme.textTheme.bodyMedium?.copyWith( + color: disable + ? theme.disabledColor + : theme.colorScheme.onSurfaceVariant, + ), + ), + ), + ], + ), + ), + ), + ), + ), ); } } diff --git a/lib/features/cheque/screens/cheque_status_screen.dart b/lib/features/cheque/screens/cheque_status_screen.dart new file mode 100644 index 0000000..a9def87 --- /dev/null +++ b/lib/features/cheque/screens/cheque_status_screen.dart @@ -0,0 +1,333 @@ +import 'package:flutter/material.dart'; +import 'package:kmobile/api/services/cheque_service.dart'; +import 'package:kmobile/data/models/user.dart'; +import 'package:kmobile/di/injection.dart'; + +class ChequeStatusScreen extends StatefulWidget { + final List users; + final int selectedIndex; + const ChequeStatusScreen({ + super.key, + required this.users, + required this.selectedIndex, + }); + + @override + State createState() => _ChequeStatusScreenState(); +} + +class _ChequeStatusScreenState extends State { + User? _selectedAccount; + final TextEditingController _searchController = TextEditingController(); + var service = getIt(); + bool _isLoading = true; + List _allCheques = []; + Map> _groupedCheques = {}; + + @override + void initState() { + super.initState(); + final List _filteredUsers = widget.users + .where((user) => ['SA', 'SB', 'CA', 'CC'].contains(user.accountType)) + .toList(); + + if (widget.users.isNotEmpty && widget.selectedIndex < widget.users.length) { + if (_filteredUsers.isNotEmpty) { + if (_filteredUsers.contains(widget.users[widget.selectedIndex])) { + _selectedAccount = widget.users[widget.selectedIndex]; + } else { + _selectedAccount = _filteredUsers.first; + } + } else { + _selectedAccount = widget.users[widget.selectedIndex]; + } + } else { + if (_filteredUsers.isNotEmpty) { + _selectedAccount = _filteredUsers.first; + } + } + + _loadCheques(); + _searchController.addListener(() { + _filterCheques(_searchController.text); + }); + } + + Future _loadCheques() async { + if (_selectedAccount == null) { + setState(() { + _isLoading = false; + _groupedCheques = {}; + }); + return; + } + setState(() { + _isLoading = true; + }); + + String instrType; + switch (_selectedAccount!.accountType) { + case 'SA': + case 'SB': + instrType = '10'; + break; + case 'CA': + instrType = '11'; + break; + case 'CC': + instrType = '13'; + break; + default: + instrType = '10'; + } + + try { + final data = await service.ChequeEnquiry( + accountNumber: _selectedAccount!.accountNo!, instrType: instrType); + _allCheques = data; + _groupedCheques.clear(); + for (var cheque in _allCheques) { + if (cheque.type != null) { + if (!_groupedCheques.containsKey(cheque.type)) { + _groupedCheques[cheque.type!] = []; + } + _groupedCheques[cheque.type!]!.add(cheque); + } + } + setState(() { + _isLoading = false; + }); + } catch (e) { + setState(() { + _isLoading = false; + _groupedCheques = {}; + }); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Failed to fetch cheque status: ${e.toString()}'), + ), + ); + } + } + + void _filterCheques(String query) { + _groupedCheques.clear(); + List filteredCheques; + if (query.isEmpty) { + filteredCheques = _allCheques; + } else { + filteredCheques = _allCheques.where((cheque) { + final lowerQuery = query.toLowerCase(); + return (cheque.ChequeNumber?.toLowerCase().contains(lowerQuery) ?? false) || + (cheque.amount?.toLowerCase().contains(lowerQuery) ?? false) || + (cheque.status?.toLowerCase().contains(lowerQuery) ?? false) || + (cheque.fromCheque?.toLowerCase().contains(lowerQuery) ?? false) || + (cheque.toCheque?.toLowerCase().contains(lowerQuery) ?? false); + }).toList(); + } + + for (var cheque in filteredCheques) { + if (cheque.type != null) { + if (!_groupedCheques.containsKey(cheque.type)) { + _groupedCheques[cheque.type!] = []; + } + _groupedCheques[cheque.type!]!.add(cheque); + } + } + + setState(() {}); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Track Cheque Status'), + centerTitle: false, + ), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + Card( + elevation: 4, + margin: const EdgeInsets.symmetric(vertical: 8.0), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "Account Number", + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 18), + ), + if (_selectedAccount != null) + DropdownButtonFormField( + value: _selectedAccount, + onChanged: (User? newUser) { + if (newUser != null) { + setState(() { + _selectedAccount = newUser; + _loadCheques(); + }); + } + }, + items: widget.users + .where((user) => + ['SA', 'SB', 'CA', 'CC'].contains(user.accountType)) + .map((user) { + return DropdownMenuItem( + value: user, + child: Text(user.accountNo.toString()), + ); + }).toList(), + ) + else + const Text('No accounts found'), + ], + ), + ), + ), + const SizedBox(height: 20), + TextField( + controller: _searchController, + decoration: const InputDecoration( + labelText: 'Search...', + prefixIcon: Icon(Icons.search), + ), + ), + const SizedBox(height: 20), + Expanded( + child: _isLoading + ? const Center(child: CircularProgressIndicator()) + : _groupedCheques.isEmpty + ? const Center(child: Text('No cheque status found.')) + : ListView( + children: _groupedCheques.entries.map((entry) { + return ExpansionTile( + title: Text(entry.key, style: TextStyle(fontWeight: FontWeight.bold)), + children: entry.value + .map((cheque) => ChequeStatusTile(cheque: cheque)) + .toList(), + ); + }).toList(), + ), + ), + ], + ), + ), + ); + } +} + +class ChequeStatusTile extends StatelessWidget { + final Cheque cheque; + + const ChequeStatusTile({ + super.key, + required this.cheque, + }); + + @override + Widget build(BuildContext context) { + switch (cheque.type) { + case 'CI': + return _buildCiTile(context); + case 'PR': + return _buildPrTile(context); + case 'ST': + return _buildStTile(context); + default: + return const SizedBox.shrink(); + } + } + + Widget _buildCiTile(BuildContext context) { + return Card( + margin: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildInfoRow('Branch Code:', cheque.branchCode), + _buildInfoRow('From Cheque:', cheque.fromCheque), + _buildInfoRow('To Cheque:', cheque.toCheque), + _buildInfoRow('Date:', cheque.Date), + _buildInfoRow('Cheques Count:', cheque.Chequescount), + _buildInfoRow('Instrument Type:', cheque.InstrType), + ], + ), + ), + ); + } + + Widget _buildPrTile(BuildContext context) { + return Card( + margin: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildInfoRow('Branch Code:', cheque.branchCode), + _buildInfoRow('Cheque Number:', cheque.ChequeNumber), + _buildInfoRow('Date:', cheque.Date), + _buildInfoRow('Transaction Code:', cheque.transactionCode), + _buildInfoRow('Amount:', cheque.amount), + _buildInfoRow('Status:', cheque.status, statusColor: _getStatusColor(cheque.status ?? '')), + _buildInfoRow('Instrument Type:', cheque.InstrType), + ], + ), + ), + ); + } + + Widget _buildStTile(BuildContext context) { + return Card( + margin: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildInfoRow('Branch Code:', cheque.branchCode), + _buildInfoRow('From Cheque:', cheque.fromCheque), + _buildInfoRow('To Cheque:', cheque.toCheque), + _buildInfoRow('Stop Issue Date:', cheque.stopIssueDate), + _buildInfoRow('Stop Expiry Date:', cheque.StopExpiryDate), + _buildInfoRow('Cheques Count:', cheque.Chequescount), + _buildInfoRow('Instrument Type:', cheque.InstrType), + ], + ), + ), + ); + } + + Widget _buildInfoRow(String label, String? value, {Color? statusColor}) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(label, style: const TextStyle(fontWeight: FontWeight.bold)), + Text(value ?? '', style: TextStyle(color: statusColor)), + ], + ), + ); + } + + Color _getStatusColor(String status) { + switch (status.toLowerCase()) { + case 'cashed': + return Colors.green; + case 'pending': + return Colors.orange; + case 'bounced': + return Colors.red; + default: + return Colors.grey; + } + } +} diff --git a/lib/features/dashboard/screens/dashboard_screen.dart b/lib/features/dashboard/screens/dashboard_screen.dart index 6e5be62..5a132bc 100644 --- a/lib/features/dashboard/screens/dashboard_screen.dart +++ b/lib/features/dashboard/screens/dashboard_screen.dart @@ -8,6 +8,7 @@ import 'package:kmobile/features/accounts/screens/account_statement_screen.dart' import 'package:kmobile/features/accounts/screens/all_accounts_screen.dart'; import 'package:kmobile/features/auth/controllers/auth_cubit.dart'; import 'package:kmobile/features/auth/controllers/auth_state.dart'; +import 'package:kmobile/features/cheque/screens/cheque_management_screen.dart'; import 'package:kmobile/features/customer_info/screens/customer_info_screen.dart'; import 'package:kmobile/features/beneficiaries/screens/manage_beneficiaries_screen.dart'; import 'package:kmobile/features/enquiry/screens/enquiry_screen.dart'; @@ -443,7 +444,8 @@ class _DashboardScreenState extends State final accountType = currAccount.accountType?.toLowerCase(); final isPaymentDisabled = accountType != 'sa' && accountType != 'sb' && - accountType != 'ca'; + accountType != 'ca' && + accountType != 'cc'; // first‐time load if (!_txInitialized) { _txInitialized = true; @@ -643,20 +645,20 @@ class _DashboardScreenState extends State const EnquiryScreen())); }), _buildQuickLink( - Symbols.person, - AppLocalizations.of(context).profile, + Symbols.checkbook, + AppLocalizations.of(context).chequeManagement, () { Navigator.push( context, MaterialPageRoute( - builder: (context) => ProfileScreen( - mobileNumber: mobileNumberToPass, - customerNo: customerNo, - customerName: customerName), + builder: (context) => ChequeManagementScreen( + users: users, + selectedIndex: selectedAccountIndex + ), ), ); }, - disable: false, + disable: isPaymentDisabled, ), ], ),