first commit
This commit is contained in:
757
lib/Screens/all_transaction/all_transaction.dart
Normal file
757
lib/Screens/all_transaction/all_transaction.dart
Normal file
@@ -0,0 +1,757 @@
|
||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:hugeicons/hugeicons.dart';
|
||||
import 'package:iconly/iconly.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:mobile_pos/Screens/Customers/Provider/customer_provider.dart';
|
||||
import 'package:mobile_pos/Screens/all_transaction/provider/transacton_provider.dart';
|
||||
import 'package:mobile_pos/core/theme/_app_colors.dart';
|
||||
import 'package:mobile_pos/generated/l10n.dart' as l;
|
||||
import '../../../GlobalComponents/glonal_popup.dart';
|
||||
import '../../../Provider/profile_provider.dart';
|
||||
import '../../../constant.dart';
|
||||
import '../../currency.dart';
|
||||
import '../../pdf_report/transactions/all_transaction_report_pdf.dart';
|
||||
|
||||
class AllTransactionReport extends ConsumerStatefulWidget {
|
||||
const AllTransactionReport({super.key});
|
||||
|
||||
@override
|
||||
SalesReportScreenState createState() => SalesReportScreenState();
|
||||
}
|
||||
|
||||
class SalesReportScreenState extends ConsumerState<AllTransactionReport> {
|
||||
final TextEditingController fromDateController = TextEditingController();
|
||||
final TextEditingController toDateController = TextEditingController();
|
||||
|
||||
final Map<String, String> dateOptions = {
|
||||
'today': l.S.current.today,
|
||||
'yesterday': l.S.current.yesterday,
|
||||
'last_seven_days': l.S.current.last7Days,
|
||||
'last_thirty_days': l.S.current.last30Days,
|
||||
'current_month': l.S.current.currentMonth,
|
||||
'last_month': l.S.current.lastMonth,
|
||||
'current_year': l.S.current.currentYear,
|
||||
'custom_date': l.S.current.customerDate,
|
||||
};
|
||||
|
||||
String selectedTime = 'today';
|
||||
|
||||
final Map<String, String> transactionType = {
|
||||
'all_transaction': l.S.current.allTransaction,
|
||||
'sale': l.S.current.sales,
|
||||
'purchase': l.S.current.purchase,
|
||||
'due_collect': l.S.current.dueCollection,
|
||||
'income': l.S.current.income,
|
||||
'expense': l.S.current.expense,
|
||||
'due_pay': l.S.current.duePay,
|
||||
'bank': l.S.current.bank,
|
||||
'cash': l.S.current.cash,
|
||||
'cheque': l.S.current.cheque,
|
||||
};
|
||||
|
||||
String selectedTransaction = 'all_transaction';
|
||||
|
||||
String? selectedParty;
|
||||
|
||||
bool _isRefreshing = false;
|
||||
bool _showCustomDatePickers = false;
|
||||
|
||||
DateTime? fromDate;
|
||||
DateTime? toDate;
|
||||
String searchCustomer = '';
|
||||
|
||||
TransactionFilteredModel _getFilter() {
|
||||
return TransactionFilteredModel(
|
||||
duration: selectedTime,
|
||||
fromDate: fromDate == null ? null : DateFormat('yyyy-MM-dd').format(fromDate!),
|
||||
toDate: toDate == null ? null : DateFormat('yyyy-MM-dd').format(toDate!),
|
||||
transactionType: selectedTransaction == 'all_transaction' ? null : selectedTransaction,
|
||||
party: selectedParty,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _selectDate({
|
||||
required BuildContext context,
|
||||
required bool isFrom,
|
||||
}) async {
|
||||
final DateTime? picked = await showDatePicker(
|
||||
context: context,
|
||||
firstDate: DateTime(2021),
|
||||
lastDate: DateTime.now(),
|
||||
initialDate: isFrom ? (fromDate ?? DateTime.now()) : (toDate ?? DateTime.now()),
|
||||
);
|
||||
|
||||
if (picked != null) {
|
||||
setState(() {
|
||||
if (isFrom) {
|
||||
fromDate = picked;
|
||||
fromDateController.text = DateFormat('yyyy-MM-dd').format(picked);
|
||||
} else {
|
||||
toDate = picked;
|
||||
toDateController.text = DateFormat('yyyy-MM-dd').format(picked);
|
||||
}
|
||||
});
|
||||
|
||||
if (fromDate != null && toDate != null) {
|
||||
_refreshFilteredProvider();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _refreshFilteredProvider() async {
|
||||
if (_isRefreshing) return;
|
||||
setState(() {
|
||||
_isRefreshing = true;
|
||||
});
|
||||
|
||||
try {
|
||||
final filter = _getFilter();
|
||||
// Force refresh by invalidating the provider
|
||||
ref.invalidate(filteredTransactionProvider(filter));
|
||||
// Wait for the new data
|
||||
await ref.refresh(filteredTransactionProvider(filter).future);
|
||||
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
} catch (e) {
|
||||
print('Refresh error: $e');
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isRefreshing = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
fromDateController.dispose();
|
||||
toDateController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _updateDateUI(DateTime? from, DateTime? to) {
|
||||
setState(() {
|
||||
fromDate = from;
|
||||
toDate = to;
|
||||
|
||||
fromDateController.text = from != null ? DateFormat('yyyy-MM-dd').format(from) : '';
|
||||
toDateController.text = to != null ? DateFormat('yyyy-MM-dd').format(to) : '';
|
||||
});
|
||||
}
|
||||
|
||||
void _setDateRangeFromDropdown(String value) {
|
||||
final now = DateTime.now();
|
||||
|
||||
setState(() {
|
||||
selectedTime = value;
|
||||
_showCustomDatePickers = value == 'custom_date';
|
||||
});
|
||||
|
||||
switch (value) {
|
||||
case 'today':
|
||||
_updateDateUI(now, now);
|
||||
break;
|
||||
|
||||
case 'yesterday':
|
||||
final y = now.subtract(const Duration(days: 1));
|
||||
_updateDateUI(y, y);
|
||||
break;
|
||||
|
||||
case 'last_seven_days':
|
||||
_updateDateUI(
|
||||
now.subtract(const Duration(days: 6)),
|
||||
now,
|
||||
);
|
||||
break;
|
||||
|
||||
case 'last_thirty_days':
|
||||
_updateDateUI(
|
||||
now.subtract(const Duration(days: 29)),
|
||||
now,
|
||||
);
|
||||
break;
|
||||
|
||||
case 'current_month':
|
||||
_updateDateUI(
|
||||
DateTime(now.year, now.month, 1),
|
||||
now,
|
||||
);
|
||||
break;
|
||||
|
||||
case 'last_month':
|
||||
final first = DateTime(now.year, now.month - 1, 1);
|
||||
final last = DateTime(now.year, now.month, 0);
|
||||
_updateDateUI(first, last);
|
||||
break;
|
||||
|
||||
case 'current_year':
|
||||
_updateDateUI(
|
||||
DateTime(now.year, 1, 1),
|
||||
now,
|
||||
);
|
||||
break;
|
||||
|
||||
case 'custom_date':
|
||||
// Clear dates for custom selection
|
||||
_updateDateUI(null, null);
|
||||
return; // Don't refresh, user will select dates manually
|
||||
}
|
||||
|
||||
// Refresh data after setting dates (except for custom)
|
||||
_refreshFilteredProvider();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
final now = DateTime.now();
|
||||
|
||||
// Set initial From and To date = TODAY
|
||||
fromDate = now;
|
||||
toDate = now;
|
||||
|
||||
fromDateController.text = DateFormat('yyyy-MM-dd').format(now);
|
||||
toDateController.text = DateFormat('yyyy-MM-dd').format(now);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final _theme = Theme.of(context);
|
||||
return Consumer(
|
||||
builder: (context, ref, __) {
|
||||
final _lang = l.S.of(context);
|
||||
final filter = _getFilter();
|
||||
final providerData = ref.watch(filteredTransactionProvider(filter));
|
||||
final partyData = ref.watch(partiesProvider);
|
||||
final personalData = ref.watch(businessInfoProvider);
|
||||
|
||||
return GlobalPopup(
|
||||
child: Scaffold(
|
||||
backgroundColor: kWhite,
|
||||
appBar: AppBar(
|
||||
title: Text(_lang.allTransaction),
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(120),
|
||||
child: Column(
|
||||
children: [
|
||||
Divider(thickness: 1, color: kBottomBorder, height: 1),
|
||||
//Date Time
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(IconlyLight.calendar, color: kPeraColor, size: 20),
|
||||
const SizedBox(width: 3),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
if (_showCustomDatePickers) {
|
||||
_selectDate(context: context, isFrom: true);
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
fromDate != null ? DateFormat('dd MMM yyyy').format(fromDate!) : _lang.from,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
_lang.to,
|
||||
style: _theme.textTheme.titleSmall,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Flexible(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (_showCustomDatePickers) {
|
||||
_selectDate(context: context, isFrom: false);
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
toDate != null ? DateFormat('dd MMM yyyy').format(toDate!) : _lang.to,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
RotatedBox(
|
||||
quarterTurns: 1,
|
||||
child: Container(
|
||||
height: 1,
|
||||
width: 20,
|
||||
color: kSubPeraColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
Expanded(
|
||||
child: DropdownButtonHideUnderline(
|
||||
child: DropdownButton<String>(
|
||||
iconSize: 20,
|
||||
value: selectedTime,
|
||||
isExpanded: true,
|
||||
items: dateOptions.entries.map((entry) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: entry.key,
|
||||
child: Text(
|
||||
entry.value,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: _theme.textTheme.bodyMedium,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (value) {
|
||||
if (value == null) return;
|
||||
_setDateRangeFromDropdown(value);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Divider(thickness: 1, color: kBottomBorder, height: 1),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
child: DropdownButtonFormField2<String>(
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
//
|
||||
),
|
||||
value: selectedTransaction,
|
||||
isExpanded: true,
|
||||
items: transactionType.entries.map((entry) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: entry.key,
|
||||
child: Text(
|
||||
entry.value,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: _theme.textTheme.titleSmall,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (value) {
|
||||
if (value == null) return;
|
||||
|
||||
setState(() => selectedTransaction = value);
|
||||
_refreshFilteredProvider();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: partyData.when(
|
||||
data: (data) {
|
||||
return SizedBox(
|
||||
height: 40,
|
||||
child: DropdownButtonFormField2<String>(
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
//
|
||||
),
|
||||
isExpanded: true,
|
||||
items: [
|
||||
DropdownMenuItem<String>(
|
||||
value: 'all_parties',
|
||||
child: Text(
|
||||
_lang.allParties,
|
||||
style: _theme.textTheme.titleSmall,
|
||||
),
|
||||
),
|
||||
...data.map((entry) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: entry.id?.toString() ?? '',
|
||||
child: Text(
|
||||
entry.name ?? 'Unknown',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: _theme.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
value: selectedParty ?? 'all_parties',
|
||||
onChanged: (value) {
|
||||
if (value == null) return;
|
||||
setState(() => selectedParty = value);
|
||||
_refreshFilteredProvider();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
error: (e, stack) => Center(
|
||||
child: Text(e.toString()),
|
||||
),
|
||||
loading: () => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Divider(thickness: 1, color: kBottomBorder, height: 1),
|
||||
],
|
||||
),
|
||||
),
|
||||
iconTheme: const IconThemeData(color: Colors.black),
|
||||
centerTitle: true,
|
||||
backgroundColor: Colors.white,
|
||||
elevation: 0.0,
|
||||
actions: [
|
||||
personalData.when(
|
||||
data: (business) {
|
||||
return providerData.when(
|
||||
data: (transaction) {
|
||||
return Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
if (transaction.data?.isNotEmpty == true) {
|
||||
generateAllTransactionReportPdf(context, transaction, business, fromDate, toDate);
|
||||
} else {
|
||||
EasyLoading.showError(_lang.listIsEmpty);
|
||||
}
|
||||
},
|
||||
icon: HugeIcon(icon: HugeIcons.strokeRoundedPdf02, color: kSecondayColor),
|
||||
),
|
||||
/*
|
||||
IconButton(
|
||||
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: () {
|
||||
if (!permissionService.hasPermission(Permit.expenseReportsRead.value)) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
backgroundColor: Colors.red,
|
||||
content: Text('You do not have permission to view expense report.'),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (transaction.data?.isNotEmpty == true) {
|
||||
// generateSaleReportExcel(context, transaction, business, fromDate, toDate);
|
||||
} else {
|
||||
EasyLoading.showInfo('No data available for generate pdf');
|
||||
}
|
||||
},
|
||||
icon: SvgPicture.asset('assets/excel.svg'),
|
||||
),
|
||||
*/
|
||||
SizedBox(width: 8),
|
||||
],
|
||||
);
|
||||
},
|
||||
error: (e, stack) => Center(
|
||||
child: Text(e.toString()),
|
||||
),
|
||||
loading: () => Center(
|
||||
child: CircularProgressIndicator(),
|
||||
));
|
||||
},
|
||||
error: (e, stack) => Center(
|
||||
child: Text(e.toString()),
|
||||
),
|
||||
loading: () => Center(
|
||||
child: CircularProgressIndicator(),
|
||||
)),
|
||||
],
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: _refreshFilteredProvider,
|
||||
child: providerData.when(
|
||||
data: (transactions) {
|
||||
final dataList = transactions.data ?? [];
|
||||
|
||||
if (dataList.isEmpty) {
|
||||
return Center(
|
||||
child: Text(_lang.noTransactionFound),
|
||||
);
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
// Overview Containers
|
||||
SizedBox.fromSize(
|
||||
size: Size.fromHeight(100),
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
constraints: const BoxConstraints(minWidth: 170, maxHeight: 80),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xffFAE3FF),
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(8),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"$currency${formatPointNumber(transactions.totalAmount ?? 0)}",
|
||||
style: _theme.textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
_lang.transactions,
|
||||
style: _theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: kPeraColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
Container(
|
||||
constraints: const BoxConstraints(minWidth: 170, maxHeight: 80),
|
||||
decoration: BoxDecoration(
|
||||
color: kSuccessColor.withValues(alpha: 0.15),
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(8),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"$currency${formatPointNumber(transactions.moneyIn ?? 0)}",
|
||||
style: _theme.textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
_lang.moneyIn,
|
||||
style: _theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: kPeraColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
Container(
|
||||
constraints: const BoxConstraints(minWidth: 170, maxHeight: 80),
|
||||
decoration: BoxDecoration(
|
||||
color: DAppColors.kError.withValues(alpha: 0.15),
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(8),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"$currency${formatPointNumber(transactions.moneyOut ?? 0)}",
|
||||
style: _theme.textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
_lang.moneyOut,
|
||||
style: _theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: kPeraColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Header
|
||||
DefaultTextStyle.merge(
|
||||
style: _theme.textTheme.bodyLarge?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xffF7F7F7),
|
||||
border: Border(bottom: Divider.createBorderSide(context)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(flex: 4, child: Text(_lang.name)),
|
||||
Expanded(flex: 3, child: Text(_lang.type, textAlign: TextAlign.center)),
|
||||
Expanded(flex: 2, child: Text(_lang.amount, textAlign: TextAlign.end)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Transactions
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: dataList.length,
|
||||
itemBuilder: (context, index) {
|
||||
final _transaction = dataList[index];
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 6,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: Divider.createBorderSide(context),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
text: "${_transaction.party?.name ?? "N/A"}\n",
|
||||
children: [
|
||||
TextSpan(
|
||||
text: _transaction.date == null
|
||||
? "N/A"
|
||||
: DateFormat("dd MMM yyyy").format(DateTime.parse(_transaction.date!)),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.normal,
|
||||
color: const Color(0xff4B5563),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
style: _theme.textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Text(
|
||||
_transaction.platform?.toTitleCase() ?? "N/A",
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
"$currency${formatPointNumber(_transaction.amount ?? 0, addComma: true)}",
|
||||
textAlign: TextAlign.end,
|
||||
style: TextStyle(
|
||||
color: switch (_transaction.type?.trim().toLowerCase()) {
|
||||
'credit' => Colors.green,
|
||||
'debit' => Colors.red,
|
||||
_ => null,
|
||||
},
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (e, _) {
|
||||
print('Error Found: ${e.toString()}');
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('Error: ${e.toString()}'),
|
||||
const SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
onPressed: _refreshFilteredProvider,
|
||||
child: Text(_lang.retry),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: providerData.when(
|
||||
data: (data) {
|
||||
return DefaultTextStyle.merge(
|
||||
style: _theme.textTheme.bodyLarge?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xffF7F7F7),
|
||||
border: Border(top: Divider.createBorderSide(context)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(child: Text(_lang.total)),
|
||||
Expanded(
|
||||
child: Text("$currency${formatPointNumber(data.totalAmount ?? 0)}", textAlign: TextAlign.end),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
error: (_, __) => const SizedBox.shrink(),
|
||||
loading: SizedBox.shrink,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension TitleCaseExtension on String {
|
||||
String toTitleCase() {
|
||||
if (isEmpty) return this;
|
||||
|
||||
final normalized = replaceAll(RegExp(r'[_\-]+'), ' ');
|
||||
|
||||
final words = normalized.split(' ').map((w) => w.trim()).where((w) => w.isNotEmpty).toList();
|
||||
|
||||
if (words.isEmpty) return '';
|
||||
|
||||
final titleCased = words.map((word) {
|
||||
final lower = word.toLowerCase();
|
||||
return lower[0].toUpperCase() + lower.substring(1);
|
||||
}).join(' ');
|
||||
|
||||
return titleCased;
|
||||
}
|
||||
}
|
||||
166
lib/Screens/all_transaction/model/transaction_model.dart
Normal file
166
lib/Screens/all_transaction/model/transaction_model.dart
Normal file
@@ -0,0 +1,166 @@
|
||||
import 'package:mobile_pos/Screens/Due%20Calculation/Model/due_collection_model.dart';
|
||||
import 'package:mobile_pos/Screens/Purchase/Model/purchase_transaction_model.dart';
|
||||
import 'package:mobile_pos/model/sale_transaction_model.dart';
|
||||
import 'package:mobile_pos/widgets/multipal%20payment%20mathods/model/payment_transaction_model.dart';
|
||||
|
||||
class TransactionModel {
|
||||
TransactionModel({
|
||||
this.message,
|
||||
this.totalAmount,
|
||||
this.moneyIn,
|
||||
this.moneyOut,
|
||||
this.data,
|
||||
});
|
||||
|
||||
String? message;
|
||||
num? totalAmount;
|
||||
num? moneyIn;
|
||||
num? moneyOut;
|
||||
List<TransactionModelData>? data;
|
||||
|
||||
factory TransactionModel.fromJson(Map<String, dynamic> json) {
|
||||
return TransactionModel(
|
||||
message: json['message'],
|
||||
totalAmount: json['total_amount'],
|
||||
moneyIn: json['money_in'],
|
||||
moneyOut: json['money_out'],
|
||||
data: json['data'] != null
|
||||
? List<TransactionModelData>.from(
|
||||
json['data'].map((v) => TransactionModelData.fromJson(v)),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'message': message,
|
||||
'total_amount': totalAmount,
|
||||
'money_in': moneyIn,
|
||||
'money_out': moneyOut,
|
||||
'data': data?.map((e) => e.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class TransactionModelData {
|
||||
TransactionModelData({
|
||||
this.id,
|
||||
this.platform,
|
||||
this.transactionType,
|
||||
this.type,
|
||||
this.amount,
|
||||
this.totalAmount,
|
||||
this.date,
|
||||
this.businessId,
|
||||
this.branchId,
|
||||
this.paymentTypeId,
|
||||
this.userId,
|
||||
this.fromBank,
|
||||
this.toBank,
|
||||
this.referenceId,
|
||||
this.invoiceNo,
|
||||
this.image,
|
||||
this.note,
|
||||
this.meta,
|
||||
this.deletedAt,
|
||||
this.createdAt,
|
||||
this.updatedAt,
|
||||
this.party,
|
||||
this.paymentType,
|
||||
this.sale,
|
||||
this.purchase,
|
||||
this.dueCollect,
|
||||
});
|
||||
|
||||
num? id;
|
||||
String? platform;
|
||||
String? transactionType;
|
||||
String? type;
|
||||
num? amount;
|
||||
num? totalAmount;
|
||||
String? date;
|
||||
num? businessId;
|
||||
num? branchId;
|
||||
num? paymentTypeId;
|
||||
num? userId;
|
||||
String? fromBank;
|
||||
int? toBank;
|
||||
num? referenceId;
|
||||
String? invoiceNo;
|
||||
String? image;
|
||||
String? note;
|
||||
Meta? meta;
|
||||
String? deletedAt;
|
||||
String? createdAt;
|
||||
String? updatedAt;
|
||||
|
||||
Party? party;
|
||||
PaymentsTransaction? paymentType;
|
||||
SalesTransactionModel? sale;
|
||||
PurchaseTransaction? purchase;
|
||||
DueCollection? dueCollect;
|
||||
|
||||
factory TransactionModelData.fromJson(Map<String, dynamic> json) {
|
||||
final _partyKey = json['sale']?['party'] ?? json['purchase']?['party'] ?? json['due_collect']?['party'];
|
||||
|
||||
return TransactionModelData(
|
||||
id: json['id'],
|
||||
platform: json['platform'],
|
||||
transactionType: json['transaction_type'],
|
||||
type: json['type'],
|
||||
amount: json['amount'],
|
||||
totalAmount: json['total_amount'],
|
||||
date: json['date'],
|
||||
businessId: json['business_id'],
|
||||
branchId: json['branch_id'],
|
||||
paymentTypeId: json['payment_type_id'],
|
||||
userId: json['user_id'],
|
||||
fromBank: json['from_bank'],
|
||||
toBank: json['to_bank'],
|
||||
referenceId: json['reference_id'],
|
||||
invoiceNo: json['invoice_no'],
|
||||
image: json['image'],
|
||||
note: json['note'],
|
||||
meta: json['meta'] != null ? Meta.fromJson(json['meta']) : null,
|
||||
deletedAt: json['deleted_at'],
|
||||
createdAt: json['created_at'],
|
||||
updatedAt: json['updated_at'],
|
||||
party: _partyKey != null ? Party.fromJson(_partyKey) : null,
|
||||
paymentType: json['payment_type'] != null ? PaymentsTransaction.fromJson(json['payment_type']) : null,
|
||||
sale: json['sale'] != null ? SalesTransactionModel.fromJson(json['sale']) : null,
|
||||
purchase: json['purchase'] != null ? PurchaseTransaction.fromJson(json['purchase']) : null,
|
||||
dueCollect: json['due_collect'] != null ? DueCollection.fromJson(json['due_collect']) : null,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'platform': platform,
|
||||
'transaction_type': transactionType,
|
||||
'type': type,
|
||||
'amount': amount,
|
||||
'total_amount': totalAmount,
|
||||
'date': date,
|
||||
'business_id': businessId,
|
||||
'branch_id': branchId,
|
||||
'payment_type_id': paymentTypeId,
|
||||
'user_id': userId,
|
||||
'from_bank': fromBank,
|
||||
'to_bank': toBank,
|
||||
'reference_id': referenceId,
|
||||
'invoice_no': invoiceNo,
|
||||
'image': image,
|
||||
'note': note,
|
||||
'meta': meta?.toJson(),
|
||||
'deleted_at': deletedAt,
|
||||
'created_at': createdAt,
|
||||
'updated_at': updatedAt,
|
||||
'payment_type': paymentType,
|
||||
'sale': sale,
|
||||
'purchase': purchase,
|
||||
'due_collect': dueCollect,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../model/transaction_model.dart';
|
||||
import '../repo/transaction_repo.dart';
|
||||
|
||||
final transactionRepoProvider = Provider<TransactionRepo>((ref) {
|
||||
return TransactionRepo();
|
||||
});
|
||||
|
||||
// final filteredTransactionProvider = FutureProvider.family<TransactionModel, TransactionFilteredModel>(
|
||||
// (ref, filter) async {
|
||||
// final repo = ref.read(transactionRepoProvider);
|
||||
//
|
||||
// return repo.fetchTransactionList(
|
||||
// duration: filter.duration,
|
||||
// fromDate: filter.fromDate,
|
||||
// toDate: filter.toDate,
|
||||
// platform: filter.transactionType == 'all_transaction' ? null : filter.transactionType,
|
||||
// partyId: filter.party == 'all_parties' ? null : int.tryParse(filter.party!),
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
|
||||
class TransactionFilteredModel {
|
||||
final String duration;
|
||||
final String? fromDate;
|
||||
final String? toDate;
|
||||
final String? transactionType;
|
||||
final String? party;
|
||||
|
||||
const TransactionFilteredModel({
|
||||
required this.duration,
|
||||
this.fromDate,
|
||||
this.toDate,
|
||||
this.transactionType,
|
||||
this.party,
|
||||
});
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is TransactionFilteredModel &&
|
||||
runtimeType == other.runtimeType &&
|
||||
duration == other.duration &&
|
||||
fromDate == other.fromDate &&
|
||||
toDate == other.toDate &&
|
||||
transactionType == other.transactionType &&
|
||||
party == other.party;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
duration.hashCode ^ fromDate.hashCode ^ toDate.hashCode ^ transactionType.hashCode ^ party.hashCode;
|
||||
}
|
||||
|
||||
final filteredTransactionProvider = FutureProvider.autoDispose.family<TransactionModel, TransactionFilteredModel>(
|
||||
(ref, filter) async {
|
||||
final repo = ref.read(transactionRepoProvider);
|
||||
|
||||
// Convert party string to int if it's not "all_parties"
|
||||
int? partyId;
|
||||
if (filter.party != null && filter.party!.isNotEmpty && filter.party != 'all_parties') {
|
||||
partyId = int.tryParse(filter.party!);
|
||||
}
|
||||
|
||||
return repo.fetchTransactionList(
|
||||
duration: filter.duration,
|
||||
fromDate: filter.fromDate,
|
||||
toDate: filter.toDate,
|
||||
platform: filter.transactionType,
|
||||
partyId: partyId,
|
||||
);
|
||||
},
|
||||
);
|
||||
62
lib/Screens/all_transaction/repo/transaction_repo.dart
Normal file
62
lib/Screens/all_transaction/repo/transaction_repo.dart
Normal file
@@ -0,0 +1,62 @@
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import '../../../Const/api_config.dart';
|
||||
import '../../../http_client/customer_http_client_get.dart';
|
||||
import '../model/transaction_model.dart';
|
||||
|
||||
class TransactionRepo {
|
||||
Future<TransactionModel> fetchTransactionList({
|
||||
required String duration,
|
||||
String? fromDate,
|
||||
String? toDate,
|
||||
String? platform,
|
||||
int? partyId,
|
||||
}) async {
|
||||
try {
|
||||
final client = CustomHttpClientGet(client: http.Client());
|
||||
final List<String> queryList = [];
|
||||
|
||||
// Add required duration parameter
|
||||
queryList.add('duration=$duration');
|
||||
|
||||
// Add date parameters only if duration is custom_date
|
||||
if (duration == 'custom_date') {
|
||||
if (fromDate != null && fromDate.isNotEmpty) {
|
||||
queryList.add('from_date=$fromDate');
|
||||
}
|
||||
if (toDate != null && toDate.isNotEmpty) {
|
||||
queryList.add('to_date=$toDate');
|
||||
}
|
||||
}
|
||||
|
||||
// Add platform filter if specified and not "all_transaction"
|
||||
if (platform != null && platform.isNotEmpty && platform != 'all_transaction') {
|
||||
queryList.add('platform=$platform');
|
||||
}
|
||||
|
||||
// Add party filter if specified
|
||||
if (partyId != null) {
|
||||
queryList.add('party_id=$partyId');
|
||||
}
|
||||
|
||||
final uri = Uri.parse(
|
||||
'${APIConfig.url}/transactions?${queryList.join('&')}',
|
||||
);
|
||||
|
||||
print('Fetching transactions from: $uri'); // Debug print
|
||||
|
||||
final response = await client.get(url: uri);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final jsonData = jsonDecode(response.body);
|
||||
return TransactionModel.fromJson(jsonData);
|
||||
} else {
|
||||
print('API Error: ${response.statusCode} - ${response.body}');
|
||||
throw Exception('Failed to fetch transactions: ${response.statusCode}');
|
||||
}
|
||||
} catch (e) {
|
||||
print('TransactionRepo error: $e');
|
||||
throw Exception('Failed to fetch transactions: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user