bug fix
This commit is contained in:
parent
b3884f32c4
commit
d73f745cf2
File diff suppressed because one or more lines are too long
@ -374,7 +374,7 @@ class _AppHeaderState extends ConsumerState<AppHeader> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
_buildPopupItem(
|
_buildPopupItem(
|
||||||
label: "Payout upload",
|
label: "Payout Upload",
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_hidePopup();
|
_hidePopup();
|
||||||
setState(() => _activeMenu = 'PayoutUpload');
|
setState(() => _activeMenu = 'PayoutUpload');
|
||||||
|
|||||||
@ -14,7 +14,6 @@ import '../../../data/utils/validators.dart';
|
|||||||
import '../../layouts/main_layout.dart';
|
import '../../layouts/main_layout.dart';
|
||||||
import '../../layouts/responsive_layout.dart';
|
import '../../layouts/responsive_layout.dart';
|
||||||
import '../../providers/manager_provider.dart';
|
import '../../providers/manager_provider.dart';
|
||||||
import '../../themes/indicators/date_field_theme.dart';
|
|
||||||
import '../../themes/indicators/export_btn.dart';
|
import '../../themes/indicators/export_btn.dart';
|
||||||
import '../../themes/indicators/search_field_theme.dart';
|
import '../../themes/indicators/search_field_theme.dart';
|
||||||
import '../../widgets/custom_Stdate_EnDate_Filter.dart';
|
import '../../widgets/custom_Stdate_EnDate_Filter.dart';
|
||||||
@ -1077,77 +1076,6 @@ class EnquiryListState extends ConsumerState<EnquiryList> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildStartDate(BuildContext context) {
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text('Start Date', style: _textStyle),
|
|
||||||
SizedBox(height: 5),
|
|
||||||
ThemedDateField(
|
|
||||||
hintText: "Select Date",
|
|
||||||
txtwidth: ResponsiveLayout.isMobile(context)
|
|
||||||
? null
|
|
||||||
: MediaQuery.of(context).size.width * 0.16,
|
|
||||||
// txtheight: 50,
|
|
||||||
// backgroundColor: const Color(0xFFECECEC),
|
|
||||||
// validator: (value) => Validators.requiredField(value, "date"),
|
|
||||||
controller: controllers['startDate']!,
|
|
||||||
onDateSelected: (date) {
|
|
||||||
print("SPicked Date: $date");
|
|
||||||
|
|
||||||
controllers['startDate']?.text = DateFormat(
|
|
||||||
'dd-MM-yyyy',
|
|
||||||
).format(date);
|
|
||||||
// controllers['date']?.text = date as String;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget buildEndDate(BuildContext context) {
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text('End Date', style: _textStyle),
|
|
||||||
SizedBox(height: 5),
|
|
||||||
ThemedDateField(
|
|
||||||
hintText: "Select Date",
|
|
||||||
txtwidth: ResponsiveLayout.isMobile(context)
|
|
||||||
? null
|
|
||||||
: MediaQuery.of(context).size.width * 0.16,
|
|
||||||
// txtheight: 50,
|
|
||||||
// backgroundColor: const Color(0xFFECECEC),
|
|
||||||
validator: (value) {
|
|
||||||
if (value == null || value.isEmpty) {
|
|
||||||
return "End Date is required";
|
|
||||||
}
|
|
||||||
|
|
||||||
final fromText = controllers['startDate']?.text ?? '';
|
|
||||||
if (fromText.isNotEmpty) {
|
|
||||||
final startDate = DateFormat('dd-MM-yyyy').parse(fromText);
|
|
||||||
final endDate = DateFormat('dd-MM-yyyy').parse(value);
|
|
||||||
|
|
||||||
if (endDate.isBefore(startDate)) {
|
|
||||||
return "End Date cannot be earlier than Start Date";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null; // ✅ no error
|
|
||||||
},
|
|
||||||
controller: controllers['endDate']!,
|
|
||||||
lastDate: DateTime.now(),
|
|
||||||
onDateSelected: (date) {
|
|
||||||
print("EPicked Date: $date");
|
|
||||||
controllers['endDate']?.text = DateFormat(
|
|
||||||
'dd-MM-yyyy',
|
|
||||||
).format(date);
|
|
||||||
// controllers['date']?.text = date as String;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static final _dataBold = GoogleFonts.inter(
|
static final _dataBold = GoogleFonts.inter(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
|
|
||||||
@ -1186,9 +1114,4 @@ class EnquiryListState extends ConsumerState<EnquiryList> {
|
|||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
);
|
);
|
||||||
|
|
||||||
static const _textStyle = TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import '../../../layouts/main_layout.dart';
|
|||||||
import '../../../layouts/responsive_layout.dart';
|
import '../../../layouts/responsive_layout.dart';
|
||||||
import '../../../providers/manager_provider.dart';
|
import '../../../providers/manager_provider.dart';
|
||||||
import '../../../themes/indicators/customizd_file_upload.dart';
|
import '../../../themes/indicators/customizd_file_upload.dart';
|
||||||
import '../../../themes/indicators/date_field_theme.dart';
|
import '../../../themes/indicators/date_range_picker_field.dart';
|
||||||
import '../../../themes/indicators/export_btn.dart';
|
import '../../../themes/indicators/export_btn.dart';
|
||||||
import '../../../themes/indicators/input_field_decoration.dart';
|
import '../../../themes/indicators/input_field_decoration.dart';
|
||||||
import '../../../themes/indicators/search_field_theme.dart';
|
import '../../../themes/indicators/search_field_theme.dart';
|
||||||
@ -795,10 +795,7 @@ class endosementState extends ConsumerState<Endorsement> {
|
|||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
buildStartDate(context),
|
buildDateRangeFilter(context),
|
||||||
const SizedBox(width: 12),
|
|
||||||
|
|
||||||
buildEndDate(context),
|
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
|
|
||||||
buildEndorsementTypeSearch(context),
|
buildEndorsementTypeSearch(context),
|
||||||
@ -1448,38 +1445,20 @@ class endosementState extends ConsumerState<Endorsement> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildStartDate(BuildContext context) {
|
Widget buildDateRangeFilter(BuildContext context) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: 36,
|
height: 36,
|
||||||
width: ResponsiveLayout.isMobile(context)
|
width: ResponsiveLayout.isMobile(context)
|
||||||
? null
|
? null
|
||||||
: MediaQuery.of(context).size.width * 0.10,
|
: MediaQuery.of(context).size.width * 0.15,
|
||||||
child: ThemedDateField(
|
child: DateRangePickerField(
|
||||||
hintText: "Start Date",
|
startController: startController,
|
||||||
controller: startController,
|
endController: endController,
|
||||||
onDateSelected: (date) {
|
hintText: 'Select date range',
|
||||||
startController.text =
|
showLabelAboveField: false,
|
||||||
DateFormat('dd-MM-yyyy').format(date);
|
txtheight: 36,
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget buildEndDate(BuildContext context) {
|
|
||||||
return SizedBox(
|
|
||||||
height: 36,
|
|
||||||
width: ResponsiveLayout.isMobile(context)
|
|
||||||
? null
|
|
||||||
: MediaQuery.of(context).size.width * 0.10,
|
|
||||||
child: ThemedDateField(
|
|
||||||
hintText: "End Date",
|
|
||||||
controller: endController,
|
|
||||||
lastDate: DateTime.now(),
|
lastDate: DateTime.now(),
|
||||||
onDateSelected: (date) {
|
useFormField: false,
|
||||||
endController.text =
|
|
||||||
DateFormat('dd-MM-yyyy').format(date);
|
|
||||||
// refresh();
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4388,42 +4388,6 @@ Expanded(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildStartDate(BuildContext context) {
|
|
||||||
return SizedBox(
|
|
||||||
height: 36,
|
|
||||||
width: ResponsiveLayout.isMobile(context)
|
|
||||||
? null
|
|
||||||
: MediaQuery.of(context).size.width * 0.10,
|
|
||||||
child: ThemedDateField(
|
|
||||||
hintText: "Start Date",
|
|
||||||
controller: startController,
|
|
||||||
onDateSelected: (date) {
|
|
||||||
startController.text =
|
|
||||||
DateFormat('dd-MM-yyyy').format(date);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget buildEndDate(BuildContext context) {
|
|
||||||
return SizedBox(
|
|
||||||
height: 36,
|
|
||||||
width: ResponsiveLayout.isMobile(context)
|
|
||||||
? null
|
|
||||||
: MediaQuery.of(context).size.width * 0.10,
|
|
||||||
child: ThemedDateField(
|
|
||||||
hintText: "End Date",
|
|
||||||
controller: endController,
|
|
||||||
lastDate: DateTime.now(),
|
|
||||||
onDateSelected: (date) {
|
|
||||||
endController.text =
|
|
||||||
DateFormat('dd-MM-yyyy').format(date);
|
|
||||||
// refresh();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget buildRTOSearch(BuildContext context) {
|
Widget buildRTOSearch(BuildContext context) {
|
||||||
final List<Map<String, dynamic>> endorsementOptions =
|
final List<Map<String, dynamic>> endorsementOptions =
|
||||||
List<Map<String, dynamic>>.from(filteredEndrosmentData);
|
List<Map<String, dynamic>>.from(filteredEndrosmentData);
|
||||||
|
|||||||
@ -286,7 +286,9 @@ class VehicleTypeListState extends ConsumerState<VehicleTypeList> {
|
|||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: Text('Action', style: _headerStyle),
|
child: Center(
|
||||||
|
child: Text('Action', style: _headerStyle),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -415,38 +417,34 @@ class VehicleTypeListState extends ConsumerState<VehicleTypeList> {
|
|||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: Center( // Add Center wrapper
|
child: Align(
|
||||||
child: Row(
|
alignment: Alignment.center,
|
||||||
mainAxisSize: MainAxisSize.min, // Add this
|
child: item['is_active'] == "1"
|
||||||
children: [
|
? Tooltip(
|
||||||
item['is_active'] == "1"
|
message: 'Edit',
|
||||||
? Tooltip(
|
child: IconButton(
|
||||||
message: 'Edit',
|
icon: Image.asset(
|
||||||
child: IconButton(
|
"assets/miscellaneous/Edit.png",
|
||||||
icon: Image.asset(
|
height: 12,
|
||||||
"assets/miscellaneous/Edit.png",
|
width: 15,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
handleEdit(item);
|
||||||
|
},
|
||||||
|
splashRadius: 20,
|
||||||
|
hoverColor: Colors.black12,
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Image.asset(
|
||||||
|
"assets/miscellaneous/Edit_muted.png",
|
||||||
height: 12,
|
height: 12,
|
||||||
width: 15,
|
width: 15,
|
||||||
),
|
),
|
||||||
onPressed: () {
|
|
||||||
handleEdit(item);
|
|
||||||
},
|
|
||||||
splashRadius: 28,
|
|
||||||
hoverColor: Colors.black12,
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
constraints: const BoxConstraints(),
|
|
||||||
),
|
),
|
||||||
)
|
|
||||||
: Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0), // Match IconButton padding
|
|
||||||
child: Image.asset(
|
|
||||||
"assets/miscellaneous/Edit_muted.png",
|
|
||||||
height: 12,
|
|
||||||
width: 15,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -427,7 +427,10 @@ class _RenewalAlertsCard extends StatelessWidget {
|
|||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => _DashCard(
|
Widget build(BuildContext context) {
|
||||||
|
final visibleRowCount = renewals.length.clamp(1, kVisibleRows);
|
||||||
|
final listHeight = visibleRowCount * kRowHeight;
|
||||||
|
return _DashCard(
|
||||||
header: Row(children: [
|
header: Row(children: [
|
||||||
Container(
|
Container(
|
||||||
width: 30,
|
width: 30,
|
||||||
@ -519,7 +522,7 @@ class _RenewalAlertsCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: kVisibleRows * kRowHeight,
|
height: listHeight,
|
||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
physics: const ClampingScrollPhysics(),
|
physics: const ClampingScrollPhysics(),
|
||||||
itemCount: renewals.length,
|
itemCount: renewals.length,
|
||||||
@ -534,6 +537,7 @@ class _RenewalAlertsCard extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RenewalTableRow extends StatelessWidget {
|
class _RenewalTableRow extends StatelessWidget {
|
||||||
|
|||||||
@ -1,14 +1,16 @@
|
|||||||
|
import 'dart:math' show min;
|
||||||
|
|
||||||
import 'package:dropdown_search/dropdown_search.dart';
|
import 'package:dropdown_search/dropdown_search.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:nhance_partner/data/utils/toastNotification.dart';
|
||||||
|
|
||||||
import '../../../core/services/api_service.dart';
|
import '../../../core/services/api_service.dart';
|
||||||
import '../../layouts/responsive_layout.dart';
|
import '../../layouts/responsive_layout.dart';
|
||||||
import '../../providers/manager_provider.dart';
|
import '../../providers/manager_provider.dart';
|
||||||
import '../../providers/userRoleProvider.dart';
|
import '../../providers/userRoleProvider.dart';
|
||||||
import '../../themes/indicators/date_field_theme.dart';
|
import '../../themes/indicators/date_range_picker_field.dart';
|
||||||
import '../../themes/indicators/input_field_decoration.dart';
|
import '../../themes/indicators/input_field_decoration.dart';
|
||||||
|
|
||||||
class DateFilterRowPayout extends ConsumerStatefulWidget {
|
class DateFilterRowPayout extends ConsumerStatefulWidget {
|
||||||
@ -28,6 +30,13 @@ class DateFilterRowPayout extends ConsumerStatefulWidget {
|
|||||||
final role;
|
final role;
|
||||||
final id;
|
final id;
|
||||||
|
|
||||||
|
/// When non-null, toggles filter layout for payout details:
|
||||||
|
/// - `false` — no table rows: show date range only (hide partner + actions).
|
||||||
|
/// - `true` — table has rows: show partner (+ broker if [showBrokerFilter]) and
|
||||||
|
/// search/reset aligned to the right; hide date range.
|
||||||
|
/// When `null`, legacy: date range and partner row together.
|
||||||
|
final bool? policyTableHasRows;
|
||||||
|
|
||||||
const DateFilterRowPayout({
|
const DateFilterRowPayout({
|
||||||
super.key,
|
super.key,
|
||||||
this.showBrokerFilter = true,
|
this.showBrokerFilter = true,
|
||||||
@ -45,6 +54,7 @@ class DateFilterRowPayout extends ConsumerStatefulWidget {
|
|||||||
required this.id,
|
required this.id,
|
||||||
this.isMobile = false,
|
this.isMobile = false,
|
||||||
this.dataFrom,
|
this.dataFrom,
|
||||||
|
this.policyTableHasRows,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -224,28 +234,17 @@ class _DateFilterRowState extends ConsumerState<DateFilterRowPayout> {
|
|||||||
final spacing = 5.0;
|
final spacing = 5.0;
|
||||||
|
|
||||||
List<Widget> getRowChildren(bool isMobile, double spacing) {
|
List<Widget> getRowChildren(bool isMobile, double spacing) {
|
||||||
final dateFields = [
|
final hasDateRange = widget.startController.text.trim().isNotEmpty &&
|
||||||
buildStartDate(context),
|
widget.endController.text.trim().isNotEmpty;
|
||||||
SizedBox(width: spacing),
|
|
||||||
buildEndDate(context),
|
double partnerMaxWidth() {
|
||||||
SizedBox(width: spacing),
|
final sw = MediaQuery.sizeOf(context).width;
|
||||||
if (widget.showBrokerFilter) ...[
|
return min(sw * 0.32, 420);
|
||||||
buildBroker(context),
|
}
|
||||||
SizedBox(width: spacing),
|
|
||||||
],
|
|
||||||
buildPartner(context),
|
|
||||||
SizedBox(width: spacing),
|
|
||||||
];
|
|
||||||
|
|
||||||
final buttons = [
|
final buttons = [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 0.0),
|
padding: const EdgeInsets.symmetric(vertical: 0.0),
|
||||||
// child: GestureDetector(
|
|
||||||
// onTap: () {
|
|
||||||
// if (formKey.currentState!.validate()) onFilter();
|
|
||||||
// },
|
|
||||||
// child: Icon(Icons.filter_alt_outlined),
|
|
||||||
// ),
|
|
||||||
child: Tooltip(
|
child: Tooltip(
|
||||||
message: 'Filter',
|
message: 'Filter',
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
@ -254,18 +253,26 @@ class _DateFilterRowState extends ConsumerState<DateFilterRowPayout> {
|
|||||||
size: 18,
|
size: 18,
|
||||||
color: const Color(0xFF94A3B8),
|
color: const Color(0xFF94A3B8),
|
||||||
),
|
),
|
||||||
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (widget.formKey.currentState!.validate()) {
|
if (widget.formKey.currentState?.validate() != true) return;
|
||||||
widget.onFilter();
|
final partners = widget.selectedParnter;
|
||||||
|
final hasPartner = partners != null &&
|
||||||
|
partners.isNotEmpty &&
|
||||||
|
partners.any((e) => e.toString().trim().isNotEmpty);
|
||||||
|
if (!hasPartner) {
|
||||||
|
ToastHelper.showWarningToast(
|
||||||
|
context,
|
||||||
|
'Please select a partner before searching.',
|
||||||
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
widget.onFilter();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(0.0),
|
padding: const EdgeInsets.all(0.0),
|
||||||
// child: GestureDetector(onTap: onRefresh, child: Icon(Icons.refresh)),
|
|
||||||
child: Tooltip(
|
child: Tooltip(
|
||||||
message: 'Refresh',
|
message: 'Refresh',
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
@ -280,17 +287,38 @@ class _DateFilterRowState extends ConsumerState<DateFilterRowPayout> {
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
if (isMobile) {
|
final partnerActionsRow = <Widget>[
|
||||||
// For mobile: buttons below fields
|
if (widget.showBrokerFilter) ...[
|
||||||
return [
|
buildBroker(context),
|
||||||
...dateFields,
|
SizedBox(width: spacing),
|
||||||
SizedBox(height: 10),
|
],
|
||||||
Row(mainAxisAlignment: MainAxisAlignment.end, children: buttons),
|
buildPartner(context, maxWidth: partnerMaxWidth()),
|
||||||
];
|
SizedBox(width: spacing),
|
||||||
} else {
|
...buttons,
|
||||||
// For desktop: buttons inline with fields
|
];
|
||||||
return [...dateFields, Spacer(), ...buttons];
|
|
||||||
|
if (!hasDateRange) {
|
||||||
|
return [buildDateRangeFilter(context)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
return [
|
||||||
|
buildDateRangeFilter(context),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Wrap(
|
||||||
|
spacing: spacing,
|
||||||
|
runSpacing: 8,
|
||||||
|
crossAxisAlignment: WrapCrossAlignment.end,
|
||||||
|
children: partnerActionsRow,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
buildDateRangeFilter(context),
|
||||||
|
SizedBox(width: spacing),
|
||||||
|
Spacer(),
|
||||||
|
...partnerActionsRow,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return Form(
|
return Form(
|
||||||
@ -309,78 +337,19 @@ class _DateFilterRowState extends ConsumerState<DateFilterRowPayout> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildStartDate(BuildContext context) {
|
Widget buildDateRangeFilter(BuildContext context) {
|
||||||
return Column(
|
final w = ResponsiveLayout.isMobile(context)
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
? null
|
||||||
children: [
|
: MediaQuery.of(context).size.width * 0.15;
|
||||||
Text('Start Date *', style: _textStyle),
|
return DateRangePickerField(
|
||||||
SizedBox(height: 5),
|
startController: widget.startController,
|
||||||
SizedBox(
|
endController: widget.endController,
|
||||||
height: 35,
|
hintText: 'Select date range',
|
||||||
child: ThemedDateField(
|
txtwidth: w,
|
||||||
hintText: "Select Date",
|
txtheight: 32,
|
||||||
txtwidth: ResponsiveLayout.isMobile(context)
|
lastDate: DateTime.now(),
|
||||||
? null
|
rangeValidator: DateRangePickerField.defaultFilterValidator,
|
||||||
: MediaQuery.of(context).size.width * 0.15,
|
onRangeSelected: (_) => Future.microtask(() => widget.onFilter()),
|
||||||
// txtheight: 50,
|
|
||||||
// backgroundColor: const Color(0xFFECECEC),
|
|
||||||
// validator: (value) => Validators.requiredField(value, "date"),
|
|
||||||
controller: widget.startController,
|
|
||||||
onDateSelected: (date) {
|
|
||||||
print("Picked Date: $date");
|
|
||||||
widget.startController.text = DateFormat(
|
|
||||||
'dd-MM-yyyy',
|
|
||||||
).format(date);
|
|
||||||
// controllers['date']?.text = date as String;
|
|
||||||
Future.microtask(() => widget.onFilter());
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget buildEndDate(BuildContext context) {
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text('End Date *', style: _textStyle),
|
|
||||||
SizedBox(height: 5),
|
|
||||||
SizedBox(
|
|
||||||
height: 35,
|
|
||||||
child: ThemedDateField(
|
|
||||||
hintText: "Select Date",
|
|
||||||
txtwidth: ResponsiveLayout.isMobile(context)
|
|
||||||
? null
|
|
||||||
: MediaQuery.of(context).size.width * 0.15,
|
|
||||||
// txtheight: 50,
|
|
||||||
// backgroundColor: const Color(0xFFECECEC),
|
|
||||||
validator: (value) {
|
|
||||||
final fromText = widget.startController.text ?? '';
|
|
||||||
if (fromText.isNotEmpty) {
|
|
||||||
final startDate = DateFormat('dd-MM-yyyy').parse(fromText);
|
|
||||||
if (value == null || value.isEmpty) {
|
|
||||||
return "End Date is required";
|
|
||||||
}
|
|
||||||
final endDate = DateFormat('dd-MM-yyyy').parse(value);
|
|
||||||
|
|
||||||
if (endDate.isBefore(startDate)) {
|
|
||||||
return "End Date cannot be earlier than Start Date";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null; // ✅ no error
|
|
||||||
},
|
|
||||||
controller: widget.endController,
|
|
||||||
// lastDate: DateTime.now(),
|
|
||||||
onDateSelected: (date) {
|
|
||||||
print("Picked Date: $date");
|
|
||||||
widget.endController.text = DateFormat('dd-MM-yyyy').format(date);
|
|
||||||
Future.microtask(() => widget.onFilter());
|
|
||||||
// controllers['date']?.text = date as String;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -555,8 +524,8 @@ class _DateFilterRowState extends ConsumerState<DateFilterRowPayout> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildPartner(BuildContext context) {
|
Widget buildPartner(BuildContext context, {double? maxWidth}) {
|
||||||
final isReadOnly = false;
|
final w = maxWidth ?? MediaQuery.of(context).size.width * 0.4;
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@ -565,7 +534,7 @@ class _DateFilterRowState extends ConsumerState<DateFilterRowPayout> {
|
|||||||
SizedBox(height: 5),
|
SizedBox(height: 5),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 35,
|
height: 35,
|
||||||
width: MediaQuery.of(context).size.width * 0.4,
|
width: w,
|
||||||
child: DropdownSearch<Map<String, dynamic>>.multiSelection(
|
child: DropdownSearch<Map<String, dynamic>>.multiSelection(
|
||||||
key: dropDownKeyPartner,
|
key: dropDownKeyPartner,
|
||||||
|
|
||||||
@ -591,6 +560,28 @@ class _DateFilterRowState extends ConsumerState<DateFilterRowPayout> {
|
|||||||
"${val['agent_name']} | ${val['unused_commission_amount']} (${val['total_policies']})",
|
"${val['agent_name']} | ${val['unused_commission_amount']} (${val['total_policies']})",
|
||||||
compareFn: (item, selectedItem) =>
|
compareFn: (item, selectedItem) =>
|
||||||
item['agent_id'] == selectedItem['agent_id'], // ✅ compare by id
|
item['agent_id'] == selectedItem['agent_id'], // ✅ compare by id
|
||||||
|
dropdownBuilder: (context, selectedItems) {
|
||||||
|
final safeItems = selectedItems;
|
||||||
|
final text = safeItems.isEmpty
|
||||||
|
? 'Select Partner'
|
||||||
|
: safeItems.length == 1
|
||||||
|
? "${safeItems.first['agent_name']} | ${safeItems.first['unused_commission_amount']} (${safeItems.first['total_policies']})"
|
||||||
|
: '${safeItems.length} partners selected';
|
||||||
|
return Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Text(
|
||||||
|
text,
|
||||||
|
style: GoogleFonts.inter(
|
||||||
|
fontSize: 12,
|
||||||
|
color: safeItems.isEmpty
|
||||||
|
? const Color(0xFF64748B)
|
||||||
|
: Colors.black,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
// validator: (val) {
|
// validator: (val) {
|
||||||
// if (val == null) {
|
// if (val == null) {
|
||||||
// return "Required"; // ✅ error message
|
// return "Required"; // ✅ error message
|
||||||
@ -612,8 +603,9 @@ class _DateFilterRowState extends ConsumerState<DateFilterRowPayout> {
|
|||||||
decoratorProps: DropDownDecoratorProps(
|
decoratorProps: DropDownDecoratorProps(
|
||||||
decoration:
|
decoration:
|
||||||
AppInputDecorations.dropdownDecoration(
|
AppInputDecorations.dropdownDecoration(
|
||||||
label: "Select Partner",
|
label: "",
|
||||||
).copyWith(
|
).copyWith(
|
||||||
|
hintText: null,
|
||||||
hintStyle: GoogleFonts.inter(
|
hintStyle: GoogleFonts.inter(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
|
|||||||
@ -5,13 +5,16 @@ import 'package:go_router/go_router.dart';
|
|||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:nhance_partner/data/utils/toastNotification.dart';
|
import 'package:nhance_partner/data/utils/toastNotification.dart';
|
||||||
|
import 'dart:math' show max;
|
||||||
|
|
||||||
|
import '../../../data/utils/Pagination.dart';
|
||||||
import '../../../core/routing/routes.dart';
|
import '../../../core/routing/routes.dart';
|
||||||
import '../../../core/services/api_service.dart';
|
import '../../../core/services/api_service.dart';
|
||||||
import '../../layouts/main_layout.dart';
|
import '../../layouts/main_layout.dart';
|
||||||
import '../../layouts/responsive_layout.dart';
|
import '../../layouts/responsive_layout.dart';
|
||||||
import '../../providers/manager_provider.dart';
|
import '../../providers/manager_provider.dart';
|
||||||
import '../../providers/userRoleProvider.dart';
|
import '../../providers/userRoleProvider.dart';
|
||||||
|
import '../../themes/indicators/export_btn.dart';
|
||||||
import 'custom_dateRange.dart';
|
import 'custom_dateRange.dart';
|
||||||
|
|
||||||
class PayOutDetails extends ConsumerStatefulWidget {
|
class PayOutDetails extends ConsumerStatefulWidget {
|
||||||
@ -92,6 +95,8 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
|
|
||||||
bool isLoading = false;
|
bool isLoading = false;
|
||||||
bool hasFetchedTableData = false;
|
bool hasFetchedTableData = false;
|
||||||
|
int currentPage = 1;
|
||||||
|
int itemsPerPage = 10;
|
||||||
final Set<int> _inlineSavingIds = <int>{};
|
final Set<int> _inlineSavingIds = <int>{};
|
||||||
/// Per-policy payout editors so [setState] rebuilds do not reset focus/text.
|
/// Per-policy payout editors so [setState] rebuilds do not reset focus/text.
|
||||||
final Map<int, TextEditingController> _commissionControllers = {};
|
final Map<int, TextEditingController> _commissionControllers = {};
|
||||||
@ -184,6 +189,7 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
return TextFormField(
|
return TextFormField(
|
||||||
key: fieldKey,
|
key: fieldKey,
|
||||||
controller: controller,
|
controller: controller,
|
||||||
|
textAlign: TextAlign.right,
|
||||||
style: fieldStyle,
|
style: fieldStyle,
|
||||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||||
inputFormatters: _commissionInputFormatter,
|
inputFormatters: _commissionInputFormatter,
|
||||||
@ -195,6 +201,7 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
return TextFormField(
|
return TextFormField(
|
||||||
key: fieldKey,
|
key: fieldKey,
|
||||||
initialValue: (p["commission_amount"] ?? '').toString(),
|
initialValue: (p["commission_amount"] ?? '').toString(),
|
||||||
|
textAlign: TextAlign.right,
|
||||||
style: fieldStyle,
|
style: fieldStyle,
|
||||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||||
inputFormatters: _commissionInputFormatter,
|
inputFormatters: _commissionInputFormatter,
|
||||||
@ -206,6 +213,9 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
for (final controller in controllers.values) {
|
||||||
|
controller.dispose();
|
||||||
|
}
|
||||||
_disposeCommissionControllers();
|
_disposeCommissionControllers();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@ -217,6 +227,7 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
|
|
||||||
print('PD => filteredPolicies1 - $filteredPolicies');
|
print('PD => filteredPolicies1 - $filteredPolicies');
|
||||||
setState(() {
|
setState(() {
|
||||||
|
currentPage = 1;
|
||||||
if (query.trim().isEmpty) {
|
if (query.trim().isEmpty) {
|
||||||
print('PD => filteredPolicies2');
|
print('PD => filteredPolicies2');
|
||||||
filteredPolicies = List.from(masterPolicies);
|
filteredPolicies = List.from(masterPolicies);
|
||||||
@ -253,7 +264,6 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
for (var controller in controllers.values) {
|
for (var controller in controllers.values) {
|
||||||
controller.clear();
|
controller.clear();
|
||||||
controller.dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedBrokerID = '';
|
selectedBrokerID = '';
|
||||||
@ -262,6 +272,7 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
totalPolicies = '';
|
totalPolicies = '';
|
||||||
totalCommission = '';
|
totalCommission = '';
|
||||||
filteredPolicies = [];
|
filteredPolicies = [];
|
||||||
|
currentPage = 1;
|
||||||
_disposeCommissionControllers();
|
_disposeCommissionControllers();
|
||||||
hasFetchedTableData = false;
|
hasFetchedTableData = false;
|
||||||
// dropDownKeyPartner.currentState?.clear();
|
// dropDownKeyPartner.currentState?.clear();
|
||||||
@ -380,8 +391,9 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
hasFetchedTableData = false;
|
hasFetchedTableData = false;
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
final List<int> agentIds = (selectedAgentId ?? [])
|
final List<String> agentIds = (selectedAgentId ?? [])
|
||||||
.map((e) => int.parse(e))
|
.map((e) => e.toString().trim())
|
||||||
|
.where((e) => e.isNotEmpty)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
final jsondata = {
|
final jsondata = {
|
||||||
@ -400,6 +412,7 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
filteredPolicies = List<Map<String, dynamic>>.from(response['data']);
|
filteredPolicies = List<Map<String, dynamic>>.from(response['data']);
|
||||||
_rebuildCommissionControllers(filteredPolicies);
|
_rebuildCommissionControllers(filteredPolicies);
|
||||||
hasFetchedTableData = true;
|
hasFetchedTableData = true;
|
||||||
|
currentPage = 1;
|
||||||
print('PD =>API Data - $filteredPolicies');
|
print('PD =>API Data - $filteredPolicies');
|
||||||
// filteredPolicies = allPolicies.where((p) {
|
// filteredPolicies = allPolicies.where((p) {
|
||||||
// if (p["agentId"] != selectedAgentId) return false;
|
// if (p["agentId"] != selectedAgentId) return false;
|
||||||
@ -414,6 +427,7 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
filteredPolicies = [];
|
filteredPolicies = [];
|
||||||
_rebuildCommissionControllers(filteredPolicies);
|
_rebuildCommissionControllers(filteredPolicies);
|
||||||
hasFetchedTableData = true;
|
hasFetchedTableData = true;
|
||||||
|
currentPage = 1;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -422,6 +436,7 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
filteredPolicies = [];
|
filteredPolicies = [];
|
||||||
_rebuildCommissionControllers(filteredPolicies);
|
_rebuildCommissionControllers(filteredPolicies);
|
||||||
hasFetchedTableData = false;
|
hasFetchedTableData = false;
|
||||||
|
currentPage = 1;
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -450,13 +465,13 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
// Save invoice (simulation)
|
// Save invoice (simulation)
|
||||||
Future<void> saveInvoice() async {
|
Future<void> saveInvoice() async {
|
||||||
try {
|
try {
|
||||||
if (selectedAgentId == null) {
|
// if (selectedAgentId == null) {
|
||||||
ToastHelper.showWarningToast(
|
// ToastHelper.showWarningToast(
|
||||||
context,
|
// context,
|
||||||
"Please select a partner before saving.",
|
// "Please select a partner before saving.",
|
||||||
);
|
// );
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (selectedPolicies.isEmpty) {
|
if (selectedPolicies.isEmpty) {
|
||||||
ToastHelper.showWarningToast(
|
ToastHelper.showWarningToast(
|
||||||
@ -848,6 +863,30 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
|
ExportBtn(
|
||||||
|
sheetName: 'Payout Details',
|
||||||
|
fileName:
|
||||||
|
'payout_details_${DateFormat('yyyyMMdd_HHmmss').format(DateTime.now())}',
|
||||||
|
data: _exportRows,
|
||||||
|
displayHeaders: const [
|
||||||
|
'S.NO',
|
||||||
|
'POLICY NO',
|
||||||
|
'POLICY DATE',
|
||||||
|
'PARTNER',
|
||||||
|
'CUSTOMER',
|
||||||
|
'PREMIUM (₹)',
|
||||||
|
'PAYOUT (₹)',
|
||||||
|
],
|
||||||
|
keys: const [
|
||||||
|
'sno',
|
||||||
|
'policy_no',
|
||||||
|
'policy_date',
|
||||||
|
'partner',
|
||||||
|
'customer',
|
||||||
|
'premium',
|
||||||
|
'payout',
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -863,6 +902,7 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
role: roleId,
|
role: roleId,
|
||||||
id: userId,
|
id: userId,
|
||||||
showBrokerFilter: false,
|
showBrokerFilter: false,
|
||||||
|
policyTableHasRows: filteredPolicies.isNotEmpty,
|
||||||
selectedParnter: selectedAgentId,
|
selectedParnter: selectedAgentId,
|
||||||
selectedBroker: selectedBrokerID,
|
selectedBroker: selectedBrokerID,
|
||||||
|
|
||||||
@ -913,7 +953,10 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
secondChild: SingleChildScrollView(
|
secondChild: SingleChildScrollView(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: _tableMinWidth,
|
width: max(
|
||||||
|
_tableMinWidth,
|
||||||
|
MediaQuery.of(context).size.width - 32,
|
||||||
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
_policyTableHeader(), // static header
|
_policyTableHeader(), // static header
|
||||||
@ -926,7 +969,6 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
if (selectedPolicies.isNotEmpty) ...[
|
if (selectedPolicies.isNotEmpty) ...[
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
@ -937,59 +979,59 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// ---------------- SELECTED COUNT ----------------
|
// ---------------- SELECTED COUNT ----------------
|
||||||
Row(
|
// Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
// children: [
|
||||||
Text(
|
// Text(
|
||||||
// selectedPolicies.length.toString(),
|
// // selectedPolicies.length.toString(),
|
||||||
totalPolicies,
|
// totalPolicies,
|
||||||
style: GoogleFonts.poppins(
|
// style: GoogleFonts.poppins(
|
||||||
fontSize: 12,
|
// fontSize: 12,
|
||||||
fontWeight: FontWeight.w500,
|
// fontWeight: FontWeight.w500,
|
||||||
color: Colors.blueGrey,
|
// color: Colors.blueGrey,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
const SizedBox(width: 4),
|
// const SizedBox(width: 4),
|
||||||
Text(
|
// Text(
|
||||||
"policies selected",
|
// "policies selected",
|
||||||
style: GoogleFonts.poppins(
|
// style: GoogleFonts.poppins(
|
||||||
fontSize: 12,
|
// fontSize: 12,
|
||||||
fontWeight: FontWeight.w500,
|
// fontWeight: FontWeight.w500,
|
||||||
color: Colors.blueGrey,
|
// color: Colors.blueGrey,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
const SizedBox(width: 4),
|
// const SizedBox(width: 4),
|
||||||
Text(
|
// Text(
|
||||||
"|",
|
// "|",
|
||||||
style: GoogleFonts.poppins(
|
// style: GoogleFonts.poppins(
|
||||||
fontSize: 12,
|
// fontSize: 12,
|
||||||
fontWeight: FontWeight.w500,
|
// fontWeight: FontWeight.w500,
|
||||||
color: Colors.blueGrey,
|
// color: Colors.blueGrey,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
const SizedBox(width: 4),
|
// const SizedBox(width: 4),
|
||||||
Text(
|
// Text(
|
||||||
"Total Payout Amount",
|
// "Total Payout Amount",
|
||||||
style: GoogleFonts.poppins(
|
// style: GoogleFonts.poppins(
|
||||||
fontSize: 12,
|
// fontSize: 12,
|
||||||
fontWeight: FontWeight.w500,
|
// fontWeight: FontWeight.w500,
|
||||||
color: Colors.blueGrey,
|
// color: Colors.blueGrey,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
const SizedBox(width: 4),
|
// const SizedBox(width: 4),
|
||||||
|
//
|
||||||
/// NULL-SAFE TOTAL COMMISSION CALCULATION
|
// /// NULL-SAFE TOTAL COMMISSION CALCULATION
|
||||||
Text(
|
// Text(
|
||||||
// "₹ ${_calculateTotalCommission()}",
|
// // "₹ ${_calculateTotalCommission()}",
|
||||||
"₹ $totalCommission",
|
// "₹ $totalCommission",
|
||||||
style: GoogleFonts.poppins(
|
// style: GoogleFonts.poppins(
|
||||||
fontSize: 14,
|
// fontSize: 14,
|
||||||
fontWeight: FontWeight.w500,
|
// fontWeight: FontWeight.w500,
|
||||||
color: Colors.teal,
|
// color: Colors.teal,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
|
|
||||||
Spacer(),
|
Spacer(),
|
||||||
buildUTRNumber(context),
|
buildUTRNumber(context),
|
||||||
@ -1036,6 +1078,27 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
if (filteredPolicies.isNotEmpty)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(8, 4, 8, 2),
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: PaginationControls(
|
||||||
|
currentPage: currentPage,
|
||||||
|
itemsPerPage: itemsPerPage,
|
||||||
|
totalItems: filteredPolicies.length,
|
||||||
|
onPageChanged: (page) {
|
||||||
|
setState(() => currentPage = page);
|
||||||
|
},
|
||||||
|
onItemsPerPageChanged: (items) {
|
||||||
|
setState(() {
|
||||||
|
itemsPerPage = items;
|
||||||
|
currentPage = 1;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
// ----------------------
|
// ----------------------
|
||||||
// const SizedBox(height: 100),
|
// const SizedBox(height: 100),
|
||||||
],
|
],
|
||||||
@ -1101,22 +1164,25 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
_headerText("S.NO", colSno, align: TextAlign.center),
|
||||||
_headerText("POLICY NO", colPolicy),
|
_headerText("POLICY NO", colPolicy),
|
||||||
_headerText("POLICY DATE", colDate),
|
_headerText("POLICY DATE", colDate),
|
||||||
_headerText("PARTNER", colAgent),
|
_headerText("PARTNER", colAgent),
|
||||||
_headerText("CUSTOMER", colCustomer),
|
_headerText("CUSTOMER", colCustomer),
|
||||||
_headerText("PREMIUM", colPremium),
|
_headerText("PREMIUM (₹)", colPremium, align: TextAlign.right),
|
||||||
_headerText("PAYOUT", colCommission),
|
const SizedBox(width: 10),
|
||||||
|
_headerText("PAYOUT (₹)", colCommission, align: TextAlign.right),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _headerText(String text, double width) {
|
Widget _headerText(String text, double width, {TextAlign align = TextAlign.left}) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: width,
|
width: width,
|
||||||
child: Text(
|
child: Text(
|
||||||
text,
|
text,
|
||||||
|
textAlign: align,
|
||||||
style: GoogleFonts.poppins(
|
style: GoogleFonts.poppins(
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
@ -1128,12 +1194,15 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _policyTableBody() {
|
Widget _policyTableBody() {
|
||||||
|
final pageRows = _paginatedPolicies;
|
||||||
|
final startIndex =
|
||||||
|
filteredPolicies.isEmpty ? 0 : (currentPage - 1) * itemsPerPage;
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: MediaQuery.of(context).size.height * 0.48,
|
height: MediaQuery.of(context).size.height * 0.48,
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
itemCount: filteredPolicies.length,
|
itemCount: pageRows.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final p = filteredPolicies[index];
|
final p = pageRows[index];
|
||||||
|
|
||||||
final int id = int.tryParse(p["policy_id"]?.toString() ?? "0") ?? 0;
|
final int id = int.tryParse(p["policy_id"]?.toString() ?? "0") ?? 0;
|
||||||
final bool isSelected = selectedPolicies.contains(id);
|
final bool isSelected = selectedPolicies.contains(id);
|
||||||
@ -1175,6 +1244,11 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
_cell(
|
||||||
|
'${startIndex + index + 1}',
|
||||||
|
colSno,
|
||||||
|
align: TextAlign.center,
|
||||||
|
),
|
||||||
_cell(p["policy_no"], colPolicy),
|
_cell(p["policy_no"], colPolicy),
|
||||||
// _cell(p["issued_date"] ?? "-", colDate),
|
// _cell(p["issued_date"] ?? "-", colDate),
|
||||||
_cell(_formatDate(p['issued_date']), colDate),
|
_cell(_formatDate(p['issued_date']), colDate),
|
||||||
@ -1186,20 +1260,16 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
colAgent,
|
colAgent,
|
||||||
),
|
),
|
||||||
_cell(p["customer_name"], colCustomer),
|
_cell(p["customer_name"], colCustomer),
|
||||||
_cell("₹${p["premium_amount"]}", colPremium),
|
_cell(
|
||||||
|
_amountText(p["premium_amount"]),
|
||||||
|
colPremium,
|
||||||
|
align: TextAlign.right,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: colCommission,
|
width: colCommission,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
const Text(
|
|
||||||
"₹",
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 11,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: Color(0xFF009B77),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 2),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _commissionAmountField(
|
child: _commissionAmountField(
|
||||||
p,
|
p,
|
||||||
@ -1218,6 +1288,35 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _amountText(dynamic value) {
|
||||||
|
final raw = (value ?? '').toString().replaceAll('₹', '').trim();
|
||||||
|
if (raw.isEmpty || raw.toLowerCase() == 'null') return '-';
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Map<String, dynamic>> get _paginatedPolicies {
|
||||||
|
if (filteredPolicies.isEmpty) return const <Map<String, dynamic>>[];
|
||||||
|
final start = (currentPage - 1) * itemsPerPage;
|
||||||
|
final end = (start + itemsPerPage).clamp(0, filteredPolicies.length);
|
||||||
|
return filteredPolicies.sublist(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Map<String, dynamic>> get _exportRows {
|
||||||
|
return filteredPolicies.map((p) {
|
||||||
|
return {
|
||||||
|
'sno': 0,
|
||||||
|
'policy_no': p['policy_no'],
|
||||||
|
'policy_date': _formatDate((p['issued_date'] ?? '').toString()),
|
||||||
|
'partner': (p["agent_code"] != null && p["agent_name"] != null)
|
||||||
|
? '${p["agent_code"]} - ${p["agent_name"]}'
|
||||||
|
: (p["agent_name"] ?? '-').toString(),
|
||||||
|
'customer': p['customer_name'],
|
||||||
|
'premium': _amountText(p['premium_amount']),
|
||||||
|
'payout': _amountText(p['commission_amount']),
|
||||||
|
};
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
double _parseCommissionValue(dynamic value) {
|
double _parseCommissionValue(dynamic value) {
|
||||||
if (value == null) return 0;
|
if (value == null) return 0;
|
||||||
return double.tryParse(value.toString().trim()) ?? 0;
|
return double.tryParse(value.toString().trim()) ?? 0;
|
||||||
@ -1300,11 +1399,16 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _cell(String? value, double width) {
|
Widget _cell(
|
||||||
|
dynamic value,
|
||||||
|
double width, {
|
||||||
|
TextAlign align = TextAlign.left,
|
||||||
|
}) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: width,
|
width: width,
|
||||||
child: Text(
|
child: Text(
|
||||||
value ?? "-",
|
(value ?? "-").toString(),
|
||||||
|
textAlign: align,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: const TextStyle(fontSize: 11),
|
style: const TextStyle(fontSize: 11),
|
||||||
),
|
),
|
||||||
@ -1426,20 +1530,24 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static final double colCheck = 44;
|
static final double colCheck = 44;
|
||||||
|
static final double colSno = 60;
|
||||||
static final double colPolicy = 200;
|
static final double colPolicy = 200;
|
||||||
static final double colDate = 200;
|
static final double colDate = 200;
|
||||||
static final double colAgent = 200;
|
static final double colAgent = 400;
|
||||||
static final double colCustomer = 200;
|
static final double colCustomer = 200;
|
||||||
static final double colPremium = 200;
|
static final double colPremium = 200;
|
||||||
static final double colCommission = 200;
|
static final double colPremiumPayoutGap = 10;
|
||||||
|
static final double colCommission = 150;
|
||||||
|
|
||||||
double get _tableMinWidth =>
|
double get _tableMinWidth =>
|
||||||
colCheck +
|
colCheck +
|
||||||
|
colSno +
|
||||||
colPolicy +
|
colPolicy +
|
||||||
colDate +
|
colDate +
|
||||||
colAgent +
|
colAgent +
|
||||||
colCustomer +
|
colCustomer +
|
||||||
colPremium +
|
colPremium +
|
||||||
|
colPremiumPayoutGap +
|
||||||
colCommission +
|
colCommission +
|
||||||
32;
|
32;
|
||||||
}
|
}
|
||||||
@ -5,11 +5,13 @@ import 'package:go_router/go_router.dart';
|
|||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
import '../../../data/utils/Pagination.dart';
|
||||||
import '../../../core/routing/routes.dart';
|
import '../../../core/routing/routes.dart';
|
||||||
import '../../../core/services/api_service.dart';
|
import '../../../core/services/api_service.dart';
|
||||||
import '../../layouts/main_layout.dart';
|
import '../../layouts/main_layout.dart';
|
||||||
import '../../layouts/responsive_layout.dart';
|
import '../../layouts/responsive_layout.dart';
|
||||||
import '../../providers/manager_provider.dart';
|
import '../../providers/manager_provider.dart';
|
||||||
|
import '../../themes/indicators/export_btn.dart';
|
||||||
import '../../themes/indicators/search_field_theme.dart';
|
import '../../themes/indicators/search_field_theme.dart';
|
||||||
|
|
||||||
/// Read-only payout invoice + policy lines (opened from payout list “View”).
|
/// Read-only payout invoice + policy lines (opened from payout list “View”).
|
||||||
@ -35,15 +37,8 @@ class _PayOutDetailsViewState extends ConsumerState<PayOutDetailsView> {
|
|||||||
dynamic totalPolicies = '0';
|
dynamic totalPolicies = '0';
|
||||||
dynamic totalCommission = '0';
|
dynamic totalCommission = '0';
|
||||||
|
|
||||||
static const double colPolicy = 150;
|
int _rowsPerPage = 10;
|
||||||
static const double colDate = 120;
|
int _currentPage = 1;
|
||||||
static const double colAgent = 200;
|
|
||||||
static const double colCustomer = 200;
|
|
||||||
static const double colPremium = 200;
|
|
||||||
static const double colCommission = 120;
|
|
||||||
|
|
||||||
double get _tableMinWidth =>
|
|
||||||
colPolicy + colDate + colAgent + colCustomer + colPremium + colCommission + 32;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -114,6 +109,7 @@ class _PayOutDetailsViewState extends ConsumerState<PayOutDetailsView> {
|
|||||||
totalCommission = totalCommiss;
|
totalCommission = totalCommiss;
|
||||||
masterPolicies = items;
|
masterPolicies = items;
|
||||||
filteredPolicies = List<Map<String, dynamic>>.from(items);
|
filteredPolicies = List<Map<String, dynamic>>.from(items);
|
||||||
|
_currentPage = 1;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -132,22 +128,21 @@ class _PayOutDetailsViewState extends ConsumerState<PayOutDetailsView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void filterPolicyData(String query) {
|
void filterPolicyData(String query) {
|
||||||
final lowerQuery = query.toLowerCase();
|
final q = query.trim();
|
||||||
setState(() {
|
setState(() {
|
||||||
if (query.trim().isEmpty) {
|
_currentPage = 1;
|
||||||
|
if (q.isEmpty) {
|
||||||
filteredPolicies = List.from(masterPolicies);
|
filteredPolicies = List.from(masterPolicies);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
filteredPolicies = masterPolicies.where((item) {
|
filteredPolicies = masterPolicies.where((item) {
|
||||||
return (item['policy_no'] ?? '').toString().toLowerCase().contains(lowerQuery) ||
|
return (item['policy_no'] ?? '').toString().contains(q) ||
|
||||||
(item['customer_name'] ?? '').toString().toLowerCase().contains(lowerQuery) ||
|
(item['customer_name'] ?? '').toString().contains(q) ||
|
||||||
(item['agent_name'] ?? '').toString().toLowerCase().contains(lowerQuery) ||
|
(item['agent_name'] ?? '').toString().contains(q) ||
|
||||||
(item['insurer_name'] ?? '').toString().toLowerCase().contains(lowerQuery) ||
|
(item['insurer_name'] ?? '').toString().contains(q) ||
|
||||||
(item['premium_amount'] ?? '').toString().toLowerCase().contains(lowerQuery) ||
|
(item['premium_amount'] ?? '').toString().contains(q) ||
|
||||||
(item['commission_amount'] ?? '').toString().toLowerCase().contains(lowerQuery) ||
|
(item['commission_amount'] ?? '').toString().contains(q) ||
|
||||||
_formatDate(item['issued_date']?.toString() ?? '')
|
_formatDate(item['issued_date']?.toString() ?? '').contains(q);
|
||||||
.toLowerCase()
|
|
||||||
.contains(lowerQuery);
|
|
||||||
}).toList();
|
}).toList();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -182,11 +177,37 @@ class _PayOutDetailsViewState extends ConsumerState<PayOutDetailsView> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _headerText(String text, double width) {
|
String _amountText(dynamic value) {
|
||||||
|
final text = _displayText(value);
|
||||||
|
if (text == '-') return '-';
|
||||||
|
return text.replaceAll('₹', '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<double> _columnWidths(double maxWidth) {
|
||||||
|
const sidePadding = 32.0; // 16 left + 16 right
|
||||||
|
final usable = (maxWidth - sidePadding).clamp(720.0, 2000.0);
|
||||||
|
const totalFlex = 2 + 14 + 10 + 20 + 16 + 11 + 11;
|
||||||
|
return [
|
||||||
|
usable * (2 / totalFlex), // S.NO
|
||||||
|
usable * (14 / totalFlex), // POLICY NO
|
||||||
|
usable * (10 / totalFlex), // POLICY DATE
|
||||||
|
usable * (20 / totalFlex), // PARTNER
|
||||||
|
usable * (16 / totalFlex), // CUSTOMER
|
||||||
|
usable * (11 / totalFlex), // PREMIUM
|
||||||
|
usable * (11 / totalFlex), // PAYOUT
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _headerText(
|
||||||
|
String text,
|
||||||
|
double width, {
|
||||||
|
TextAlign align = TextAlign.left,
|
||||||
|
}) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: width,
|
width: width,
|
||||||
child: Text(
|
child: Text(
|
||||||
text,
|
text,
|
||||||
|
textAlign: align,
|
||||||
style: GoogleFonts.poppins(
|
style: GoogleFonts.poppins(
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
@ -197,18 +218,29 @@ class _PayOutDetailsViewState extends ConsumerState<PayOutDetailsView> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _cell(String? value, double width) {
|
Widget _cell(
|
||||||
|
String? value,
|
||||||
|
double width, {
|
||||||
|
TextAlign align = TextAlign.left,
|
||||||
|
Color? color,
|
||||||
|
FontWeight? fontWeight,
|
||||||
|
}) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: width,
|
width: width,
|
||||||
child: Text(
|
child: Text(
|
||||||
value ?? '-',
|
value ?? '-',
|
||||||
|
textAlign: align,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: const TextStyle(fontSize: 11),
|
style: TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
color: color,
|
||||||
|
fontWeight: fontWeight,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _policyTableHeader() {
|
Widget _policyTableHeader(List<double> w) {
|
||||||
return Container(
|
return Container(
|
||||||
height: 30,
|
height: 30,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
@ -218,18 +250,19 @@ class _PayOutDetailsViewState extends ConsumerState<PayOutDetailsView> {
|
|||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
_headerText('POLICY NO', colPolicy),
|
_headerText('S.NO', w[0]),
|
||||||
_headerText('POLICY DATE', colDate),
|
_headerText('POLICY NO', w[1]),
|
||||||
_headerText('PARTNER', colAgent),
|
_headerText('POLICY DATE', w[2]),
|
||||||
_headerText('CUSTOMER', colCustomer),
|
_headerText('PARTNER', w[3]),
|
||||||
_headerText('PREMIUM', colPremium),
|
_headerText('CUSTOMER', w[4]),
|
||||||
_headerText('PAYOUT', colCommission),
|
_headerText('PREMIUM (₹)', w[5], align: TextAlign.right),
|
||||||
|
_headerText('PAYOUT (₹)', w[6], align: TextAlign.right),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _policyRow(Map<String, dynamic> p) {
|
Widget _policyRow(Map<String, dynamic> p, int index, List<double> w) {
|
||||||
return Container(
|
return Container(
|
||||||
height: 38,
|
height: 38,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
@ -239,33 +272,66 @@ class _PayOutDetailsViewState extends ConsumerState<PayOutDetailsView> {
|
|||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
_cell(p['policy_no']?.toString(), colPolicy),
|
_cell('${index + 1}', w[0]),
|
||||||
_cell(_formatDate(p['issued_date']?.toString()), colDate),
|
_cell(p['policy_no']?.toString(), w[1]),
|
||||||
|
_cell(_formatDate(p['issued_date']?.toString()), w[2]),
|
||||||
_cell(
|
_cell(
|
||||||
(p['agent_code'] != null && p['agent_name'] != null)
|
(p['agent_code'] != null && p['agent_name'] != null)
|
||||||
? '${p['agent_code']} - ${p['agent_name']}'
|
? '${p['agent_code']} - ${p['agent_name']}'
|
||||||
: (p['agent_name'] ?? '-').toString(),
|
: (p['agent_name'] ?? '-').toString(),
|
||||||
colAgent,
|
w[3],
|
||||||
),
|
),
|
||||||
_cell(p['customer_name']?.toString(), colCustomer),
|
_cell(p['customer_name']?.toString(), w[4]),
|
||||||
_cell('₹${p['premium_amount']}', colPremium),
|
_cell(
|
||||||
SizedBox(
|
_amountText(p['premium_amount']),
|
||||||
width: colCommission,
|
w[5],
|
||||||
child: Text(
|
align: TextAlign.right,
|
||||||
'₹${_displayText(p['commission_amount'])}',
|
),
|
||||||
overflow: TextOverflow.ellipsis,
|
_cell(
|
||||||
style: const TextStyle(
|
_amountText(p['commission_amount']),
|
||||||
fontSize: 11,
|
w[6],
|
||||||
fontWeight: FontWeight.w600,
|
align: TextAlign.right,
|
||||||
color: Color(0xFF009B77),
|
color: const Color(0xFF009B77),
|
||||||
),
|
fontWeight: FontWeight.w600,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int get _totalPages {
|
||||||
|
if (filteredPolicies.isEmpty) return 1;
|
||||||
|
return (filteredPolicies.length / _rowsPerPage).ceil();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Map<String, dynamic>> get _paginatedPolicies {
|
||||||
|
if (filteredPolicies.isEmpty) return const <Map<String, dynamic>>[];
|
||||||
|
final start = (_currentPage - 1) * _rowsPerPage;
|
||||||
|
final end = (start + _rowsPerPage).clamp(0, filteredPolicies.length);
|
||||||
|
return filteredPolicies.sublist(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _goToPage(int page) {
|
||||||
|
if (page < 1 || page > _totalPages) return;
|
||||||
|
setState(() => _currentPage = page);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Map<String, dynamic>> get _exportRows {
|
||||||
|
return filteredPolicies.map((p) {
|
||||||
|
return {
|
||||||
|
'sno': 0,
|
||||||
|
'policy_no': p['policy_no'],
|
||||||
|
'policy_date': _formatDate(p['issued_date']?.toString()),
|
||||||
|
'partner': (p['agent_code'] != null && p['agent_name'] != null)
|
||||||
|
? '${p['agent_code']} - ${p['agent_name']}'
|
||||||
|
: (p['agent_name'] ?? '-').toString(),
|
||||||
|
'customer': p['customer_name'],
|
||||||
|
'premium': _amountText(p['premium_amount']),
|
||||||
|
'payout': _amountText(p['commission_amount']),
|
||||||
|
};
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final statusRaw = widget.viewItem['payout_status'];
|
final statusRaw = widget.viewItem['payout_status'];
|
||||||
@ -356,6 +422,31 @@ class _PayOutDetailsViewState extends ConsumerState<PayOutDetailsView> {
|
|||||||
? MediaQuery.of(context).size.width * 0.7
|
? MediaQuery.of(context).size.width * 0.7
|
||||||
: MediaQuery.of(context).size.width * 0.15,
|
: MediaQuery.of(context).size.width * 0.15,
|
||||||
),
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
ExportBtn(
|
||||||
|
sheetName: 'Payout Details',
|
||||||
|
fileName:
|
||||||
|
'payout_details_${DateFormat('yyyyMMdd_HHmmss').format(DateTime.now())}',
|
||||||
|
data: _exportRows,
|
||||||
|
displayHeaders: const [
|
||||||
|
'S.NO',
|
||||||
|
'POLICY NO',
|
||||||
|
'POLICY DATE',
|
||||||
|
'PARTNER',
|
||||||
|
'CUSTOMER',
|
||||||
|
'PREMIUM (₹)',
|
||||||
|
'PAYOUT (₹)',
|
||||||
|
],
|
||||||
|
keys: const [
|
||||||
|
'sno',
|
||||||
|
'policy_no',
|
||||||
|
'policy_date',
|
||||||
|
'partner',
|
||||||
|
'customer',
|
||||||
|
'premium',
|
||||||
|
'payout',
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -377,45 +468,38 @@ class _PayOutDetailsViewState extends ConsumerState<PayOutDetailsView> {
|
|||||||
),
|
),
|
||||||
child: LayoutBuilder(
|
child: LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
// Same pattern as enquiry list: nested axis scroll views
|
final widths = _columnWidths(constraints.maxWidth);
|
||||||
// avoids broken vertical gestures on web/desktop.
|
final rows = _paginatedPolicies;
|
||||||
return SingleChildScrollView(
|
final startIdx = (filteredPolicies.isEmpty)
|
||||||
scrollDirection: Axis.horizontal,
|
? 0
|
||||||
primary: false,
|
: ((_currentPage - 1) * _rowsPerPage);
|
||||||
child: SingleChildScrollView(
|
|
||||||
scrollDirection: Axis.vertical,
|
return Column(
|
||||||
primary: false,
|
children: [
|
||||||
child: ConstrainedBox(
|
_policyTableHeader(widths),
|
||||||
constraints: BoxConstraints(
|
Expanded(
|
||||||
minWidth: _tableMinWidth,
|
child: filteredPolicies.isEmpty
|
||||||
minHeight: constraints.maxHeight.isFinite
|
? const Center(
|
||||||
? constraints.maxHeight
|
child: Text(
|
||||||
: 0,
|
'No policies in this payout',
|
||||||
),
|
style: TextStyle(
|
||||||
child: Column(
|
fontSize: 12,
|
||||||
mainAxisSize: MainAxisSize.min,
|
color: Colors.blueGrey,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
_policyTableHeader(),
|
|
||||||
if (filteredPolicies.isEmpty)
|
|
||||||
const Padding(
|
|
||||||
padding: EdgeInsets.all(24),
|
|
||||||
child: Center(
|
|
||||||
child: Text(
|
|
||||||
'No policies in this payout',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: Colors.blueGrey,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
else
|
: ListView.builder(
|
||||||
...filteredPolicies.map(_policyRow),
|
itemCount: rows.length,
|
||||||
],
|
itemBuilder: (context, index) {
|
||||||
),
|
return _policyRow(
|
||||||
|
rows[index],
|
||||||
|
startIdx + index,
|
||||||
|
widths,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -438,7 +522,7 @@ class _PayOutDetailsViewState extends ConsumerState<PayOutDetailsView> {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
'policies',
|
'Policies',
|
||||||
style: GoogleFonts.poppins(
|
style: GoogleFonts.poppins(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
@ -475,6 +559,25 @@ class _PayOutDetailsViewState extends ConsumerState<PayOutDetailsView> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (filteredPolicies.isNotEmpty)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(8, 4, 8, 2),
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: PaginationControls(
|
||||||
|
currentPage: _currentPage,
|
||||||
|
itemsPerPage: _rowsPerPage,
|
||||||
|
totalItems: filteredPolicies.length,
|
||||||
|
onPageChanged: (page) => _goToPage(page),
|
||||||
|
onItemsPerPageChanged: (items) {
|
||||||
|
setState(() {
|
||||||
|
_rowsPerPage = items;
|
||||||
|
_currentPage = 1;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import '../../layouts/main_layout.dart';
|
|||||||
import '../../layouts/responsive_layout.dart';
|
import '../../layouts/responsive_layout.dart';
|
||||||
import '../../providers/manager_provider.dart';
|
import '../../providers/manager_provider.dart';
|
||||||
import '../../providers/userRoleProvider.dart';
|
import '../../providers/userRoleProvider.dart';
|
||||||
import '../../themes/indicators/date_field_theme.dart';
|
import '../../themes/indicators/date_range_picker_field.dart';
|
||||||
import '../../themes/indicators/export_btn.dart';
|
import '../../themes/indicators/export_btn.dart';
|
||||||
import '../../themes/indicators/search_field_theme.dart';
|
import '../../themes/indicators/search_field_theme.dart';
|
||||||
|
|
||||||
@ -69,6 +69,25 @@ class _PayoutReportScreenState extends ConsumerState<PayoutReportScreen> {
|
|||||||
(s.length > 1 ? s.substring(1).toLowerCase() : '');
|
(s.length > 1 ? s.substring(1).toLowerCase() : '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _isAccountsReceivedRole() {
|
||||||
|
final role = (ref.read(userRoleProvider) ?? '')
|
||||||
|
.toString()
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
.replaceAll(RegExp(r'[\s_\-]+'), '');
|
||||||
|
return role == 'accountsreceived' || role == 'accounts';
|
||||||
|
}
|
||||||
|
|
||||||
|
String _receivedDisplayLabel(dynamic v) {
|
||||||
|
final raw = v?.toString().trim().toLowerCase() ?? '';
|
||||||
|
if (_isAccountsReceivedRole()) {
|
||||||
|
if (raw == 'yes' || raw == 'received' || raw == 'paid') {
|
||||||
|
return 'Paid';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _receivedLabel(v);
|
||||||
|
}
|
||||||
|
|
||||||
String? _agentIdForApi() {
|
String? _agentIdForApi() {
|
||||||
if (_isAgentRole()) {
|
if (_isAgentRole()) {
|
||||||
final uid = ref.read(userIdProvider);
|
final uid = ref.read(userIdProvider);
|
||||||
@ -689,7 +708,7 @@ class _PayoutReportScreenState extends ConsumerState<PayoutReportScreen> {
|
|||||||
valueColor: const Color(0xFF37474F),
|
valueColor: const Color(0xFF37474F),
|
||||||
),
|
),
|
||||||
_summaryChip(
|
_summaryChip(
|
||||||
label: 'Raised',
|
label: 'Paid',
|
||||||
value: _fmtSummaryAmount(_summaryRaised),
|
value: _fmtSummaryAmount(_summaryRaised),
|
||||||
bg: const Color(0xFFE0F2F1),
|
bg: const Color(0xFFE0F2F1),
|
||||||
border: const Color(0xFF80CBC4),
|
border: const Color(0xFF80CBC4),
|
||||||
@ -787,7 +806,7 @@ class _PayoutReportScreenState extends ConsumerState<PayoutReportScreen> {
|
|||||||
'Premium',
|
'Premium',
|
||||||
'Payout',
|
'Payout',
|
||||||
'UTR No',
|
'UTR No',
|
||||||
'Received',
|
'Paid/Received',
|
||||||
],
|
],
|
||||||
keys: const [
|
keys: const [
|
||||||
'sno',
|
'sno',
|
||||||
@ -802,7 +821,7 @@ class _PayoutReportScreenState extends ConsumerState<PayoutReportScreen> {
|
|||||||
'received_or_not',
|
'received_or_not',
|
||||||
],
|
],
|
||||||
valueFormatters: {
|
valueFormatters: {
|
||||||
'received_or_not': (v, _) => _receivedLabel(v),
|
'received_or_not': (v, _) => _receivedDisplayLabel(v),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
@ -815,27 +834,6 @@ class _PayoutReportScreenState extends ConsumerState<PayoutReportScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDateField({
|
|
||||||
required TextEditingController controller,
|
|
||||||
required String hintText,
|
|
||||||
required bool isStart,
|
|
||||||
}) {
|
|
||||||
return SizedBox(
|
|
||||||
height: 35,
|
|
||||||
child: ThemedDateField(
|
|
||||||
hintText: hintText,
|
|
||||||
txtwidth: ResponsiveLayout.isMobile(context)
|
|
||||||
? null
|
|
||||||
: MediaQuery.of(context).size.width * 0.1,
|
|
||||||
controller: controller,
|
|
||||||
lastDate: isStart ? null : DateTime.now(),
|
|
||||||
onDateSelected: (date) {
|
|
||||||
controller.text = DateFormat('dd-MM-yyyy').format(date);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _filterControlsRow(BuildContext context) {
|
Widget _filterControlsRow(BuildContext context) {
|
||||||
final spacing = 5.0;
|
final spacing = 5.0;
|
||||||
final isMobile = ResponsiveLayout.isMobile(context);
|
final isMobile = ResponsiveLayout.isMobile(context);
|
||||||
@ -845,16 +843,16 @@ class _PayoutReportScreenState extends ConsumerState<PayoutReportScreen> {
|
|||||||
final fields = Row(
|
final fields = Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_buildDateField(
|
DateRangePickerField(
|
||||||
controller: _startController,
|
startController: _startController,
|
||||||
hintText: 'Select Date',
|
endController: _endController,
|
||||||
isStart: true,
|
hintText: 'Select date range',
|
||||||
),
|
showLabelAboveField: false,
|
||||||
SizedBox(width: spacing),
|
txtwidth: isMobile
|
||||||
_buildDateField(
|
? null
|
||||||
controller: _endController,
|
: MediaQuery.of(context).size.width * 0.15,
|
||||||
hintText: 'Select Date',
|
lastDate: DateTime.now(),
|
||||||
isStart: false,
|
useFormField: false,
|
||||||
),
|
),
|
||||||
if (showAgentFilter) ...[
|
if (showAgentFilter) ...[
|
||||||
SizedBox(width: spacing),
|
SizedBox(width: spacing),
|
||||||
@ -1012,7 +1010,13 @@ class _PayoutReportScreenState extends ConsumerState<PayoutReportScreen> {
|
|||||||
child: Text('PAYOUT', style: _headerColumn, textAlign: TextAlign.right),
|
child: Text('PAYOUT', style: _headerColumn, textAlign: TextAlign.right),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 15),
|
const SizedBox(width: 15),
|
||||||
Expanded(flex: 2, child: Text('RECEIVED', style: _headerColumn)),
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Text(
|
||||||
|
_isAccountsReceivedRole() ? 'PAID' : 'RECEIVED',
|
||||||
|
style: _headerColumn,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -1114,7 +1118,7 @@ class _PayoutReportScreenState extends ConsumerState<PayoutReportScreen> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: Text(
|
child: Text(
|
||||||
_receivedLabel(item['received_or_not']),
|
_receivedDisplayLabel(item['received_or_not']),
|
||||||
style: rowStyle,
|
style: rowStyle,
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
|
|||||||
446
lib/presentation/themes/indicators/date_range_picker_dialog.dart
Normal file
446
lib/presentation/themes/indicators/date_range_picker_dialog.dart
Normal file
@ -0,0 +1,446 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
/// Compact date-range dialog (centered card, not full-screen on web).
|
||||||
|
/// From/To header, month navigation, range highlight, Cancel / Apply.
|
||||||
|
Future<DateTimeRange?> showCompactDateRangePicker({
|
||||||
|
required BuildContext context,
|
||||||
|
required DateTime firstDate,
|
||||||
|
required DateTime lastDate,
|
||||||
|
DateTimeRange? initialRange,
|
||||||
|
}) {
|
||||||
|
return showDialog<DateTimeRange>(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: true,
|
||||||
|
builder: (ctx) => _CompactDateRangeDialog(
|
||||||
|
firstDate: DateTime(firstDate.year, firstDate.month, firstDate.day),
|
||||||
|
lastDate: DateTime(lastDate.year, lastDate.month, lastDate.day),
|
||||||
|
initialRange: initialRange,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CompactDateRangeDialog extends StatefulWidget {
|
||||||
|
const _CompactDateRangeDialog({
|
||||||
|
required this.firstDate,
|
||||||
|
required this.lastDate,
|
||||||
|
this.initialRange,
|
||||||
|
});
|
||||||
|
|
||||||
|
final DateTime firstDate;
|
||||||
|
final DateTime lastDate;
|
||||||
|
final DateTimeRange? initialRange;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_CompactDateRangeDialog> createState() => _CompactDateRangeDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CompactDateRangeDialogState extends State<_CompactDateRangeDialog> {
|
||||||
|
static const _primary = Color(0xFF2E7D6E);
|
||||||
|
static const _primaryLight = Color(0xFFE6F4F1);
|
||||||
|
static const _muted = Color(0xFF64748B);
|
||||||
|
|
||||||
|
late DateTime _focusedMonth;
|
||||||
|
DateTime? _start;
|
||||||
|
DateTime? _end;
|
||||||
|
|
||||||
|
final _headerFrom = DateFormat('EEE, dd MMM');
|
||||||
|
final _headerMonth = DateFormat('MMMM yyyy');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
final ir = widget.initialRange;
|
||||||
|
if (ir != null) {
|
||||||
|
_start = DateTime(ir.start.year, ir.start.month, ir.start.day);
|
||||||
|
_end = DateTime(ir.end.year, ir.end.month, ir.end.day);
|
||||||
|
_focusedMonth = DateTime(_start!.year, _start!.month, 1);
|
||||||
|
} else {
|
||||||
|
final now = DateTime.now();
|
||||||
|
final n = DateTime(now.year, now.month, now.day);
|
||||||
|
_focusedMonth = DateTime(n.year, n.month, 1);
|
||||||
|
if (n.isBefore(widget.firstDate)) {
|
||||||
|
_focusedMonth = DateTime(widget.firstDate.year, widget.firstDate.month, 1);
|
||||||
|
} else if (n.isAfter(widget.lastDate)) {
|
||||||
|
_focusedMonth = DateTime(widget.lastDate.year, widget.lastDate.month, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _sameDay(DateTime a, DateTime b) =>
|
||||||
|
a.year == b.year && a.month == b.month && a.day == b.day;
|
||||||
|
|
||||||
|
DateTime _norm(DateTime d) => DateTime(d.year, d.month, d.day);
|
||||||
|
|
||||||
|
bool _inSelectedRange(DateTime d) {
|
||||||
|
if (_start == null) return false;
|
||||||
|
final x = _norm(d);
|
||||||
|
final s = _norm(_start!);
|
||||||
|
final e = _norm(_end ?? _start!);
|
||||||
|
return !x.isBefore(s) && !x.isAfter(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isRangeStart(DateTime d) => _start != null && _sameDay(d, _start!);
|
||||||
|
|
||||||
|
bool _isRangeEnd(DateTime d) {
|
||||||
|
if (_start == null) return false;
|
||||||
|
final end = _end ?? _start!;
|
||||||
|
return _sameDay(d, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onDayTap(DateTime day) {
|
||||||
|
final d = _norm(day);
|
||||||
|
if (d.isBefore(widget.firstDate) || d.isAfter(widget.lastDate)) return;
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
if (_start == null || (_start != null && _end != null)) {
|
||||||
|
_start = d;
|
||||||
|
_end = null;
|
||||||
|
} else {
|
||||||
|
if (d.isBefore(_start!)) {
|
||||||
|
_end = _start;
|
||||||
|
_start = d;
|
||||||
|
} else {
|
||||||
|
_end = d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get _canPrev {
|
||||||
|
final lastOfPrevMonth = DateTime(_focusedMonth.year, _focusedMonth.month, 0);
|
||||||
|
return !lastOfPrevMonth.isBefore(widget.firstDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get _canNext {
|
||||||
|
final firstOfNextMonth = DateTime(_focusedMonth.year, _focusedMonth.month + 1, 1);
|
||||||
|
return !firstOfNextMonth.isAfter(widget.lastDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _prevMonth() {
|
||||||
|
if (!_canPrev) return;
|
||||||
|
setState(() {
|
||||||
|
_focusedMonth = DateTime(_focusedMonth.year, _focusedMonth.month - 1, 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _nextMonth() {
|
||||||
|
if (!_canNext) return;
|
||||||
|
setState(() {
|
||||||
|
_focusedMonth = DateTime(_focusedMonth.year, _focusedMonth.month + 1, 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
List<DateTime> _calendarCells() {
|
||||||
|
final first = DateTime(_focusedMonth.year, _focusedMonth.month, 1);
|
||||||
|
final daysInMonth = DateTime(_focusedMonth.year, _focusedMonth.month + 1, 0).day;
|
||||||
|
final leading = first.weekday - 1;
|
||||||
|
final cells = <DateTime>[];
|
||||||
|
|
||||||
|
final prevEnd = DateTime(_focusedMonth.year, _focusedMonth.month, 0);
|
||||||
|
for (int i = 0; i < leading; i++) {
|
||||||
|
final dayNum = prevEnd.day - leading + i + 1;
|
||||||
|
cells.add(DateTime(prevEnd.year, prevEnd.month, dayNum));
|
||||||
|
}
|
||||||
|
for (int d = 1; d <= daysInMonth; d++) {
|
||||||
|
cells.add(DateTime(_focusedMonth.year, _focusedMonth.month, d));
|
||||||
|
}
|
||||||
|
var next = 1;
|
||||||
|
while (cells.length % 7 != 0 || cells.length < 42) {
|
||||||
|
cells.add(DateTime(_focusedMonth.year, _focusedMonth.month + 1, next));
|
||||||
|
next++;
|
||||||
|
}
|
||||||
|
return cells;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _apply() {
|
||||||
|
if (_start == null) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final end = _end ?? _start!;
|
||||||
|
Navigator.of(context).pop(DateTimeRange(start: _start!, end: end));
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _dayCell(DateTime d) {
|
||||||
|
final inMonth = d.month == _focusedMonth.month;
|
||||||
|
final disabled =
|
||||||
|
d.isBefore(widget.firstDate) || d.isAfter(widget.lastDate);
|
||||||
|
|
||||||
|
if (!inMonth) {
|
||||||
|
return Center(
|
||||||
|
child: Text(
|
||||||
|
'${d.day}',
|
||||||
|
style: GoogleFonts.inter(
|
||||||
|
fontSize: 13,
|
||||||
|
color: const Color(0xFF94A3B8),
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final inRange = !disabled && _inSelectedRange(d);
|
||||||
|
final isStart = !disabled && _isRangeStart(d);
|
||||||
|
final isEnd = !disabled && _isRangeEnd(d);
|
||||||
|
final span = _start != null &&
|
||||||
|
_end != null &&
|
||||||
|
!_sameDay(_start!, _end!);
|
||||||
|
|
||||||
|
BorderRadius? bgRadius;
|
||||||
|
if (inRange && span) {
|
||||||
|
if (isStart) {
|
||||||
|
bgRadius = const BorderRadius.horizontal(left: Radius.circular(20));
|
||||||
|
} else if (isEnd) {
|
||||||
|
bgRadius = const BorderRadius.horizontal(right: Radius.circular(20));
|
||||||
|
} else {
|
||||||
|
bgRadius = BorderRadius.zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: disabled ? null : () => _onDayTap(d),
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: (inRange && span) ? _primaryLight : null,
|
||||||
|
borderRadius: bgRadius,
|
||||||
|
),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Container(
|
||||||
|
width: 36,
|
||||||
|
height: 36,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: (isStart || isEnd) ? _primary : Colors.transparent,
|
||||||
|
border: (isStart || isEnd)
|
||||||
|
? Border.all(color: Colors.white, width: 2)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'${d.day}',
|
||||||
|
style: GoogleFonts.inter(
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight:
|
||||||
|
(isStart || isEnd) ? FontWeight.w700 : FontWeight.w500,
|
||||||
|
color: disabled
|
||||||
|
? const Color(0xFFCBD5E1)
|
||||||
|
: (isStart || isEnd)
|
||||||
|
? Colors.white
|
||||||
|
: const Color(0xFF0F172A),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final cells = _calendarCells();
|
||||||
|
|
||||||
|
return Dialog(
|
||||||
|
insetPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 24),
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 400),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.close, size: 22),
|
||||||
|
color: _muted,
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFF8FAFC),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(color: const Color(0xFFE2E8F0)),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 12, horizontal: 12),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'From',
|
||||||
|
style: GoogleFonts.inter(
|
||||||
|
fontSize: 11,
|
||||||
|
color: _muted,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
_start != null
|
||||||
|
? _headerFrom.format(_start!)
|
||||||
|
: '—',
|
||||||
|
style: GoogleFonts.inter(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: const Color(0xFF0F172A),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
width: 1, height: 52, color: const Color(0xFFE2E8F0)),
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 12, horizontal: 12),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'To',
|
||||||
|
style: GoogleFonts.inter(
|
||||||
|
fontSize: 11,
|
||||||
|
color: _muted,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
_start != null
|
||||||
|
? _headerFrom.format(_end ?? _start!)
|
||||||
|
: '—',
|
||||||
|
style: GoogleFonts.inter(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: const Color(0xFF0F172A),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 14),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: _canPrev ? _prevMonth : null,
|
||||||
|
icon: const Icon(Icons.chevron_left),
|
||||||
|
color: _primary,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
_headerMonth.format(_focusedMonth),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: const Color(0xFF1E293B),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: _canNext ? _nextMonth : null,
|
||||||
|
icon: const Icon(Icons.chevron_right),
|
||||||
|
color: _primary,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Row(
|
||||||
|
children: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
|
||||||
|
.map(
|
||||||
|
(w) => Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
w,
|
||||||
|
style: GoogleFonts.inter(
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: _primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
GridView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
crossAxisCount: 7,
|
||||||
|
mainAxisSpacing: 2,
|
||||||
|
crossAxisSpacing: 2,
|
||||||
|
childAspectRatio: 1.05,
|
||||||
|
),
|
||||||
|
itemCount: cells.length,
|
||||||
|
itemBuilder: (context, i) => _dayCell(cells[i]),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: OutlinedButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
foregroundColor: _primary,
|
||||||
|
side: const BorderSide(color: _primary),
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'Cancel',
|
||||||
|
style:
|
||||||
|
GoogleFonts.poppins(fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: FilledButton(
|
||||||
|
onPressed: _start != null ? _apply : null,
|
||||||
|
style: FilledButton.styleFrom(
|
||||||
|
backgroundColor: _primary,
|
||||||
|
disabledBackgroundColor: const Color(0xFFCBD5E1),
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'Apply',
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
255
lib/presentation/themes/indicators/date_range_picker_field.dart
Normal file
255
lib/presentation/themes/indicators/date_range_picker_field.dart
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
import 'date_range_picker_dialog.dart';
|
||||||
|
|
||||||
|
/// Single UI control for filter **from** / **to** dates.
|
||||||
|
///
|
||||||
|
/// Writes [dd-MM-yyyy] to [startController] and [endController] (same as [ThemedDateField]).
|
||||||
|
/// Does not change API keys or request shape — only how the user picks dates.
|
||||||
|
class DateRangePickerField extends StatefulWidget {
|
||||||
|
const DateRangePickerField({
|
||||||
|
super.key,
|
||||||
|
required this.startController,
|
||||||
|
required this.endController,
|
||||||
|
this.hintText,
|
||||||
|
this.labelText,
|
||||||
|
this.txtwidth,
|
||||||
|
this.txtheight = 35,
|
||||||
|
this.firstDate,
|
||||||
|
this.lastDate,
|
||||||
|
this.onRangeSelected,
|
||||||
|
this.rangeValidator,
|
||||||
|
this.showLabelAboveField = false,
|
||||||
|
this.labelStyle,
|
||||||
|
this.useFormField = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
final TextEditingController startController;
|
||||||
|
final TextEditingController endController;
|
||||||
|
|
||||||
|
/// Shown when no range is selected.
|
||||||
|
final String? hintText;
|
||||||
|
|
||||||
|
/// Optional label above the field (e.g. `Date range *`).
|
||||||
|
final String? labelText;
|
||||||
|
final double? txtwidth;
|
||||||
|
final double? txtheight;
|
||||||
|
final DateTime? firstDate;
|
||||||
|
final DateTime? lastDate;
|
||||||
|
|
||||||
|
/// Called after user confirms a range (same moment controllers are updated).
|
||||||
|
final void Function(DateTimeRange range)? onRangeSelected;
|
||||||
|
|
||||||
|
/// When non-null and [useFormField] is true, wrapped in [FormField] for [FormState.validate].
|
||||||
|
final String? Function(
|
||||||
|
TextEditingController start,
|
||||||
|
TextEditingController end,
|
||||||
|
)? rangeValidator;
|
||||||
|
|
||||||
|
final bool showLabelAboveField;
|
||||||
|
final TextStyle? labelStyle;
|
||||||
|
|
||||||
|
/// If false, no [FormField] — use when this widget is not under a [Form].
|
||||||
|
final bool useFormField;
|
||||||
|
|
||||||
|
static final DateFormat storageFormat = DateFormat('dd-MM-yyyy');
|
||||||
|
static final DateFormat displayFormat = DateFormat('dd MMM yyyy');
|
||||||
|
|
||||||
|
/// Matches previous end-date-only validation: if start is set, end is required and not before start.
|
||||||
|
static String? defaultFilterValidator(
|
||||||
|
TextEditingController start,
|
||||||
|
TextEditingController end,
|
||||||
|
) {
|
||||||
|
final s = start.text.trim();
|
||||||
|
final e = end.text.trim();
|
||||||
|
if (s.isNotEmpty) {
|
||||||
|
try {
|
||||||
|
storageFormat.parse(s);
|
||||||
|
if (e.isEmpty) return 'End Date is required';
|
||||||
|
final sd = storageFormat.parse(s);
|
||||||
|
final ed = storageFormat.parse(e);
|
||||||
|
if (ed.isBefore(sd)) {
|
||||||
|
return 'End Date cannot be earlier than Start Date';
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
return 'Invalid date';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DateRangePickerField> createState() => _DateRangePickerFieldState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DateRangePickerFieldState extends State<DateRangePickerField> {
|
||||||
|
void _onTextChanged() {
|
||||||
|
if (mounted) setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
widget.startController.addListener(_onTextChanged);
|
||||||
|
widget.endController.addListener(_onTextChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
widget.startController.removeListener(_onTextChanged);
|
||||||
|
widget.endController.removeListener(_onTextChanged);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
String _displayText() {
|
||||||
|
final s = widget.startController.text.trim();
|
||||||
|
final e = widget.endController.text.trim();
|
||||||
|
if (s.isEmpty && e.isEmpty) {
|
||||||
|
return widget.hintText ?? 'Select date range';
|
||||||
|
}
|
||||||
|
if (s.isNotEmpty && e.isNotEmpty) {
|
||||||
|
try {
|
||||||
|
final sd = DateRangePickerField.storageFormat.parse(s);
|
||||||
|
final ed = DateRangePickerField.storageFormat.parse(e);
|
||||||
|
return '${DateRangePickerField.displayFormat.format(sd)} – ${DateRangePickerField.displayFormat.format(ed)}';
|
||||||
|
} catch (_) {
|
||||||
|
return '$s – $e';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (s.isNotEmpty) return s;
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _pick(BuildContext context, FormFieldState<String>? field) async {
|
||||||
|
final first = widget.firstDate ?? DateTime(2000);
|
||||||
|
final last = widget.lastDate ?? DateTime(2100);
|
||||||
|
|
||||||
|
DateTimeRange? initial;
|
||||||
|
try {
|
||||||
|
final s = widget.startController.text.trim();
|
||||||
|
final e = widget.endController.text.trim();
|
||||||
|
if (s.isNotEmpty && e.isNotEmpty) {
|
||||||
|
initial = DateTimeRange(
|
||||||
|
start: DateRangePickerField.storageFormat.parse(s),
|
||||||
|
end: DateRangePickerField.storageFormat.parse(e),
|
||||||
|
);
|
||||||
|
} else if (s.isNotEmpty) {
|
||||||
|
final d = DateRangePickerField.storageFormat.parse(s);
|
||||||
|
initial = DateTimeRange(start: d, end: d);
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
final picked = await showCompactDateRangePicker(
|
||||||
|
context: context,
|
||||||
|
firstDate: first,
|
||||||
|
lastDate: last,
|
||||||
|
initialRange: initial,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (picked != null) {
|
||||||
|
widget.startController.text =
|
||||||
|
DateRangePickerField.storageFormat.format(picked.start);
|
||||||
|
widget.endController.text =
|
||||||
|
DateRangePickerField.storageFormat.format(picked.end);
|
||||||
|
field?.didChange('range');
|
||||||
|
widget.onRangeSelected?.call(picked);
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const _border = Color(0xFFE2E8F0);
|
||||||
|
static const _error = Color(0xFFD83731);
|
||||||
|
|
||||||
|
Widget _tappableField({
|
||||||
|
required bool hasError,
|
||||||
|
required VoidCallback onTap,
|
||||||
|
}) {
|
||||||
|
final empty = widget.startController.text.trim().isEmpty &&
|
||||||
|
widget.endController.text.trim().isEmpty;
|
||||||
|
|
||||||
|
return InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
child: Container(
|
||||||
|
width: widget.txtwidth ?? MediaQuery.sizeOf(context).width,
|
||||||
|
height: widget.txtheight,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 15),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
border: Border.all(
|
||||||
|
color: hasError ? _error : _border,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
_displayText(),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
color: hasError
|
||||||
|
? _error
|
||||||
|
: empty
|
||||||
|
? const Color(0xFF64748B)
|
||||||
|
: Colors.black,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Icon(Icons.date_range, size: 16, color: Color(0xFF5A6C7D)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final labelStyle = widget.labelStyle ??
|
||||||
|
const TextStyle(fontSize: 12, fontWeight: FontWeight.w600);
|
||||||
|
|
||||||
|
Widget content({required bool hasError, FormFieldState<String>? field}) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
if (widget.showLabelAboveField && widget.labelText != null) ...[
|
||||||
|
Text(widget.labelText!, style: labelStyle),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
],
|
||||||
|
_tappableField(
|
||||||
|
hasError: hasError,
|
||||||
|
onTap: () => _pick(context, field),
|
||||||
|
),
|
||||||
|
if (hasError && field != null && (field.errorText ?? '').isNotEmpty)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 5, left: 5),
|
||||||
|
child: Text(
|
||||||
|
field.errorText ?? '',
|
||||||
|
style: const TextStyle(color: _error, fontSize: 10),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!widget.useFormField || widget.rangeValidator == null) {
|
||||||
|
return content(hasError: false, field: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return FormField<String>(
|
||||||
|
initialValue: '',
|
||||||
|
validator: (_) => widget.rangeValidator!(
|
||||||
|
widget.startController,
|
||||||
|
widget.endController,
|
||||||
|
),
|
||||||
|
builder: (field) {
|
||||||
|
final hasError = field.hasError;
|
||||||
|
return content(hasError: hasError, field: field);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,6 +8,7 @@ import '../layouts/responsive_layout.dart';
|
|||||||
import '../providers/manager_provider.dart';
|
import '../providers/manager_provider.dart';
|
||||||
import '../providers/userRoleProvider.dart';
|
import '../providers/userRoleProvider.dart';
|
||||||
import '../themes/indicators/date_field_theme.dart';
|
import '../themes/indicators/date_field_theme.dart';
|
||||||
|
import '../themes/indicators/date_range_picker_field.dart';
|
||||||
import '../themes/indicators/input_field_decoration.dart';
|
import '../themes/indicators/input_field_decoration.dart';
|
||||||
|
|
||||||
// class DateFilterRow extends StatelessWidget {
|
// class DateFilterRow extends StatelessWidget {
|
||||||
@ -276,9 +277,7 @@ class _DateFilterRowState extends ConsumerState<DateFilterRow> {
|
|||||||
|
|
||||||
List<Widget> getRowChildren(bool isMobile, double spacing) {
|
List<Widget> getRowChildren(bool isMobile, double spacing) {
|
||||||
final dateFields = [
|
final dateFields = [
|
||||||
buildStartDate(context),
|
buildDateRangeFilter(context),
|
||||||
SizedBox(width: spacing),
|
|
||||||
buildEndDate(context),
|
|
||||||
SizedBox(width: spacing),
|
SizedBox(width: spacing),
|
||||||
|
|
||||||
if (!widget.compactDateFiltersOnly) ...[
|
if (!widget.compactDateFiltersOnly) ...[
|
||||||
@ -371,78 +370,18 @@ class _DateFilterRowState extends ConsumerState<DateFilterRow> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildStartDate(BuildContext context) {
|
Widget buildDateRangeFilter(BuildContext context) {
|
||||||
return Column(
|
final w = ResponsiveLayout.isMobile(context)
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
? null
|
||||||
children: [
|
: MediaQuery.of(context).size.width * 0.15;
|
||||||
// Text('Start Date', style: _textStyle),
|
return DateRangePickerField(
|
||||||
// SizedBox(height: 5),
|
startController: widget.startController,
|
||||||
SizedBox(
|
endController: widget.endController,
|
||||||
height: 35,
|
hintText: 'Select date range',
|
||||||
child: ThemedDateField(
|
txtwidth: w,
|
||||||
hintText: "Select Date",
|
lastDate: DateTime.now(),
|
||||||
txtwidth: ResponsiveLayout.isMobile(context)
|
rangeValidator: DateRangePickerField.defaultFilterValidator,
|
||||||
? null
|
onRangeSelected: (_) => Future.microtask(() => widget.onFilter()),
|
||||||
: MediaQuery.of(context).size.width * 0.1,
|
|
||||||
// txtheight: 50,
|
|
||||||
// backgroundColor: const Color(0xFFECECEC),
|
|
||||||
// validator: (value) => Validators.requiredField(value, "date"),
|
|
||||||
controller: widget.startController,
|
|
||||||
onDateSelected: (date) {
|
|
||||||
print("SPicked Date: $date");
|
|
||||||
widget.startController.text = DateFormat(
|
|
||||||
'dd-MM-yyyy',
|
|
||||||
).format(date);
|
|
||||||
// controllers['date']?.text = date as String;
|
|
||||||
// Future.microtask(() => widget.onFilter());
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget buildEndDate(BuildContext context) {
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
// Text('End Date', style: _textStyle),
|
|
||||||
// SizedBox(height: 5),
|
|
||||||
SizedBox(
|
|
||||||
height: 35,
|
|
||||||
child: ThemedDateField(
|
|
||||||
hintText: "Select Date",
|
|
||||||
txtwidth: ResponsiveLayout.isMobile(context)
|
|
||||||
? null
|
|
||||||
: MediaQuery.of(context).size.width * 0.1,
|
|
||||||
// txtheight: 50,
|
|
||||||
// backgroundColor: const Color(0xFFECECEC),
|
|
||||||
validator: (value) {
|
|
||||||
final fromText = widget.startController.text ?? '';
|
|
||||||
if (fromText.isNotEmpty) {
|
|
||||||
final startDate = DateFormat('dd-MM-yyyy').parse(fromText);
|
|
||||||
if (value == null || value.isEmpty) {
|
|
||||||
return "End Date is required";
|
|
||||||
}
|
|
||||||
final endDate = DateFormat('dd-MM-yyyy').parse(value);
|
|
||||||
|
|
||||||
if (endDate.isBefore(startDate)) {
|
|
||||||
return "End Date cannot be earlier than Start Date";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null; // ✅ no error
|
|
||||||
},
|
|
||||||
controller: widget.endController,
|
|
||||||
lastDate: DateTime.now(),
|
|
||||||
onDateSelected: (edate) {
|
|
||||||
print("EPicked Date: $edate");
|
|
||||||
widget.endController.text = DateFormat('dd-MM-yyyy').format(edate);
|
|
||||||
Future.microtask(() => widget.onFilter());
|
|
||||||
// controllers['date']?.text = date as String;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
22
pubspec.lock
22
pubspec.lock
@ -117,10 +117,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: characters
|
name: characters
|
||||||
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.1"
|
version: "1.4.0"
|
||||||
charcode:
|
charcode:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -780,26 +780,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.19"
|
version: "0.12.17"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.13.0"
|
version: "0.11.1"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.17.0"
|
version: "1.16.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1297,10 +1297,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
|
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.10"
|
version: "0.7.6"
|
||||||
toastification:
|
toastification:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1502,5 +1502,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.9.0-0 <4.0.0"
|
dart: ">=3.8.1 <4.0.0"
|
||||||
flutter: ">=3.32.0"
|
flutter: ">=3.32.0"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user