bug fix
This commit is contained in:
parent
c35d42f39c
commit
30e81109e6
File diff suppressed because one or more lines are too long
@ -362,6 +362,35 @@ class _AppHeaderState extends ConsumerState<AppHeader> {
|
||||
},
|
||||
),
|
||||
],
|
||||
|
||||
if (key == 'Payout' && role == 'Accounts') ...[
|
||||
_buildPopupItem(
|
||||
label: "Payout",
|
||||
onTap: () {
|
||||
_hidePopup();
|
||||
setState(() => _activeMenu = 'Payout');
|
||||
context.go(AppRoutes.payoutList);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
_buildPopupItem(
|
||||
label: "Payout upload",
|
||||
onTap: () {
|
||||
_hidePopup();
|
||||
setState(() => _activeMenu = 'PayoutUpload');
|
||||
context.go(AppRoutes.payoutUpload);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
_buildPopupItem(
|
||||
label: "Payout Reports",
|
||||
onTap: () {
|
||||
_hidePopup();
|
||||
setState(() => _activeMenu = 'PayoutReport');
|
||||
context.go(AppRoutes.payoutReport);
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -503,40 +532,17 @@ class _AppHeaderState extends ConsumerState<AppHeader> {
|
||||
],
|
||||
|
||||
|
||||
// Payout (Accounts only) — single top-level item, no submenu
|
||||
// Payout (Accounts only) — one top-level menu with submenu
|
||||
if (role == 'Accounts') ...[
|
||||
_buildMenuItem(
|
||||
_buildHoverMenuItem(
|
||||
icon: Icons.payments_outlined,
|
||||
label: "Payout",
|
||||
isActive: activeMenu == 'Payout',
|
||||
onTap: () {
|
||||
_hidePopup();
|
||||
setState(() => _activeMenu = 'Payout');
|
||||
context.go(AppRoutes.payoutList);
|
||||
},
|
||||
popupKey: 'Payout',
|
||||
isActive: activeMenu == 'Payout' ||
|
||||
activeMenu == 'PayoutUpload' ||
|
||||
activeMenu == 'PayoutReport',
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
_buildMenuItem(
|
||||
icon: Icons.upload_file_outlined,
|
||||
label: "Payout Upload",
|
||||
isActive: activeMenu == 'PayoutUpload',
|
||||
onTap: () {
|
||||
_hidePopup();
|
||||
setState(() => _activeMenu = 'PayoutUpload');
|
||||
context.go(AppRoutes.payoutUpload);
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
_buildMenuItem(
|
||||
icon: Icons.table_chart_outlined,
|
||||
label: "Payout Report",
|
||||
isActive: activeMenu == 'PayoutReport',
|
||||
onTap: () {
|
||||
_hidePopup();
|
||||
setState(() => _activeMenu = 'PayoutReport');
|
||||
context.go(AppRoutes.payoutReport);
|
||||
},
|
||||
),
|
||||
],
|
||||
|
||||
// Partner (agent): Payout Report — same route as Accounts
|
||||
|
||||
@ -730,7 +730,9 @@ class _gridViewScreenState extends ConsumerState<gridViewScreen> {
|
||||
final isAgent =
|
||||
(ref.watch(userRoleProvider) ?? '').toString().trim().toLowerCase() ==
|
||||
'agent';
|
||||
final isMobile = ResponsiveLayout.isMobile(context);
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (!isAgent) ...[
|
||||
Tooltip(
|
||||
@ -751,14 +753,12 @@ class _gridViewScreenState extends ConsumerState<gridViewScreen> {
|
||||
style: GoogleFonts.poppins(fontSize: 14, fontWeight: FontWeight.w400),
|
||||
),
|
||||
const Spacer(),
|
||||
// Text(
|
||||
// 'Retention Rate: $_retentionRate%',
|
||||
// style: GoogleFonts.poppins(
|
||||
// fontSize: 12,
|
||||
// fontWeight: FontWeight.w500,
|
||||
// color: const Color(0xFF2E7D6E),
|
||||
// ),
|
||||
// ),
|
||||
if (isMobile)
|
||||
Expanded(child: _buildSearch(context, true))
|
||||
else
|
||||
_buildSearch(context, false),
|
||||
const SizedBox(width: 12),
|
||||
_buildExportButtonWidget(),
|
||||
],
|
||||
);
|
||||
}
|
||||
@ -1006,18 +1006,9 @@ class _gridViewScreenState extends ConsumerState<gridViewScreen> {
|
||||
const SizedBox(height: 10),
|
||||
dropdownRto(),
|
||||
const SizedBox(height: 10),
|
||||
_buildSearch(context, isMobile),
|
||||
const SizedBox(height: 10),
|
||||
searchBtn(),
|
||||
const SizedBox(height: 10),
|
||||
resetBtn(),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
_buildExportButtonWidget(),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
: Row(
|
||||
@ -1044,10 +1035,6 @@ class _gridViewScreenState extends ConsumerState<gridViewScreen> {
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
_buildSearch(context, isMobile),
|
||||
const SizedBox(width: 12),
|
||||
_buildExportButtonWidget(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@ -518,40 +518,108 @@ class _AgentDetailsScreenState extends ConsumerState<AgentDetailsScreen> {
|
||||
child: Container(
|
||||
color: Colors.white,
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final maxBody = 1200.0;
|
||||
final hPad = ResponsiveLayout.isMobile(context) ? 12.0 : 20.0;
|
||||
final mobile = ResponsiveLayout.isMobile(context);
|
||||
final hPad = mobile ? 12.0 : 20.0;
|
||||
final vPad = mobile ? 12.0 : 16.0;
|
||||
final viewW = constraints.hasBoundedWidth && constraints.maxWidth.isFinite
|
||||
? constraints.maxWidth
|
||||
: MediaQuery.sizeOf(context).width;
|
||||
final contentW = viewW > maxBody ? maxBody : viewW;
|
||||
return Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: maxBody),
|
||||
child: SingleChildScrollView(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: hPad,
|
||||
vertical: ResponsiveLayout.isMobile(context) ? 12 : 16,
|
||||
),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
autovalidateMode: AutovalidateMode.disabled,
|
||||
child: Column(
|
||||
final contentW = (viewW - 2 * hPad).clamp(280.0, double.infinity);
|
||||
|
||||
final form = Form(
|
||||
key: _formKey,
|
||||
autovalidateMode: AutovalidateMode.disabled,
|
||||
child: mobile
|
||||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_pageHeaderBar(context, pageTitle),
|
||||
const SizedBox(height: 12),
|
||||
_partnerDetailsCard(context, contentW),
|
||||
const SizedBox(height: 24),
|
||||
_retentionSection(context),
|
||||
SizedBox(height: ResponsiveLayout.isMobile(context) ? 24 : 16),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: EdgeInsets.only(bottom: vPad),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_partnerDetailsHeading(),
|
||||
const SizedBox(height: 14),
|
||||
_partnerDetailsCard(context, contentW),
|
||||
const SizedBox(height: 24),
|
||||
_retentionSection(context, expandTableHeight: false),
|
||||
SizedBox(height: mobile ? 24 : 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_pageHeaderBar(context, pageTitle),
|
||||
const SizedBox(height: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 5,
|
||||
child: _partnerDetailsHeading(),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
flex: 6,
|
||||
child: _retentionTitleSearchRow(),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Expanded(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 5,
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
child: LayoutBuilder(
|
||||
builder: (ctx, c) {
|
||||
final w = c.maxWidth.isFinite && c.maxWidth > 0
|
||||
? c.maxWidth
|
||||
: contentW * 0.48;
|
||||
return _partnerDetailsCard(context, w);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
flex: 6,
|
||||
child: _retentionSection(
|
||||
context,
|
||||
expandTableHeight: true,
|
||||
showSectionTitleRow: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: hPad, vertical: vPad),
|
||||
child: form,
|
||||
);
|
||||
},
|
||||
),
|
||||
@ -560,6 +628,17 @@ class _AgentDetailsScreenState extends ConsumerState<AgentDetailsScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _partnerDetailsHeading() {
|
||||
return Text(
|
||||
'Partner Details',
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: const Color(0xFF1E293B),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _partnerDetailsCard(BuildContext context, double contentW) {
|
||||
final pad = ResponsiveLayout.isMobile(context) ? 14.0 : 20.0;
|
||||
return Container(
|
||||
@ -572,15 +651,6 @@ class _AgentDetailsScreenState extends ConsumerState<AgentDetailsScreen> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
'Partner details',
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: const Color(0xFF1E293B),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_partnerRows(context, contentW),
|
||||
const SizedBox(height: 20),
|
||||
Align(
|
||||
@ -932,12 +1002,122 @@ class _AgentDetailsScreenState extends ConsumerState<AgentDetailsScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _retentionSection(BuildContext context) {
|
||||
final mobile = ResponsiveLayout.isMobile(context);
|
||||
return Column(
|
||||
Widget _retentionTableInCard({
|
||||
required double viewportHeight,
|
||||
required double parentWidth,
|
||||
}) {
|
||||
final tableColumn = Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
if (mobile)
|
||||
_retentionTableHeader(),
|
||||
..._filteredVehicleTypes.map(_retentionDataRow),
|
||||
],
|
||||
);
|
||||
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: const Color(0xFFE2E8F0)),
|
||||
),
|
||||
child: SizedBox(
|
||||
height: viewportHeight,
|
||||
width: double.infinity,
|
||||
child: Scrollbar(
|
||||
thumbVisibility: true,
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
primary: false,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(minWidth: parentWidth),
|
||||
child: tableColumn,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Table shell with a fixed header row; only data rows scroll (web two-column layout).
|
||||
Widget _retentionTableStickyHeaderCard() {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: const Color(0xFFE2E8F0)),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_retentionTableHeader(),
|
||||
Expanded(
|
||||
child: Scrollbar(
|
||||
thumbVisibility: true,
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
primary: false,
|
||||
itemCount: _filteredVehicleTypes.length,
|
||||
itemBuilder: (context, i) =>
|
||||
_retentionDataRow(_filteredVehicleTypes[i]),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _retentionTitleSearchRow() {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Retention rate by vehicle type',
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: const Color(0xFF1E293B),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 220,
|
||||
child: ThemedSearchField(
|
||||
hintText: 'Search vehicle type',
|
||||
backgroundColor: Colors.white,
|
||||
txtHeight: 34,
|
||||
controller: _retentionSearchCtrl,
|
||||
onChanged: (_) => setState(() {}),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// [expandTableHeight]: when true (web two-column layout), the table fills remaining
|
||||
/// height beside Partner Details. When false (mobile), uses list/cards and vertical stack.
|
||||
///
|
||||
/// [showSectionTitleRow]: when false, the title + search row is omitted (shown beside
|
||||
/// Partner Details in the parent [Row]).
|
||||
Widget _retentionSection(
|
||||
BuildContext context, {
|
||||
bool expandTableHeight = false,
|
||||
bool showSectionTitleRow = true,
|
||||
}) {
|
||||
final mobile = ResponsiveLayout.isMobile(context);
|
||||
|
||||
if (mobile) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
@ -959,81 +1139,51 @@ class _AgentDetailsScreenState extends ConsumerState<AgentDetailsScreen> {
|
||||
onChanged: (_) => setState(() {}),
|
||||
),
|
||||
],
|
||||
)
|
||||
else
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Retention rate by vehicle type',
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: const Color(0xFF1E293B),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 220,
|
||||
child: ThemedSearchField(
|
||||
hintText: 'Search vehicle type',
|
||||
backgroundColor: Colors.white,
|
||||
txtHeight: 34,
|
||||
controller: _retentionSearchCtrl,
|
||||
onChanged: (_) => setState(() {}),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_retentionMobileList(context),
|
||||
const SizedBox(height: 12),
|
||||
_retentionSaveAllButtonBottom(context),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
if (!showSectionTitleRow) {
|
||||
assert(expandTableHeight, 'showSectionTitleRow false is only used with expandTableHeight');
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(child: _retentionTableStickyHeaderCard()),
|
||||
const SizedBox(height: 12),
|
||||
_retentionSaveAllButtonBottom(context),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
final Widget tableArea;
|
||||
if (expandTableHeight) {
|
||||
tableArea = Expanded(
|
||||
child: _retentionTableStickyHeaderCard(),
|
||||
);
|
||||
} else {
|
||||
tableArea = LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final parentW = constraints.maxWidth.isFinite
|
||||
? constraints.maxWidth
|
||||
: MediaQuery.sizeOf(context).width;
|
||||
final screenH = MediaQuery.sizeOf(context).height;
|
||||
final viewportH = math.min(520.0, math.max(240.0, screenH * 0.42));
|
||||
return _retentionTableInCard(viewportHeight: viewportH, parentWidth: parentW);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_retentionTitleSearchRow(),
|
||||
const SizedBox(height: 12),
|
||||
if (mobile)
|
||||
_retentionMobileList(context)
|
||||
else
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final parentW = constraints.maxWidth.isFinite
|
||||
? constraints.maxWidth
|
||||
: MediaQuery.sizeOf(context).width;
|
||||
final screenH = MediaQuery.sizeOf(context).height;
|
||||
final viewportH = math.min(520.0, math.max(240.0, screenH * 0.42));
|
||||
|
||||
final tableColumn = Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_retentionTableHeader(),
|
||||
..._filteredVehicleTypes.map(_retentionDataRow),
|
||||
],
|
||||
);
|
||||
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: const Color(0xFFE2E8F0)),
|
||||
),
|
||||
child: SizedBox(
|
||||
height: viewportH,
|
||||
width: double.infinity,
|
||||
child: Scrollbar(
|
||||
thumbVisibility: true,
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
primary: false,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(minWidth: parentW),
|
||||
child: tableColumn,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
tableArea,
|
||||
const SizedBox(height: 12),
|
||||
_retentionSaveAllButtonBottom(context),
|
||||
],
|
||||
|
||||
@ -1030,31 +1030,31 @@ class _PayoutListState extends ConsumerState<PayoutList> {
|
||||
const SizedBox(width: _actionIconGap),
|
||||
|
||||
// Step 2: Add payment only when balance amount is pending.
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.currency_rupee,
|
||||
color: Colors.green,
|
||||
size: _actionIconSize,
|
||||
),
|
||||
tooltip: 'Add payment',
|
||||
onPressed: () {
|
||||
final balance = _toDouble(item['balance_amount']);
|
||||
if (balance <= 0) {
|
||||
ToastHelper.showWarningToast(context, 'Invoice already fully paid');
|
||||
return;
|
||||
}
|
||||
_showAddPaymentDialog(item);
|
||||
},
|
||||
splashRadius: 16,
|
||||
hoverColor: Colors.black12,
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints.tightFor(
|
||||
width: _actionButtonSize,
|
||||
height: _actionButtonSize,
|
||||
),
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
const SizedBox(width: _actionIconGap),
|
||||
// IconButton(
|
||||
// icon: const Icon(
|
||||
// Icons.currency_rupee,
|
||||
// color: Colors.green,
|
||||
// size: _actionIconSize,
|
||||
// ),
|
||||
// tooltip: 'Add payment',
|
||||
// onPressed: () {
|
||||
// final balance = _toDouble(item['balance_amount']);
|
||||
// if (balance <= 0) {
|
||||
// ToastHelper.showWarningToast(context, 'Invoice already fully paid');
|
||||
// return;
|
||||
// }
|
||||
// _showAddPaymentDialog(item);
|
||||
// },
|
||||
// splashRadius: 16,
|
||||
// hoverColor: Colors.black12,
|
||||
// padding: EdgeInsets.zero,
|
||||
// constraints: const BoxConstraints.tightFor(
|
||||
// width: _actionButtonSize,
|
||||
// height: _actionButtonSize,
|
||||
// ),
|
||||
// visualDensity: VisualDensity.compact,
|
||||
// ),
|
||||
// const SizedBox(width: _actionIconGap),
|
||||
|
||||
// Step 3: Show account history modal for this invoice.
|
||||
IconButton(
|
||||
|
||||
@ -595,6 +595,48 @@ class _PayoutReportScreenState extends ConsumerState<PayoutReportScreen> {
|
||||
return '-';
|
||||
}
|
||||
|
||||
String? _firstNonEmptyRaw(Map<String, dynamic> row, List<String> keys) {
|
||||
for (final k in keys) {
|
||||
final v = row[k];
|
||||
if (v != null && v.toString().trim().isNotEmpty) {
|
||||
return v.toString();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// API sometimes returns payout + UTR in one string (e.g. `5000.00UTR20260401164749`).
|
||||
/// Normalizes into separate display values for PAYOUT and UTR NO columns (and export).
|
||||
({String payout, String utr}) _payoutAndUtr(Map<String, dynamic> row) {
|
||||
const utrKeys = ['utr_no', 'utr', 'utr_number'];
|
||||
const payoutKeys = ['commission_amount', 'payout', 'commission'];
|
||||
|
||||
String utrStr = (_firstNonEmptyRaw(row, utrKeys) ?? '').trim();
|
||||
String payoutRaw = (_firstNonEmptyRaw(row, payoutKeys) ?? '').trim();
|
||||
|
||||
String stripTrailingPlaceholder(String s) =>
|
||||
s.replaceAll(RegExp(r'[\s\-–—]+$'), '').trim();
|
||||
|
||||
if (payoutRaw.isNotEmpty) {
|
||||
final upper = payoutRaw.toUpperCase();
|
||||
final idx = upper.indexOf('UTR');
|
||||
if (idx > 0) {
|
||||
final gluedUtr = payoutRaw.substring(idx).trim();
|
||||
payoutRaw = stripTrailingPlaceholder(payoutRaw.substring(0, idx));
|
||||
if (utrStr.isEmpty || utrStr == '-') {
|
||||
utrStr = gluedUtr;
|
||||
}
|
||||
} else {
|
||||
payoutRaw = stripTrailingPlaceholder(payoutRaw);
|
||||
}
|
||||
}
|
||||
|
||||
if (payoutRaw.isEmpty) payoutRaw = '-';
|
||||
if (utrStr.isEmpty) utrStr = '-';
|
||||
|
||||
return (payout: payoutRaw, utr: utrStr);
|
||||
}
|
||||
|
||||
static num? _parseNum(dynamic v) {
|
||||
if (v == null) return null;
|
||||
if (v is num) return v;
|
||||
@ -714,8 +756,13 @@ class _PayoutReportScreenState extends ConsumerState<PayoutReportScreen> {
|
||||
|
||||
// [ExcelExporter] reverses the list before writing; pass reversed filtered
|
||||
// rows so the file order matches the on-screen table (top → bottom).
|
||||
final exportInput =
|
||||
_filtered.reversed.map((r) => Map<String, dynamic>.from(r)).toList();
|
||||
final exportInput = _filtered.reversed.map((r) {
|
||||
final m = Map<String, dynamic>.from(r);
|
||||
final pu = _payoutAndUtr(m);
|
||||
m['commission_amount'] = pu.payout;
|
||||
m['utr_no'] = pu.utr;
|
||||
return m;
|
||||
}).toList();
|
||||
|
||||
try {
|
||||
await ExcelExporter.exportToExcel(
|
||||
@ -944,9 +991,16 @@ class _PayoutReportScreenState extends ConsumerState<PayoutReportScreen> {
|
||||
Expanded(flex: 1, child: Text('WEIGHT', style: _headerColumn)),
|
||||
Expanded(flex: 2, child: Text('FUEL TYPE', style: _headerColumn)),
|
||||
Expanded(flex: 2, child: Text('VEHICLE TYPE', style: _headerColumn)),
|
||||
Expanded(flex: 2, child: Text('PREMIUM', style: _headerColumn)),
|
||||
Expanded(flex: 2, child: Text('PAYOUT', style: _headerColumn)),
|
||||
Expanded(flex: 2, child: Text('UTR NO', style: _headerColumn)),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text('PREMIUM', style: _headerColumn, textAlign: TextAlign.right),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text('PAYOUT', style: _headerColumn, textAlign: TextAlign.right),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
Expanded(flex: 2, child: Text('RECEIVED', style: _headerColumn)),
|
||||
],
|
||||
),
|
||||
@ -965,6 +1019,8 @@ class _PayoutReportScreenState extends ConsumerState<PayoutReportScreen> {
|
||||
)
|
||||
: _dataBold;
|
||||
|
||||
final pu = _payoutAndUtr(item);
|
||||
|
||||
final row = Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@ -1014,11 +1070,21 @@ class _PayoutReportScreenState extends ConsumerState<PayoutReportScreen> {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
pu.utr,
|
||||
style: rowStyle,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
_cell(item, ['premium']),
|
||||
style: rowStyle,
|
||||
textAlign: TextAlign.right,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
@ -1026,21 +1092,14 @@ class _PayoutReportScreenState extends ConsumerState<PayoutReportScreen> {
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
_cell(item, ['commission_amount']),
|
||||
style: rowStyle,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
_cell(item, ['utr_no']),
|
||||
pu.payout,
|
||||
style: rowStyle,
|
||||
textAlign: TextAlign.right,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
|
||||
22
pubspec.lock
22
pubspec.lock
@ -117,10 +117,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
version: "1.4.0"
|
||||
charcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -780,26 +780,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.19"
|
||||
version: "0.12.17"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.13.0"
|
||||
version: "0.11.1"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.0"
|
||||
version: "1.16.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1297,10 +1297,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
|
||||
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.10"
|
||||
version: "0.7.6"
|
||||
toastification:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -1502,5 +1502,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.9.0-0 <4.0.0"
|
||||
dart: ">=3.8.1 <4.0.0"
|
||||
flutter: ">=3.32.0"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user