first commit

This commit is contained in:
2026-02-07 15:57:09 +07:00
commit 157096f164
1153 changed files with 415766 additions and 0 deletions

View File

@@ -0,0 +1,402 @@
// File: add_edit_new_bank.dart
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:iconly/iconly.dart';
import 'package:intl/intl.dart';
import 'package:mobile_pos/Screens/cash%20and%20bank/bank%20account/repo/bank_account_repo.dart';
import 'package:mobile_pos/generated/l10n.dart' as l;
// --- Local Imports ---
import 'package:mobile_pos/constant.dart';
import 'package:mobile_pos/currency.dart';
import 'model/bank_account_list_model.dart';
// Accept optional BankData for editing
class AddEditNewBank extends ConsumerStatefulWidget {
final BankData? bankData;
const AddEditNewBank({super.key, this.bankData});
@override
ConsumerState<AddEditNewBank> createState() => _AddEditNewBankState();
}
class _AddEditNewBankState extends ConsumerState<AddEditNewBank> {
final _key = GlobalKey<FormState>();
// Core fields
final nameController = TextEditingController(); // Account Display Name
final openingBalanceController = TextEditingController();
final asOfDateController = TextEditingController();
// Meta fields
final accNumberController = TextEditingController();
final ifscController = TextEditingController();
final upiController = TextEditingController();
final bankNameController = TextEditingController();
final accHolderController = TextEditingController();
// State
bool _showMoreFields = false;
bool _showInInvoice = false;
DateTime? _selectedDate;
// Date formats
final DateFormat _displayFormat = DateFormat('dd/MM/yyyy');
final DateFormat _apiFormat = DateFormat('yyyy-MM-dd', 'en_US');
bool get isEditing => widget.bankData != null;
@override
void initState() {
super.initState();
if (!isEditing) {
_selectedDate = DateTime.now();
asOfDateController.text = _displayFormat.format(_selectedDate!);
} else {
_loadInitialData();
}
}
void _loadInitialData() {
final data = widget.bankData!;
nameController.text = data.name ?? '';
openingBalanceController.text = data.openingBalance?.toString() ?? '';
_showInInvoice = data.showInInvoice == 1;
if (data.openingDate != null) {
try {
_selectedDate = DateTime.parse(data.openingDate!);
asOfDateController.text = _displayFormat.format(_selectedDate!);
} catch (_) {
asOfDateController.text = data.openingDate!;
}
}
if (data.meta != null) {
_showMoreFields = true;
accNumberController.text = data.meta!.accountNumber ?? '';
ifscController.text = data.meta!.ifscCode ?? '';
upiController.text = data.meta!.upiId ?? '';
bankNameController.text = data.meta!.bankName ?? '';
accHolderController.text = data.meta!.accountHolder ?? '';
}
}
@override
void dispose() {
nameController.dispose();
openingBalanceController.dispose();
asOfDateController.dispose();
accNumberController.dispose();
ifscController.dispose();
upiController.dispose();
bankNameController.dispose();
accHolderController.dispose();
super.dispose();
}
Future<void> _selectDate(BuildContext context) async {
DateTime initialDate = _selectedDate ?? DateTime.now();
final DateTime? picked = await showDatePicker(
initialDate: initialDate,
firstDate: DateTime(2000),
lastDate: DateTime(2101),
context: context,
);
if (picked != null) {
setState(() {
_selectedDate = picked;
asOfDateController.text = _displayFormat.format(picked);
});
}
}
// --- Submission Logic ---
void _submit() async {
if (!_key.currentState!.validate() || _selectedDate == null) {
if (_selectedDate == null) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('As of Date is required.')));
}
return;
}
final repo = BankRepo();
final apiOpeningDate = _apiFormat.format(_selectedDate!);
final apiShowInInvoice = _showInInvoice ? 1 : 0;
final meta = BankMeta(
accountNumber: accNumberController.text.trim(),
ifscCode: ifscController.text.trim(),
upiId: upiController.text.trim(),
bankName: bankNameController.text.trim(),
accountHolder: accHolderController.text.trim(),
);
if (isEditing) {
await repo.updateBank(
ref: ref,
context: context,
id: widget.bankData!.id!,
name: nameController.text,
openingBalance: num.tryParse(openingBalanceController.text) ?? 0,
openingDate: apiOpeningDate,
showInInvoice: apiShowInInvoice,
meta: meta,
);
} else {
await repo.createBank(
ref: ref,
context: context,
name: nameController.text,
openingBalance: num.tryParse(openingBalanceController.text) ?? 0,
openingDate: apiOpeningDate,
showInInvoice: apiShowInInvoice,
meta: meta,
);
}
}
// --- Reset/Cancel Logic ---
void _resetOrCancel() {
if (isEditing) {
Navigator.pop(context);
} else {
setState(() {
_key.currentState?.reset();
nameController.clear();
openingBalanceController.clear();
accNumberController.clear();
ifscController.clear();
upiController.clear();
bankNameController.clear();
accHolderController.clear();
_showInInvoice = false;
_showMoreFields = false;
_selectedDate = DateTime.now();
asOfDateController.text = _displayFormat.format(_selectedDate!);
});
}
}
@override
Widget build(BuildContext context) {
final _theme = Theme.of(context);
final _lang = l.S.of(context);
return Scaffold(
backgroundColor: kWhite,
appBar: AppBar(
title: Text(
isEditing ? _lang.editBankAccounts : _lang.addNewBankAccounts,
),
centerTitle: true,
elevation: 0,
actions: [
IconButton(onPressed: () => Navigator.pop(context), icon: const Icon(Icons.close)),
],
bottom: const PreferredSize(
preferredSize: Size.fromHeight(1),
child: Divider(height: 2, color: kBackgroundColor),
),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Form(
key: _key,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// --- Row 1: Account Display Name ---
TextFormField(
controller: nameController,
decoration: InputDecoration(
labelText: _lang.accountDisplayName,
hintText: _lang.enterAccountDisplayName,
),
validator: (value) => value!.isEmpty ? _lang.displayNameIsRequired : null,
),
const SizedBox(height: 20),
// --- Row 2: Balance, Date (Max 2 fields) ---
TextFormField(
controller: openingBalanceController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
labelText: _lang.openingBalance,
hintText: 'Ex: 500',
prefixText: currency,
),
validator: (value) => value!.isEmpty ? _lang.openingBalanceIsRequired : null,
),
const SizedBox(height: 16),
TextFormField(
readOnly: true,
controller: asOfDateController,
decoration: InputDecoration(
labelText: _lang.asOfDate,
hintText: 'DD/MM/YYYY',
suffixIcon: IconButton(
icon: const Icon(IconlyLight.calendar, size: 22),
onPressed: () => _selectDate(context),
),
),
validator: (value) => value!.isEmpty ? _lang.dateIsRequired : null,
),
const SizedBox(height: 16),
// --- Toggle More Fields Button ---
GestureDetector(
onTap: () {
setState(() {
_showMoreFields = !_showMoreFields;
});
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Text(
_showMoreFields ? '- ${_lang.hideFiled}' : '+ ${_lang.addMoreFiled}',
style: _theme.textTheme.bodyMedium?.copyWith(
color: kSuccessColor,
fontWeight: FontWeight.w500,
),
),
),
),
const SizedBox(height: 16),
// --- Extra Fields (Meta Data) ---
if (_showMoreFields)
Column(
children: [
// Row 3: Account Number, IFSC
TextFormField(
controller: accNumberController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
labelText: _lang.accountNumber,
hintText: _lang.enterAccountName,
),
),
const SizedBox(height: 20),
TextFormField(
controller: ifscController,
decoration: InputDecoration(labelText: _lang.ifscCode, hintText: 'Ex: DBBL0001234'),
),
const SizedBox(height: 20),
// Row 4: UPI, Bank Name
TextFormField(
controller: upiController,
decoration: InputDecoration(
labelText: _lang.upiIdForQrCode,
hintText: 'yourname@upi',
),
),
const SizedBox(height: 20),
TextFormField(
controller: bankNameController,
decoration: InputDecoration(
labelText: _lang.bankName,
hintText: _lang.enterBankName,
),
),
const SizedBox(height: 20),
// Row 5: Account Holder (Single field)
TextFormField(
controller: accHolderController,
decoration: InputDecoration(
labelText: _lang.accountHolderName,
hintText: _lang.enterAccountHolderName,
),
),
const SizedBox(height: 16),
],
),
// --- Show in Invoice Checkbox ---
Text.rich(
TextSpan(
children: [
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Theme(
data: Theme.of(context).copyWith(
checkboxTheme: CheckboxThemeData(
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
side: const BorderSide(color: Colors.grey, width: 1),
),
),
child: Checkbox(
value: _showInInvoice,
onChanged: (value) {
setState(() => _showInInvoice = value ?? false);
},
activeColor: Colors.blue, // your kMainColor
),
),
),
TextSpan(
text: _lang.printBankDetailsAndInvoice,
style: const TextStyle(color: Colors.black),
recognizer: TapGestureRecognizer()
..onTap = () {
setState(() => _showInInvoice = !_showInInvoice);
},
),
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Row(
mainAxisSize: MainAxisSize.min,
children: const [
Icon(
Icons.info_outline,
size: 18,
color: Colors.grey, // your kGreyTextColor
),
],
),
),
],
),
),
],
),
),
),
bottomNavigationBar: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: OutlinedButton(
onPressed: _resetOrCancel,
style: OutlinedButton.styleFrom(
minimumSize: const Size(double.infinity, 50),
side: const BorderSide(color: Colors.red),
foregroundColor: Colors.red,
),
child: Text(isEditing ? _lang.cancel : _lang.resets),
),
),
const SizedBox(width: 16),
Expanded(
child: ElevatedButton(
onPressed: _submit,
style: ElevatedButton.styleFrom(
minimumSize: const Size(double.infinity, 50),
backgroundColor: const Color(0xFFB71C1C),
foregroundColor: Colors.white,
),
child: Text(_lang.save),
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,550 @@
import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:hugeicons/hugeicons.dart';
import 'package:iconly/iconly.dart';
import 'package:icons_plus/icons_plus.dart';
import 'package:intl/intl.dart';
import 'package:mobile_pos/Screens/cash%20and%20bank/bank%20account/provider/bank_account_provider.dart';
import 'package:mobile_pos/Screens/cash%20and%20bank/bank%20account/repo/bank_account_repo.dart';
// --- Local Imports ---
import 'package:mobile_pos/constant.dart';
import 'package:mobile_pos/generated/l10n.dart' as l;
import 'package:mobile_pos/currency.dart';
import 'package:mobile_pos/Screens/hrm/widgets/model_bottom_sheet.dart';
import 'package:mobile_pos/Screens/hrm/widgets/global_search_appbar.dart';
import '../../../service/check_user_role_permission_provider.dart';
import '../../../widgets/empty_widget/_empty_widget.dart';
import '../../hrm/widgets/deleteing_alart_dialog.dart';
import '../adjust bank balance/adjust_bank_balance_screen.dart';
import '../bank to bank transfer/bank_to_bank_transfer_screen.dart';
import '../bank to cash transfer/bank_to_cash_transfer.dart';
import 'add_edit_new_bank_account_screen.dart';
import 'bank_transfer_history_screen.dart';
import 'model/bank_account_list_model.dart';
class BankAccountListScreen extends ConsumerStatefulWidget {
const BankAccountListScreen({super.key});
@override
ConsumerState<BankAccountListScreen> createState() => _BankAccountListScreenState();
}
class _BankAccountListScreenState extends ConsumerState<BankAccountListScreen> {
final TextEditingController _searchController = TextEditingController();
String _searchQuery = '';
bool _isSearch = false;
List<BankData> _filteredList = [];
@override
void initState() {
super.initState();
_searchController.addListener(_applyFilters);
}
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
// --- Date Formatting Utility ---
String _formatDateForDisplay(String? date) {
if (date == null || date.isEmpty) return 'N/A';
try {
final dateTime = DateFormat('yyyy-MM-dd').parse(date);
return DateFormat('dd MMM, yyyy').format(dateTime);
} catch (_) {
return date;
}
}
// --- END Date Formatting Utility ---
void _applyFilters() {
setState(() {
_searchQuery = _searchController.text;
});
}
void _filterBanks(List<BankData> allBanks) {
final query = _searchQuery.toLowerCase().trim();
if (query.isEmpty) {
_filteredList = allBanks;
} else {
_filteredList = allBanks.where((bank) {
final name = (bank.name ?? '').toLowerCase();
final bankName = (bank.meta?.bankName ?? '').toLowerCase();
final accNumber = (bank.meta?.accountNumber ?? '').toLowerCase();
final holderName = (bank.meta?.accountHolder ?? '').toLowerCase();
return name.contains(query) ||
bankName.contains(query) ||
accNumber.contains(query) ||
holderName.contains(query);
}).toList();
}
}
// --- CRITICAL FIX 1: NAVIGATION AND ACTION METHODS ---
void _navigateToEdit(BankData bank) {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => AddEditNewBank(bankData: bank)),
);
}
void _navigateToTransactions(BankData bank) {
// Placeholder for navigating to transactions screen
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('${l.S.of(context).viewingTransactionFor} ${bank.name}')),
);
}
void _showDeleteConfirmationDialog(num id, String name) async {
bool result = await showDeleteConfirmationDialog(context: context, itemName: name);
if (result) {
final repo = BankRepo();
await repo.deleteBank(id: id, context: context, ref: ref);
// Repo handles invalidate(bankListProvider)
}
}
// --- END FIX 1 ---
// --- Pull to Refresh ---
Future<void> _refreshData() async {
ref.invalidate(bankListProvider);
return ref.watch(bankListProvider.future);
}
@override
Widget build(BuildContext context) {
final _lang = l.S.of(context);
final theme = Theme.of(context);
final bankListAsync = ref.watch(bankListProvider);
final permissionService = PermissionService(ref); // Assuming this is defined
return Scaffold(
backgroundColor: Colors.white,
appBar: GlobalSearchAppBar(
isSearch: _isSearch,
onSearchToggle: () {
setState(() {
_isSearch = !_isSearch;
if (!_isSearch) {
_searchController.clear();
}
});
},
title: _lang.bankAccounts,
controller: _searchController,
onChanged: (query) {
// Handled by _searchController.addListener
},
),
body: bankListAsync.when(
data: (model) {
// Check read permission (Assuming 'bank_read_permit' exists)
if (!permissionService.hasPermission('bank_read_permit')) {
return const Center(child: PermitDenyWidget()); // Assuming PermitDenyWidget exists
}
final allBanks = model.data ?? [];
_filterBanks(allBanks);
if (_filteredList.isEmpty) {
return RefreshIndicator(
onRefresh: _refreshData,
child: Center(
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: Text(
_searchController.text.isEmpty
? _lang.noBankAccountFound
: '${_lang.noAccountsFoundMissing} "${_searchController.text}".',
style: theme.textTheme.titleMedium,
),
),
),
);
}
return RefreshIndicator(
onRefresh: _refreshData,
child: ListView.separated(
padding: EdgeInsets.zero,
itemCount: _filteredList.length,
itemBuilder: (_, index) => _buildBankItem(
context: context,
ref: ref,
bank: _filteredList[index],
),
separatorBuilder: (_, __) => const Divider(
color: kLineColor,
height: 1,
),
),
);
},
error: (err, stack) => Center(child: Text('Failed to load bank accounts: $err')),
loading: () => const Center(child: CircularProgressIndicator()),
),
bottomNavigationBar: permissionService.hasPermission('bank_create_permit')
? Padding(
padding: const EdgeInsets.all(16),
child: Row(
spacing: 16,
children: [
Expanded(
child: OutlinedButton(
onPressed: () => _depositPopUp(context),
child: Text(_lang.deposit),
),
),
Expanded(
child: ElevatedButton.icon(
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const AddEditNewBank(),
),
),
icon: const Icon(Icons.add, color: Colors.white),
label: Text(_lang.addBank),
),
)
],
),
)
: null,
);
}
//------Deposit/Withdraw Popup-------------------
void _depositPopUp(BuildContext context) {
final _lang = l.S.of(context);
showModalBottomSheet(
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
builder: (context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Align(
alignment: Alignment.topRight,
child: InkWell(
onTap: () => Navigator.pop(context),
child: Icon(
Icons.close,
color: kPeraColor,
),
),
),
ListTile(
contentPadding: EdgeInsets.zero,
visualDensity: VisualDensity(vertical: -2, horizontal: -2),
leading: SvgPicture.asset(
'assets/bank.svg',
height: 24,
width: 24,
),
title: Text(_lang.bankToBankTransfer),
onTap: () {
Navigator.pop(context);
Navigator.push(
context,
MaterialPageRoute(builder: (_) => BankToBankTransferScreen()),
);
},
),
ListTile(
contentPadding: EdgeInsets.zero,
visualDensity: VisualDensity(vertical: -2, horizontal: -2),
leading: SvgPicture.asset(
'assets/bank_cash.svg',
height: 24,
width: 24,
),
title: Text(_lang.bankToCashTransfer),
onTap: () {
Navigator.pop(context);
Navigator.push(
context,
MaterialPageRoute(builder: (_) => BankToCashTransferScreen()),
);
},
),
ListTile(
contentPadding: EdgeInsets.zero,
visualDensity: VisualDensity(vertical: -2, horizontal: -2),
leading: SvgPicture.asset(
'assets/bank_adjust.svg',
height: 24,
width: 24,
),
title: Text(_lang.adjustBankBalance),
onTap: () {
Navigator.pop(context);
Navigator.push(
context,
MaterialPageRoute(builder: (_) => AdjustBankBalanceScreen()),
);
},
),
const SizedBox(height: 12),
],
),
);
},
);
}
// --- List Item Builder ---
Widget _buildBankItem({
required BuildContext context,
required WidgetRef ref,
required BankData bank,
}) {
final theme = Theme.of(context);
final _lang = l.S.of(context);
final bankMeta = bank.meta;
final balanceDisplay = '$currency${bank.balance?.toStringAsFixed(2) ?? '0.00'}';
final accountName = bank.name ?? 'N/A';
final bankName = bankMeta?.bankName ?? 'N/A Bank';
return InkWell(
onTap: () => viewModalSheet(
context: context,
item: {
_lang.accountName: accountName,
_lang.accountNumber: bankMeta?.accountNumber ?? 'N/A',
_lang.bankName: bankName,
_lang.holderName: bankMeta?.accountHolder ?? 'N/A',
_lang.openingDate: _formatDateForDisplay(bank.openingDate),
},
descriptionTitle: '${_lang.currentBalance}:',
description: balanceDisplay,
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ListTile(
contentPadding: EdgeInsets.zero,
visualDensity: VisualDensity(horizontal: -4),
title: Row(
children: [
Text(
accountName,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w500,
),
),
const Spacer(),
Text(
balanceDisplay,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w500,
color: kSuccessColor,
),
)
],
),
subtitle: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
bankName,
style: theme.textTheme.bodyMedium?.copyWith(
color: kGrey6,
),
),
Text(
bankMeta?.accountNumber ?? 'N/A',
style: theme.textTheme.bodyMedium?.copyWith(
color: kGrey6,
),
),
],
),
trailing: _buildActionButtons(context, ref, bank),
),
],
),
),
);
}
Widget _buildTimeColumn({
required String time,
required String label,
required ThemeData theme,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
time,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 6),
Text(
label,
style: theme.textTheme.bodyMedium?.copyWith(
color: kNeutral800,
),
),
],
);
}
Widget _buildActionButtons(BuildContext context, WidgetRef ref, BankData bank) {
final _theme = Theme.of(context);
final _lang = l.S.of(context);
final bankMeta = bank.meta;
final balanceDisplay = '$currency${bank.balance?.toStringAsFixed(2) ?? '0.00'}';
final accountName = bank.name ?? 'N/A';
final bankName = bankMeta?.bankName ?? 'N/A Bank';
final permissionService = PermissionService(ref);
return SizedBox(
width: 20,
child: PopupMenuButton<String>(
padding: EdgeInsets.zero,
onSelected: (value) {
if (bank.id == null) return;
if (value == 'view') {
if (!permissionService.hasPermission('bank_view_permit')) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(_lang.permissionDeniedToViewBank)));
return;
}
viewModalSheet(
context: context,
item: {
_lang.accountName: accountName,
_lang.accountNumber: bankMeta?.accountNumber ?? 'N/A',
_lang.bankName: bankName,
_lang.holderName: bankMeta?.accountHolder ?? 'N/A',
_lang.openingDate: _formatDateForDisplay(bank.openingDate),
},
descriptionTitle: '${_lang.currentBalance}:',
description: balanceDisplay,
);
} else if (value == 'edit') {
if (!permissionService.hasPermission('bank_update_permit')) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(_lang.permissionDeniedToUpdateBank)));
return;
}
_navigateToEdit(bank);
} else if (value == 'transactions') {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BankTransactionHistoryScreen(
accountName: bank.name ?? '',
accountNumber: '',
bankId: bank.id ?? 0,
currentBalance: bank.balance ?? 0,
bank: bank,
),
),
);
} else if (value == 'delete') {
if (!permissionService.hasPermission('bank_delete_permit')) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(_lang.permissionDeniedToDeleteBank)));
return;
}
_showDeleteConfirmationDialog(bank.id!, bank.name ?? _lang.bankAccounts);
}
},
itemBuilder: (context) => [
PopupMenuItem(
value: 'view',
child: Row(
spacing: 8,
children: [
HugeIcon(
icon: HugeIcons.strokeRoundedView,
color: kPeraColor,
size: 20,
),
Text(
_lang.view,
style: _theme.textTheme.bodyLarge?.copyWith(
color: kPeraColor,
),
),
],
)),
PopupMenuItem(
value: 'transactions',
child: Row(
spacing: 8,
children: [
HugeIcon(
icon: HugeIcons.strokeRoundedMoneyExchange02,
color: kPeraColor,
size: 20,
),
Text(
_lang.transactions,
style: _theme.textTheme.bodyLarge?.copyWith(
color: kPeraColor,
),
),
],
)),
PopupMenuItem(
value: 'edit',
child: Row(
spacing: 8,
children: [
HugeIcon(
icon: HugeIcons.strokeRoundedPencilEdit02,
color: kPeraColor,
size: 20,
),
Text(
_lang.edit,
style: _theme.textTheme.bodyLarge?.copyWith(
color: kPeraColor,
),
),
],
)),
PopupMenuItem(
value: 'delete',
child: Row(
spacing: 8,
children: [
HugeIcon(
icon: HugeIcons.strokeRoundedDelete03,
color: kPeraColor,
size: 20,
),
Text(
_lang.delete,
style: _theme.textTheme.bodyLarge?.copyWith(
color: kPeraColor,
),
),
],
)),
],
icon: const Icon(
Icons.more_vert,
color: kPeraColor,
),
),
);
}
}

View File

@@ -0,0 +1,477 @@
// File: bank_transaction_history_screen.dart (Final Fixed Code)
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';
// --- Local Imports ---
import 'package:mobile_pos/constant.dart';
import 'package:mobile_pos/currency.dart';
import 'package:mobile_pos/Screens/hrm/widgets/model_bottom_sheet.dart';
import 'package:mobile_pos/generated/l10n.dart' as l;
// --- Data Layer Imports ---
import 'package:mobile_pos/Screens/cash%20and%20bank/bank%20account/provider/bank_account_provider.dart';
import 'package:mobile_pos/Screens/cash%20and%20bank/bank%20account/provider/bank_transfers_history_provider.dart';
import '../../hrm/widgets/deleteing_alart_dialog.dart';
import '../adjust%20bank%20balance/adjust_bank_balance_screen.dart';
import '../bank%20to%20cash%20transfer/bank_to_cash_transfer.dart';
import '../bank%20to%20bank%20transfer/bank_to_bank_transfer_screen.dart';
import '../bank%20to%20bank%20transfer/repo/bank_to_bank_transfar_repo.dart';
import '../widgets/cheques_filter_search.dart'; // Reusable Filter Widget
import 'model/bank_account_list_model.dart';
import 'model/bank_transfer_history_model.dart';
// 🔔 Filter State Model (Must match the data returned by ChequesFilterSearch)
class BankFilterState {
final String searchQuery;
final DateTime? fromDate;
final DateTime? toDate;
BankFilterState({
required this.searchQuery,
this.fromDate,
this.toDate,
});
}
class BankTransactionHistoryScreen extends ConsumerStatefulWidget {
final num bankId;
final String accountName;
final String accountNumber;
final num currentBalance;
final BankData bank;
const BankTransactionHistoryScreen({
super.key,
required this.bankId,
required this.accountName,
required this.accountNumber,
required this.currentBalance,
required this.bank,
});
@override
ConsumerState<BankTransactionHistoryScreen> createState() => _BankTransactionHistoryScreenState();
}
class _BankTransactionHistoryScreenState extends ConsumerState<BankTransactionHistoryScreen> {
// Local states to hold filter values from the child widget
String _currentSearchQuery = '';
DateTime? _currentFromDate;
DateTime? _currentToDate;
final DateFormat _displayFormat = DateFormat('dd/MM/yyyy');
final DateFormat _apiFormat = DateFormat('yyyy-MM-dd');
final List<String> _timeFilterOptions = [
'Today',
'Yesterday',
'Last 7 Days',
'Last 30 Days',
'Current Month',
'Last Month',
'Current Year',
'Custom Date'
];
// FIX: Helper to initialize filter dates
void _updateInitialDateRange() {
final now = DateTime.now();
// Default to Current Year
_currentFromDate = DateTime(now.year, 1, 1);
// End of today for inclusive filtering
_currentToDate = DateTime(now.year, now.month, now.day, 23, 59, 59);
}
@override
void initState() {
super.initState();
// 🔔 FIX: Initialize filter date range right away
_updateInitialDateRange();
}
@override
void dispose() {
super.dispose();
}
String _formatDate(String? date) {
if (date == null) return 'N/A';
try {
return DateFormat('dd MMM, yyyy').format(DateTime.parse(date));
} catch (_) {
return date;
}
}
// --- DELETE Logic ---
Future<void> _confirmAndDeleteTransaction(TransactionData transaction) async {
final transactionId = transaction.id;
if (transactionId == null) return;
final confirmed = await showDeleteConfirmationDialog(
context: context,
itemName: 'transaction',
);
if (confirmed == true) {
final repo = BankTransactionRepo();
await repo.deleteBankTransaction(
ref: ref,
context: context,
transactionId: transactionId,
);
}
}
// --- Filter Callback Handler ---
void _handleFilterChange(BankFilterState filterState) {
setState(() {
_currentSearchQuery = filterState.searchQuery;
_currentFromDate = filterState.fromDate;
_currentToDate = filterState.toDate;
});
}
// --- LOCAL FILTERING FUNCTION (with robust date checks) ---
List<TransactionData> _filterTransactionsLocally(List<TransactionData> transactions) {
// 1. Filter by Date Range
Iterable<TransactionData> dateFiltered = transactions.where((t) {
if (_currentFromDate == null && _currentToDate == null) return true;
if (t.date == null) return false;
try {
final transactionDate = DateTime.parse(t.date!);
final start = _currentFromDate;
final end = _currentToDate;
bool afterStart = start == null || transactionDate.isAfter(start) || transactionDate.isAtSameMomentAs(start);
bool beforeEnd = end == null || transactionDate.isBefore(end) || transactionDate.isAtSameMomentAs(end);
return afterStart && beforeEnd;
} catch (e) {
return false;
}
});
// 2. Filter by Search Query
final query = _currentSearchQuery.toLowerCase();
if (query.isEmpty) {
return dateFiltered.toList();
}
return dateFiltered.where((t) {
return (t.transactionType ?? '').toLowerCase().contains(query) ||
(t.user?.name ?? '').toLowerCase().contains(query) ||
(t.amount?.toString() ?? '').contains(query) ||
(t.invoiceNo ?? '').toLowerCase().contains(query);
}).toList();
}
// --- END LOCAL FILTERING FUNCTION ---
// --- Core Logic Helpers (Unchanged) ---
String _getBankNameById(num? id, List<BankData> banks) {
if (id == null) return 'Cash/System';
final bank = banks.firstWhere((b) => b.id == id, orElse: () => BankData(name: 'Bank ID $id', id: id));
return bank.name ?? 'Bank ID $id';
}
String _getListName(TransactionData t, List<BankData> banks) {
final nameFromUser = t.user?.name ?? 'System';
if (t.transactionType == 'bank_to_bank') {
if (t.fromBankId != widget.bankId) {
return 'From: ${_getBankNameById(t.fromBankId, banks)}';
} else if (t.toBankId != widget.bankId) {
return 'To: ${_getBankNameById(t.toBankId, banks)}';
}
return 'Internal Transfer';
} else if (t.transactionType == 'bank_to_cash') {
return 'To: Cash';
} else if (t.transactionType == 'adjust_bank') {
return t.type == 'credit' ? 'Adjustment (Credit)' : 'Adjustment (Debit)';
}
return nameFromUser;
}
Map<String, dynamic> _getAmountDetails(TransactionData t) {
bool isOutgoing = false;
if (t.transactionType == 'adjust_bank') {
isOutgoing = t.type == 'debit';
} else if (t.transactionType == 'bank_to_bank' || t.transactionType == 'bank_to_cash') {
isOutgoing = t.fromBankId == widget.bankId;
}
final color = isOutgoing ? Colors.red.shade700 : Colors.green.shade700;
final sign = isOutgoing ? '-' : '+';
return {'sign': sign, 'color': color};
}
// --- UI Builders ---
Widget _buildBalanceCard(ThemeData theme) {
final _lang = l.S.of(context);
return Padding(
padding: const EdgeInsets.all(16.0),
child: Container(
height: 77,
width: double.infinity,
color: kSuccessColor.withValues(alpha: 0.1),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'$currency${widget.currentBalance.toStringAsFixed(2)}',
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w600,
),
),
Text(
_lang.balance,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w500,
color: kSubPeraColor,
),
),
],
),
),
);
}
Widget _buildActionMenu(TransactionData transaction, List<BankData> allBanks) {
// Helper to compile details for the view modal
Map<String, String> _compileDetails() {
final details = <String, String>{};
details['Transaction Type'] = (transaction.transactionType ?? 'N/A').replaceAll('_', ' ').toUpperCase();
details['Date'] = _formatDate(transaction.date);
details['Amount'] = '$currency${transaction.amount?.toStringAsFixed(2) ?? '0.00'}';
details['User'] = transaction.user?.name ?? 'System';
details['Invoice No'] = transaction.invoiceNo ?? 'N/A';
details['Note'] = transaction.note ?? 'No Note';
if (transaction.transactionType == 'bank_to_bank') {
details['From Account'] = _getBankNameById(transaction.fromBankId, allBanks);
details['To Account'] = _getBankNameById(transaction.toBankId, allBanks);
} else if (transaction.transactionType == 'bank_to_cash') {
details['From Account'] = _getBankNameById(transaction.fromBankId, allBanks);
details['To'] = 'Cash';
}
return details;
}
return PopupMenuButton<String>(
onSelected: (value) {
if (transaction.id == null) return;
if (value == 'view') {
// *** VIEW IMPLEMENTATION ***
viewModalSheet(
context: context,
item: _compileDetails(),
descriptionTitle: '${l.S.of(context).description}:',
description: transaction.note ?? 'N/A',
);
} else if (value == 'edit') {
// --- Determine the Destination Screen based on transaction_type ---
Widget destinationScreen;
switch (transaction.transactionType) {
case 'bank_to_bank':
destinationScreen = BankToBankTransferScreen(transaction: transaction);
break;
case 'bank_to_cash':
destinationScreen = BankToCashTransferScreen(transaction: transaction);
break;
case 'adjust_bank':
destinationScreen = AdjustBankBalanceScreen(
transaction: transaction,
);
break;
default:
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(l.S.of(context).canNotEditThisTransactionType)));
return;
}
Navigator.push(context, MaterialPageRoute(builder: (context) => destinationScreen));
} else if (value == 'delete') {
// *** DELETE IMPLEMENTATION - Call the confirmation dialog ***
_confirmAndDeleteTransaction(transaction);
}
},
itemBuilder: (context) => [
PopupMenuItem(value: 'view', child: Text(l.S.of(context).view)),
PopupMenuItem(value: 'edit', child: Text(l.S.of(context).edit)),
PopupMenuItem(value: 'delete', child: Text(l.S.of(context).delete, style: TextStyle(color: Colors.red))),
],
icon: const Icon(Icons.more_vert, color: kNeutral800),
);
}
@override
Widget build(BuildContext context) {
final _lang = l.S.of(context);
final bankMeta = widget.bank.meta;
final bankName = bankMeta?.bankName ?? 'N/A ${_lang.bank}';
final theme = Theme.of(context);
final historyAsync = ref.watch(bankTransactionHistoryProvider(widget.bankId));
final banksListAsync = ref.watch(bankListProvider);
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: ListTile(
contentPadding: EdgeInsets.zero,
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
title: Text(
widget.accountName,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
),
),
subtitle: Text(
bankName,
style: theme.textTheme.bodyMedium?.copyWith(
color: kGrey6,
),
),
),
),
body: RefreshIndicator(
onRefresh: () => ref.refresh(bankTransactionHistoryProvider(widget.bankId).future),
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// 1. Balance and Account Info Card
_buildBalanceCard(theme),
// // 2. Filters and Search (Using Reusable Widget)
// ChequesFilterSearch(
// displayFormat: _displayFormat, // Use local display format
// timeOptions: _timeFilterOptions,
// onFilterChanged: (filterState) {
// // Cast the dynamic output to the expected BankFilterState
// _handleFilterChange(filterState as BankFilterState);
// },
// ),
Container(
padding: EdgeInsets.symmetric(horizontal: 16),
height: 42,
width: double.infinity,
color: kBackgroundColor,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
_lang.transactions,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w500,
),
),
Text(
_lang.amount,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w500,
),
),
],
),
),
// 3. Transaction List
banksListAsync.when(
loading: () =>
const Center(child: Padding(padding: EdgeInsets.all(20), child: CircularProgressIndicator())),
error: (e, s) => Center(child: Text('Error loading bank data: ${e.toString()}')),
data: (bankModel) {
final allBanks = bankModel.data ?? []; // List for lookup
return historyAsync.when(
loading: () =>
const Center(child: Padding(padding: EdgeInsets.all(20), child: CircularProgressIndicator())),
error: (err, stack) => Center(child: Text('Error: ${err.toString()}')),
data: (model) {
final allTransactions = model.data ?? [];
// Apply local date and search filtering
final filteredTransactions = _filterTransactionsLocally(allTransactions);
if (filteredTransactions.isEmpty) {
return Center(
child: Padding(
padding: EdgeInsets.all(40),
child: Text(
_lang.noTransactionFoundForThisFilter,
),
),
);
}
return ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: filteredTransactions.length,
separatorBuilder: (_, __) => const Divider(color: kLineColor, height: 1),
itemBuilder: (_, index) {
final transaction = filteredTransactions[index];
final amountDetails = _getAmountDetails(transaction);
return ListTile(
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
(transaction.transactionType ?? 'N/A').replaceAll('_', ' ').toUpperCase(),
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
),
),
Text(
'$currency${transaction.amount?.toStringAsFixed(2) ?? '0.00'}',
style: theme.textTheme.titleMedium?.copyWith(
color: amountDetails['color'],
fontWeight: FontWeight.w600,
),
),
],
),
subtitle: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
_formatDate(transaction.date),
style: theme.textTheme.bodyMedium?.copyWith(
color: kGrey6,
),
),
Text(
transaction.platform.toString(),
style: theme.textTheme.bodyMedium?.copyWith(
color: kGrey6,
),
),
],
),
);
},
);
},
);
}),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,83 @@
// File: bank_account_model.dart
class BankListModel {
BankListModel({this.message, this.data});
BankListModel.fromJson(dynamic json) {
message = json['message'];
if (json['data'] != null) {
data = [];
json['data'].forEach((v) {
data?.add(BankData.fromJson(v));
});
}
}
String? message;
List<BankData>? data;
}
class BankData {
BankData({
this.id,
this.name, // Account Display Name
this.meta,
this.showInInvoice,
this.openingDate,
this.openingBalance,
this.balance,
this.status,
});
BankData.fromJson(dynamic json) {
id = json['id'];
name = json['name'];
meta = json['meta'] != null ? BankMeta.fromJson(json['meta']) : null;
showInInvoice = json['show_in_invoice'];
openingDate = json['opening_date'];
openingBalance = json['opening_balance'];
balance = json['balance'];
status = json['status'];
}
num? id;
String? name;
BankMeta? meta;
num? showInInvoice;
String? openingDate;
num? openingBalance;
num? balance;
num? status;
}
class BankMeta {
BankMeta({
this.accountNumber,
this.ifscCode,
this.upiId,
this.bankName,
this.accountHolder,
});
BankMeta.fromJson(dynamic json) {
accountNumber = json['account_number'];
ifscCode = json['ifsc_code'];
upiId = json['upi_id'];
bankName = json['bank_name'];
accountHolder = json['account_holder'];
}
String? accountNumber;
String? ifscCode;
String? upiId;
String? bankName;
String? accountHolder;
// Helper method to convert back to API format (meta fields are sent as separate inputs)
Map<String, dynamic> toApiMetaJson() {
return {
'account_number': accountNumber,
'ifsc_code': ifscCode,
'upi_id': upiId,
'bank_name': bankName,
'account_holder': accountHolder,
};
}
}

View File

@@ -0,0 +1,72 @@
// File: bank_transaction_history_model.dart (Updated to full structure)
class TransactionHistoryListModel {
TransactionHistoryListModel({this.message, this.data});
TransactionHistoryListModel.fromJson(dynamic json) {
message = json['message'];
if (json['data'] != null) {
data = [];
json['data'].forEach((v) {
data?.add(TransactionData.fromJson(v));
});
}
}
String? message;
List<TransactionData>? data;
}
class TransactionData {
TransactionData({
this.id,
this.platform,
this.transactionType,
this.type, // credit / debit / transfer
this.amount,
this.date,
this.fromBankId,
this.toBankId,
this.invoiceNo,
this.image,
this.note,
this.user,
// Add nested bank models if API provides bank objects, otherwise we only use IDs
});
TransactionData.fromJson(dynamic json) {
id = json['id'];
platform = json['platform'];
transactionType = json['transaction_type'];
type = json['type'];
amount = json['amount'];
date = json['date'];
fromBankId = json['from_bank'];
toBankId = json['to_bank'];
invoiceNo = json['invoice_no'];
image = json['image'];
note = json['note'];
user = json['user'] != null ? TransactionUser.fromJson(json['user']) : null;
}
num? id;
String? platform;
String? transactionType;
String? type;
num? amount;
String? date;
num? fromBankId;
num? toBankId;
String? invoiceNo;
String? image;
String? note;
TransactionUser? user;
}
class TransactionUser {
TransactionUser({this.id, this.name});
TransactionUser.fromJson(dynamic json) {
id = json['id'];
name = json['name'];
}
num? id;
String? name;
}

View File

@@ -0,0 +1,7 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../model/bank_account_list_model.dart';
import '../repo/bank_account_repo.dart';
final repo = BankRepo();
final bankListProvider = FutureProvider.autoDispose<BankListModel>((ref) => repo.fetchAllBanks());

View File

@@ -0,0 +1,14 @@
// File: bank_transaction_history_provider.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../model/bank_transfer_history_model.dart';
import '../repo/bank_transfer_history_repo.dart';
final repo = BankTransactionHistoryRepo();
// Provider that takes bankId as a parameter (Family Provider)
final bankTransactionHistoryProvider = FutureProvider.autoDispose.family<TransactionHistoryListModel, num>((ref, bankId) {
// Pass the bankId to the repository
return repo.fetchHistory(bankId: bankId);
});

View File

@@ -0,0 +1,206 @@
// File: bank_repo.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:http/http.dart' as http;
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../Const/api_config.dart';
import '../../../../http_client/custome_http_client.dart';
import '../../../../http_client/customer_http_client_get.dart';
import '../model/bank_account_list_model.dart';
import '../provider/bank_account_provider.dart';
class BankRepo {
static const String _endpoint = '/banks';
///---------------- FETCH ALL BANKS (GET) ----------------///
Future<BankListModel> fetchAllBanks() async {
CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client());
final uri = Uri.parse('${APIConfig.url}$_endpoint');
final response = await clientGet.get(url: uri);
if (response.statusCode == 200) {
final parsedData = jsonDecode(response.body);
return BankListModel.fromJson(parsedData);
} else {
throw Exception('Failed to fetch bank list. Status: ${response.statusCode}');
}
}
// Helper to construct API body from core data and meta data
Map<String, dynamic> _buildBody({
required String name,
required num openingBalance,
required String openingDate,
required num showInInvoice,
required BankMeta meta,
num? branchId,
}) {
// NOTE: API requires meta fields to be nested meta[key] in form-data.
// When sending JSON, we flatten the meta data and prefix it.
// Convert meta to flat fields with 'meta[key]' prefix
final metaFields = meta.toApiMetaJson().map((key, value) => MapEntry(key, value));
return {
'name': name,
'branch_id': branchId, // Assuming branchId is managed separately or is nullable
'opening_balance': openingBalance,
'opening_date': openingDate, // YYYY-MM-DD format
'show_in_invoice': showInInvoice,
...metaFields // Flattened meta fields
};
}
///---------------- CREATE BANK (POST) ----------------///
Future<void> createBank({
required WidgetRef ref,
required BuildContext context,
required String name,
required num openingBalance,
required String openingDate,
required num showInInvoice,
required BankMeta meta,
}) async {
final uri = Uri.parse('${APIConfig.url}$_endpoint');
final requestBody = jsonEncode(_buildBody(
name: name,
openingBalance: openingBalance,
openingDate: openingDate,
showInInvoice: showInInvoice,
meta: meta,
));
CustomHttpClient customHttpClient = CustomHttpClient(client: http.Client(), context: context, ref: ref);
try {
EasyLoading.show(status: 'Creating Bank...');
var responseData = await customHttpClient.post(
url: uri,
addContentTypeInHeader: true,
body: requestBody,
permission: 'bank_create_permit', // Assuming permit exists
);
final parsedData = jsonDecode(responseData.body);
EasyLoading.dismiss();
print('Add Bank Response: $parsedData');
if (responseData.statusCode == 200 || responseData.statusCode == 201) {
ref.invalidate(bankListProvider);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(parsedData['message'] ?? 'Bank Account created successfully')),
);
Navigator.pop(context);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Creation failed: ${parsedData['message'] ?? 'Unknown error'}')),
);
}
} catch (error) {
EasyLoading.dismiss();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('An error occurred: $error')),
);
}
}
///---------------- UPDATE BANK (PUT) ----------------///
Future<void> updateBank({
required WidgetRef ref,
required BuildContext context,
required num id,
required String name,
required num openingBalance,
required String openingDate,
required num showInInvoice,
required BankMeta meta,
}) async {
final uri = Uri.parse('${APIConfig.url}$_endpoint/$id');
final baseBody = _buildBody(
name: name,
openingBalance: openingBalance,
openingDate: openingDate,
showInInvoice: showInInvoice,
meta: meta,
);
// Add PUT method override
baseBody['_method'] = 'put';
final requestBody = jsonEncode(baseBody);
CustomHttpClient customHttpClient = CustomHttpClient(client: http.Client(), context: context, ref: ref);
try {
EasyLoading.show(status: 'Updating Bank...');
var responseData = await customHttpClient.post(
url: uri,
addContentTypeInHeader: true,
body: requestBody,
permission: 'bank_update_permit', // Assuming permit exists
);
final parsedData = jsonDecode(responseData.body);
EasyLoading.dismiss();
if (responseData.statusCode == 200) {
ref.invalidate(bankListProvider);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(parsedData['message'] ?? 'Bank Account updated successfully')),
);
Navigator.pop(context);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Update failed: ${parsedData['message'] ?? 'Unknown error'}')),
);
}
} catch (error) {
EasyLoading.dismiss();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('An error occurred: $error')),
);
}
}
///---------------- DELETE BANK ----------------///
Future<bool> deleteBank({
required num id,
required BuildContext context,
required WidgetRef ref,
}) async {
try {
EasyLoading.show(status: 'Deleting...');
final url = Uri.parse('${APIConfig.url}$_endpoint/$id');
CustomHttpClient customHttpClient = CustomHttpClient(ref: ref, context: context, client: http.Client());
final response = await customHttpClient.delete(
url: url,
permission: 'bank_delete_permit', // Assuming permit exists
);
EasyLoading.dismiss();
if (response.statusCode == 200) {
ref.invalidate(bankListProvider);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Bank Account deleted successfully')),
);
return true;
} else {
final parsedData = jsonDecode(response.body);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Deletion failed: ${parsedData['message'] ?? 'Unknown error'}')),
);
return false;
}
} catch (error) {
EasyLoading.dismiss();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('An error occurred during deletion: $error')),
);
return false;
}
}
}

View File

@@ -0,0 +1,50 @@
// File: bank_transaction_history_repo.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:http/http.dart' as http;
// --- Local Imports ---
import '../../../../Const/api_config.dart';
import '../../../../http_client/customer_http_client_get.dart';
import '../model/bank_transfer_history_model.dart';
class BankTransactionHistoryRepo {
static const String _endpoint = '/bank-transactions';
// NOTE: This API must accept bankId and optional filters (like time range)
Future<TransactionHistoryListModel> fetchHistory({
required num bankId,
String? timeFilter, // e.g., 'Today', 'Current Year'
String? transactionTypeFilter,
}) async {
CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client());
// Construct query parameters
final Map<String, dynamic> queryParams = {
'bank_id': bankId.toString(),
// Add other filters as API requires (e.g., 'filter_time': timeFilter)
};
final uri = Uri.parse('${APIConfig.url}$_endpoint').replace(queryParameters: queryParams);
final response = await clientGet.get(url: uri);
if (response.statusCode == 200) {
final parsedData = jsonDecode(response.body);
return TransactionHistoryListModel.fromJson(parsedData);
} else {
throw Exception('Failed to fetch transaction history. Status: ${response.statusCode}');
}
}
// NOTE: You would add methods here for deleting and updating individual transactions
// if required by the action menu.
// --- Deletion Placeholder ---
Future<void> deleteTransaction(num transactionId, BuildContext context, WidgetRef ref) async {
// ... Implementation using CustomHttpClient().delete() ...
// ref.invalidate(bankTransactionHistoryProvider(bankId));
}
}