This commit is contained in:
Surendiran 2026-04-07 12:05:12 +05:30
parent 89c72c47d8
commit 5bc5e582f1
5 changed files with 777 additions and 468 deletions

File diff suppressed because one or more lines are too long

View File

@ -409,8 +409,8 @@ class _PolicyListCard extends StatelessWidget {
); );
} }
class _RenewalAlertsCard extends StatelessWidget { class _RenewalAlertsCard extends StatefulWidget {
static const int kVisibleRows = 10; static const int kPageSize = 10;
static const double kRowHeight = 44; static const double kRowHeight = 44;
final List<Map<String, dynamic>> renewals; final List<Map<String, dynamic>> renewals;
@ -426,10 +426,44 @@ class _RenewalAlertsCard extends StatelessWidget {
required this.inr, required this.inr,
}); });
@override
State<_RenewalAlertsCard> createState() => _RenewalAlertsCardState();
}
class _RenewalAlertsCardState extends State<_RenewalAlertsCard> {
int _currentPage = 1;
int get _totalPages {
if (widget.renewals.isEmpty) return 1;
return ((widget.renewals.length - 1) ~/ _RenewalAlertsCard.kPageSize) + 1;
}
List<Map<String, dynamic>> get _pageItems {
final start = (_currentPage - 1) * _RenewalAlertsCard.kPageSize;
var end = start + _RenewalAlertsCard.kPageSize;
if (end > widget.renewals.length) end = widget.renewals.length;
if (start >= widget.renewals.length) return const [];
return widget.renewals.sublist(start, end);
}
@override
void didUpdateWidget(covariant _RenewalAlertsCard oldWidget) {
super.didUpdateWidget(oldWidget);
final dataChanged = oldWidget.renewals != widget.renewals ||
oldWidget.selectedDays != widget.selectedDays;
if (dataChanged) {
_currentPage = 1;
return;
}
if (_currentPage > _totalPages) {
_currentPage = _totalPages;
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final visibleRowCount = renewals.length.clamp(1, kVisibleRows); final pageItems = _pageItems;
final listHeight = visibleRowCount * kRowHeight; final startIndex = (_currentPage - 1) * _RenewalAlertsCard.kPageSize;
return _DashCard( return _DashCard(
header: Row(children: [ header: Row(children: [
Container( Container(
@ -472,9 +506,9 @@ class _RenewalAlertsCard extends StatelessWidget {
spacing: 2, spacing: 2,
runSpacing: 2, runSpacing: 2,
children: [10, 20, 30, 40, 50].map((d) { children: [10, 20, 30, 40, 50].map((d) {
final act = selectedDays == d; final act = widget.selectedDays == d;
return GestureDetector( return GestureDetector(
onTap: () => onDays(d), onTap: () => widget.onDays(d),
child: AnimatedContainer( child: AnimatedContainer(
duration: const Duration(milliseconds: 180), duration: const Duration(milliseconds: 180),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
@ -500,7 +534,7 @@ class _RenewalAlertsCard extends StatelessWidget {
), ),
), ),
]), ]),
body: totalCount == 0 body: widget.totalCount == 0
? const _EmptyState(message: 'No renewals due within selected period') ? const _EmptyState(message: 'No renewals due within selected period')
: Column( : Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
@ -521,17 +555,63 @@ class _RenewalAlertsCard extends StatelessWidget {
], ],
), ),
), ),
SizedBox( ...List.generate(pageItems.length, (i) {
height: listHeight, final serialNo = startIndex + i + 1;
child: ListView.separated( return Column(
physics: const ClampingScrollPhysics(), mainAxisSize: MainAxisSize.min,
itemCount: renewals.length, children: [
separatorBuilder: (_, __) => const Divider(height: 1, color: Color(0xFFD9F0ED)), _RenewalTableRow(
itemBuilder: (_, i) => _RenewalTableRow( serialNo: serialNo,
serialNo: i + 1, renewal: pageItems[i],
renewal: renewals[i], inr: widget.inr,
inr: inr, ),
if (i != pageItems.length - 1)
const Divider(height: 1, color: Color(0xFFD9F0ED)),
],
);
}),
Container(
height: 44,
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: const BoxDecoration(
border: Border(top: BorderSide(color: Color(0xFFD9F0ED))),
),
child: Row(
children: [
Text(
'${startIndex + 1}-${startIndex + pageItems.length} of ${widget.renewals.length}',
style: GoogleFonts.inter(
fontSize: 11,
fontWeight: FontWeight.w500,
color: const Color(0xFF64748B),
), ),
),
const Spacer(),
IconButton(
onPressed: _currentPage > 1
? () => setState(() => _currentPage--)
: null,
icon: const Icon(Icons.chevron_left, size: 18),
visualDensity: VisualDensity.compact,
tooltip: 'Previous',
),
Text(
'$_currentPage / $_totalPages',
style: GoogleFonts.inter(
fontSize: 11,
fontWeight: FontWeight.w600,
color: const Color(0xFF334155),
),
),
IconButton(
onPressed: _currentPage < _totalPages
? () => setState(() => _currentPage++)
: null,
icon: const Icon(Icons.chevron_right, size: 18),
visualDensity: VisualDensity.compact,
tooltip: 'Next',
),
],
), ),
), ),
], ],

View File

@ -5,8 +5,6 @@ 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 '../../../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';
@ -201,7 +199,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, textAlign: TextAlign.center,
style: fieldStyle, style: fieldStyle,
keyboardType: const TextInputType.numberWithOptions(decimal: true), keyboardType: const TextInputType.numberWithOptions(decimal: true),
inputFormatters: _commissionInputFormatter, inputFormatters: _commissionInputFormatter,
@ -950,20 +948,12 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
? CrossFadeState.showFirst ? CrossFadeState.showFirst
: CrossFadeState.showSecond, : CrossFadeState.showSecond,
firstChild: const SizedBox(), firstChild: const SizedBox(),
secondChild: SingleChildScrollView( secondChild: Column(
scrollDirection: Axis.horizontal, crossAxisAlignment: CrossAxisAlignment.stretch,
child: SizedBox( children: [
width: max( _policyTableHeader(),
_tableMinWidth, _buildDataTable(context),
MediaQuery.of(context).size.width - 32, ],
),
child: Column(
children: [
_policyTableHeader(), // static header
_policyTableBody(), // vertical scroll rows
],
),
),
), ),
), ),
], ],
@ -1124,166 +1114,214 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
final partiallySelected = selectedPolicies.isNotEmpty && !allSelected; final partiallySelected = selectedPolicies.isNotEmpty && !allSelected;
return Container( return Container(
height: 30, padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
padding: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration(
decoration: const BoxDecoration( color: const Color(0xFFF1F5F9),
// color: Color(0xFFf9fafb), borderRadius: BorderRadius.circular(6),
color: Color(0xFFf9fafb),
// color: Color(0xFFf9fafb),
// color: Color(0xFFF6F8F7),
// border: Border(bottom: BorderSide(color: Color(0xFFE0E0E0))),
// border: Border(bottom: BorderSide(color: Color(0xFFECEFF1))),
border: Border(bottom: BorderSide(color: Color(0xFFf9fafb))),
), ),
child: Row( child: Row(
children: [ children: [
SizedBox( SizedBox(
width: colCheck, width: _colCheck,
child: Checkbox( child: Center(
value: allSelected ? true : (partiallySelected ? null : false), child: Checkbox(
tristate: true, value: allSelected ? true : (partiallySelected ? null : false),
onChanged: (v) { tristate: true,
setState(() { onChanged: (v) {
if (v == true) { setState(() {
selectedPolicies = filteredPolicies if (v == true) {
.map( selectedPolicies = filteredPolicies
(p) => .map(
int.tryParse( (p) =>
p["policy_id"]?.toString() ?? "0", int.tryParse(
) ?? p["policy_id"]?.toString() ?? "0",
0, ) ??
) 0,
.where((id) => id != 0) // Filter out invalid IDs )
.toSet(); .where((id) => id != 0) // Filter out invalid IDs
} else { .toSet();
selectedPolicies.clear(); } else {
} selectedPolicies.clear();
totalPolicies = selectedPolicies.length.toString(); }
}); totalPolicies = selectedPolicies.length.toString();
_calculateTotalCommission(); });
}, _calculateTotalCommission();
},
),
), ),
), ),
_headerText("S.NO", colSno, align: TextAlign.center), _headerText("S.NO", _flexSno, align: TextAlign.left),
_headerText("POLICY NO", colPolicy), _headerText("POLICY NO", _flexPolicy, align: TextAlign.left),
_headerText("POLICY DATE", colDate), _headerText("POLICY DATE", _flexDate, align: TextAlign.left),
_headerText("PARTNER", colAgent), _headerText("PARTNER", _flexAgent, align: TextAlign.left),
_headerText("CUSTOMER", colCustomer), _headerText("CUSTOMER", _flexCustomer, align: TextAlign.left),
_headerText("PREMIUM (₹)", colPremium, align: TextAlign.right), _headerText("PREMIUM (₹)", _flexPremium, align: TextAlign.right),
const SizedBox(width: 10), SizedBox(width: 10),
_headerText("PAYOUT (₹)", colCommission, align: TextAlign.right), _headerText("PAYOUT (₹)", _flexCommission, align: TextAlign.right),
], ],
), ),
); );
} }
Widget _headerText(String text, double width, {TextAlign align = TextAlign.left}) { Widget _headerText(String text, int flex, {TextAlign align = TextAlign.left}) {
return SizedBox( return Expanded(
width: width, flex: flex,
child: Text( child: Text(
text, text,
textAlign: align, textAlign: align,
style: GoogleFonts.poppins( style: _tableHeaderStyle,
fontSize: 11,
fontWeight: FontWeight.w500,
color: Colors.blueGrey,
letterSpacing: 0.5,
),
), ),
); );
} }
Widget _policyTableBody() { Widget _buildDataTable(BuildContext context) {
if (filteredPolicies.isEmpty) {
return const SizedBox(
height: 50,
child: Center(child: Text('No available data')),
);
}
final pageRows = _paginatedPolicies; 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: pageRows.length, itemCount: pageRows.length + 1,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final p = pageRows[index]; if (index == 0) return _buildHeader();
final startIndex = (currentPage - 1) * itemsPerPage;
final item = pageRows[index - 1];
final sno = startIndex + index;
return _buildDataRow(item, sno);
},
),
);
}
final int id = int.tryParse(p["policy_id"]?.toString() ?? "0") ?? 0; Widget _buildHeader() {
final bool isSelected = selectedPolicies.contains(id); return const SizedBox.shrink();
return InkWell( }
onTap: () {
setState(() { Widget _buildDataRow(Map<String, dynamic> item, int sno) {
isSelected final int id = int.tryParse(item["policy_id"]?.toString() ?? "0") ?? 0;
? selectedPolicies.remove(id) final bool isSelected = selectedPolicies.contains(id);
: selectedPolicies.add(id); return InkWell(
totalPolicies = selectedPolicies.length.toString(); onTap: () {
}); setState(() {
_calculateTotalCommission(); isSelected ? selectedPolicies.remove(id) : selectedPolicies.add(id);
}, totalPolicies = selectedPolicies.length.toString();
child: Container( });
height: 38, _calculateTotalCommission();
padding: const EdgeInsets.symmetric(horizontal: 16), },
decoration: BoxDecoration( child: Container(
// const Color(0xFFE6F4EF) padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 16),
color: isSelected ? const Color(0xFFF0FAF6) : Colors.white, decoration: BoxDecoration(
border: const Border( color: isSelected ? const Color(0xFFF0FAF6) : Colors.white,
bottom: BorderSide(color: Color(0xFFf9fafb)), border: const Border(
// bottom: BorderSide(color: Color(0xFFEDEDED)), bottom: BorderSide(color: Color(0xFFEAEAEA), width: 1),
),
borderRadius: BorderRadius.circular(8),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: _colCheck,
child: Center(
child: Checkbox(
value: isSelected,
onChanged: (v) {
setState(() {
v == true ? selectedPolicies.add(id) : selectedPolicies.remove(id);
totalPolicies = selectedPolicies.length.toString();
});
_calculateTotalCommission();
},
), ),
), ),
child: Row( ),
children: [
SizedBox( Expanded(
width: colCheck, flex: _flexSno,
child: Checkbox( child: Text(
value: isSelected, '$sno',
onChanged: (v) { textAlign: TextAlign.left,
setState(() { style: _tableDataStyle,
v == true overflow: TextOverflow.ellipsis,
? selectedPolicies.add(id) maxLines: 2,
: selectedPolicies.remove(id);
totalPolicies = selectedPolicies.length.toString();
});
_calculateTotalCommission();
},
),
),
_cell(
'${startIndex + index + 1}',
colSno,
align: TextAlign.center,
),
_cell(p["policy_no"], colPolicy),
// _cell(p["issued_date"] ?? "-", colDate),
_cell(_formatDate(p['issued_date']), colDate),
// _cell(p["agent_name"] ?? "-", colAgent),
_cell(
(p["agent_code"] != null && p["agent_name"] != null)
? '${p["agent_code"]} - ${p["agent_name"]}'
: (p["agent_name"] ?? '-'),
colAgent,
),
_cell(p["customer_name"], colCustomer),
_cell(
_amountText(p["premium_amount"]),
colPremium,
align: TextAlign.right,
),
const SizedBox(width: 10),
SizedBox(
width: colCommission,
child: Row(
children: [
Expanded(
child: _commissionAmountField(
p,
_commissionControllerFor(p),
),
),
],
),
),
],
), ),
), ),
); Expanded(
}, flex: _flexPolicy,
child: Text(
(item["policy_no"] ?? "-").toString(),
textAlign: TextAlign.left,
style: _tableDataStyle,
overflow: TextOverflow.ellipsis,
maxLines: 2,
),
),
Expanded(
flex: _flexDate,
child: Text(
_formatDate(item['issued_date']),
textAlign: TextAlign.left,
style: _tableDataStyle,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
Expanded(
flex: _flexAgent,
child: Text(
(item["agent_code"] != null && item["agent_name"] != null)
? '${item["agent_code"]} - ${item["agent_name"]}'
: (item["agent_name"] ?? '-'),
textAlign: TextAlign.left,
style: _tableDataStyle,
overflow: TextOverflow.ellipsis,
maxLines: 2,
),
),
Expanded(
flex: _flexCustomer,
child: Text(
(item["customer_name"] ?? "-").toString(),
textAlign: TextAlign.left,
style: _tableDataStyle,
overflow: TextOverflow.ellipsis,
maxLines: 2,
),
),
Expanded(
flex: _flexPremium,
child: Text(
_amountText(item["premium_amount"]),
textAlign: TextAlign.right,
style: _tableDataStyle,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
SizedBox(width: 10),
Expanded(
flex: _flexCommission,
child: Align(
alignment: Alignment.centerRight,
child: ConstrainedBox(
constraints: const BoxConstraints(
minWidth: 76,
maxWidth: 112,
),
child: _commissionAmountField(
item,
_commissionControllerFor(item),
),
),
),
),
],
),
), ),
); );
} }
@ -1399,22 +1437,6 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
} }
} }
Widget _cell(
dynamic value,
double width, {
TextAlign align = TextAlign.left,
}) {
return SizedBox(
width: width,
child: Text(
(value ?? "-").toString(),
textAlign: align,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 11),
),
);
}
double _calculateTotalCommission() { double _calculateTotalCommission() {
print("ASD"); print("ASD");
print(filteredPolicies); print(filteredPolicies);
@ -1519,6 +1541,17 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
fontSize: 11, fontSize: 11,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
); );
static final _tableDataStyle = GoogleFonts.inter(
fontSize: 12,
height: 1.4,
fontWeight: FontWeight.w400,
color: const Color(0xFF000000),
);
static final _tableHeaderStyle = GoogleFonts.poppins(
fontSize: 11.2,
fontWeight: FontWeight.w500,
color: const Color(0xFF1E293B),
);
String _formatDate(String rawDate) { String _formatDate(String rawDate) {
try { try {
@ -1529,25 +1562,13 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
} }
} }
static final double colCheck = 44; static const double _colCheck = 44;
static final double colSno = 60; /// Wider columns get more flex; narrow ids/dates get less to avoid empty middle gaps.
static final double colPolicy = 200; static const int _flexSno = 1;
static final double colDate = 200; static const int _flexPolicy = 2;
static final double colAgent = 400; static const int _flexDate = 1;
static final double colCustomer = 200; static const int _flexAgent = 3;
static final double colPremium = 200; static const int _flexCustomer = 3;
static final double colPremiumPayoutGap = 10; static const int _flexPremium = 1;
static final double colCommission = 150; static const int _flexCommission = 1;
double get _tableMinWidth =>
colCheck +
colSno +
colPolicy +
colDate +
colAgent +
colCustomer +
colPremium +
colPremiumPayoutGap +
colCommission +
32;
} }

View File

@ -183,28 +183,13 @@ class _PayOutDetailsViewState extends ConsumerState<PayOutDetailsView> {
return text.replaceAll('', '').trim(); 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( Widget _headerText(
String text, String text,
double width, { int flex, {
TextAlign align = TextAlign.left, TextAlign align = TextAlign.left,
}) { }) {
return SizedBox( return Expanded(
width: width, flex: flex,
child: Text( child: Text(
text, text,
textAlign: align, textAlign: align,
@ -220,13 +205,13 @@ class _PayOutDetailsViewState extends ConsumerState<PayOutDetailsView> {
Widget _cell( Widget _cell(
String? value, String? value,
double width, { int flex, {
TextAlign align = TextAlign.left, TextAlign align = TextAlign.left,
Color? color, Color? color,
FontWeight? fontWeight, FontWeight? fontWeight,
}) { }) {
return SizedBox( return Expanded(
width: width, flex: flex,
child: Text( child: Text(
value ?? '-', value ?? '-',
textAlign: align, textAlign: align,
@ -240,7 +225,7 @@ class _PayOutDetailsViewState extends ConsumerState<PayOutDetailsView> {
); );
} }
Widget _policyTableHeader(List<double> w) { Widget _policyTableHeader() {
return Container( return Container(
height: 30, height: 30,
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
@ -250,19 +235,19 @@ class _PayOutDetailsViewState extends ConsumerState<PayOutDetailsView> {
), ),
child: Row( child: Row(
children: [ children: [
_headerText('S.NO', w[0]), _headerText('S.NO', 5, align: TextAlign.center),
_headerText('POLICY NO', w[1]), _headerText('POLICY NO', 14),
_headerText('POLICY DATE', w[2]), _headerText('POLICY DATE', 10, align: TextAlign.center),
_headerText('PARTNER', w[3]), _headerText('PARTNER', 20),
_headerText('CUSTOMER', w[4]), _headerText('CUSTOMER', 16),
_headerText('PREMIUM (₹)', w[5], align: TextAlign.right), _headerText('PREMIUM (₹)', 11, align: TextAlign.right),
_headerText('PAYOUT (₹)', w[6], align: TextAlign.right), _headerText('PAYOUT (₹)', 11, align: TextAlign.right),
], ],
), ),
); );
} }
Widget _policyRow(Map<String, dynamic> p, int index, List<double> w) { Widget _policyRow(Map<String, dynamic> p, int index) {
return Container( return Container(
height: 38, height: 38,
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
@ -272,24 +257,24 @@ class _PayOutDetailsViewState extends ConsumerState<PayOutDetailsView> {
), ),
child: Row( child: Row(
children: [ children: [
_cell('${index + 1}', w[0]), _cell('${index + 1}', 5, align: TextAlign.center),
_cell(p['policy_no']?.toString(), w[1]), _cell(p['policy_no']?.toString(), 14),
_cell(_formatDate(p['issued_date']?.toString()), w[2]), _cell(_formatDate(p['issued_date']?.toString()), 10, align: TextAlign.center),
_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(),
w[3], 20,
), ),
_cell(p['customer_name']?.toString(), w[4]), _cell(p['customer_name']?.toString(), 16),
_cell( _cell(
_amountText(p['premium_amount']), _amountText(p['premium_amount']),
w[5], 11,
align: TextAlign.right, align: TextAlign.right,
), ),
_cell( _cell(
_amountText(p['commission_amount']), _amountText(p['commission_amount']),
w[6], 11,
align: TextAlign.right, align: TextAlign.right,
color: const Color(0xFF009B77), color: const Color(0xFF009B77),
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -350,9 +335,9 @@ class _PayOutDetailsViewState extends ConsumerState<PayOutDetailsView> {
: Container( : Container(
color: Colors.white, color: Colors.white,
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
margin: EdgeInsets.symmetric( // margin: EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width * 0.1, // horizontal: MediaQuery.of(context).size.width * 0.1,
), // ),
child: Column( child: Column(
children: [ children: [
SizedBox( SizedBox(
@ -468,7 +453,6 @@ class _PayOutDetailsViewState extends ConsumerState<PayOutDetailsView> {
), ),
child: LayoutBuilder( child: LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
final widths = _columnWidths(constraints.maxWidth);
final rows = _paginatedPolicies; final rows = _paginatedPolicies;
final startIdx = (filteredPolicies.isEmpty) final startIdx = (filteredPolicies.isEmpty)
? 0 ? 0
@ -476,7 +460,7 @@ class _PayOutDetailsViewState extends ConsumerState<PayOutDetailsView> {
return Column( return Column(
children: [ children: [
_policyTableHeader(widths), _policyTableHeader(),
Expanded( Expanded(
child: filteredPolicies.isEmpty child: filteredPolicies.isEmpty
? const Center( ? const Center(
@ -494,7 +478,6 @@ class _PayOutDetailsViewState extends ConsumerState<PayOutDetailsView> {
return _policyRow( return _policyRow(
rows[index], rows[index],
startIdx + index, startIdx + index,
widths,
); );
}, },
), ),

View File

@ -1,52 +1,99 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.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 'dart:math' as math;
/// Compact date-range dialog (centered card, not full-screen on web). /// Date-range dialog with quick presets (Today, Yesterday, Month, Custom Range),
/// From/To header, month navigation, range highlight, Cancel / Apply. /// dual-month calendar, and Apply / Clear.
Future<DateTimeRange?> showCompactDateRangePicker({ Future<DateTimeRange?> showCompactDateRangePicker({
required BuildContext context, required BuildContext context,
required DateTime firstDate, required DateTime firstDate,
required DateTime lastDate, required DateTime lastDate,
DateTimeRange? initialRange, DateTimeRange? initialRange,
}) { }) {
return showDialog<DateTimeRange>( final box = context.findRenderObject();
final screenSize = MediaQuery.sizeOf(context);
const margin = 12.0;
const gap = 6.0;
var left = margin;
var top = margin;
var popupWidth = math.min(720.0, screenSize.width - (margin * 2));
if (box is RenderBox) {
final offset = box.localToGlobal(Offset.zero);
final fieldSize = box.size;
popupWidth = math.min(720.0, screenSize.width - (margin * 2));
left = offset.dx.clamp(margin, screenSize.width - popupWidth - margin);
top = (offset.dy + fieldSize.height + gap)
.clamp(margin, screenSize.height - margin);
}
return showGeneralDialog<DateTimeRange>(
context: context, context: context,
barrierDismissible: true, barrierDismissible: true,
builder: (ctx) => _CompactDateRangeDialog( barrierLabel: 'Close',
firstDate: DateTime(firstDate.year, firstDate.month, firstDate.day), useRootNavigator: true,
lastDate: DateTime(lastDate.year, lastDate.month, lastDate.day), barrierColor: Colors.transparent,
initialRange: initialRange, pageBuilder: (ctx, animation, secondaryAnimation) {
), return Stack(
children: [
Positioned(
left: left,
top: top,
child: _CompactDateRangeDialog(
firstDate: DateTime(firstDate.year, firstDate.month, firstDate.day),
lastDate: DateTime(lastDate.year, lastDate.month, lastDate.day),
initialRange: initialRange,
maxWidth: popupWidth,
),
),
],
);
},
transitionBuilder: (ctx, animation, secondaryAnimation, child) {
final curved = CurvedAnimation(parent: animation, curve: Curves.easeOutCubic);
return FadeTransition(
opacity: curved,
child: child,
);
},
); );
} }
enum _QuickPreset { today, yesterday, month, custom }
class _CompactDateRangeDialog extends StatefulWidget { class _CompactDateRangeDialog extends StatefulWidget {
const _CompactDateRangeDialog({ const _CompactDateRangeDialog({
required this.firstDate, required this.firstDate,
required this.lastDate, required this.lastDate,
this.initialRange, this.initialRange,
required this.maxWidth,
}); });
final DateTime firstDate; final DateTime firstDate;
final DateTime lastDate; final DateTime lastDate;
final DateTimeRange? initialRange; final DateTimeRange? initialRange;
final double maxWidth;
@override @override
State<_CompactDateRangeDialog> createState() => _CompactDateRangeDialogState(); State<_CompactDateRangeDialog> createState() => _CompactDateRangeDialogState();
} }
class _CompactDateRangeDialogState extends State<_CompactDateRangeDialog> { class _CompactDateRangeDialogState extends State<_CompactDateRangeDialog> {
static const _primary = Color(0xFF2E7D6E);
static const _primaryLight = Color(0xFFE6F4F1);
static const _muted = Color(0xFF64748B); static const _muted = Color(0xFF64748B);
Color get _primary => Theme.of(context).colorScheme.primary;
Color get _primaryLight => _primary.withValues(alpha: 0.14);
Color get _sidebarSelected => _primary;
late DateTime _focusedMonth; late DateTime _focusedMonth;
DateTime? _start; DateTime? _start;
DateTime? _end; DateTime? _end;
_QuickPreset _preset = _QuickPreset.custom;
final _headerFrom = DateFormat('EEE, dd MMM'); final _headerFrom = DateFormat('EEE, dd MMM');
final _headerMonth = DateFormat('MMMM yyyy'); final _headerMonth = DateFormat('MMMM yyyy');
final _footerRange = DateFormat('dd/MM/yyyy');
@override @override
void initState() { void initState() {
@ -56,6 +103,7 @@ class _CompactDateRangeDialogState extends State<_CompactDateRangeDialog> {
_start = DateTime(ir.start.year, ir.start.month, ir.start.day); _start = DateTime(ir.start.year, ir.start.month, ir.start.day);
_end = DateTime(ir.end.year, ir.end.month, ir.end.day); _end = DateTime(ir.end.year, ir.end.month, ir.end.day);
_focusedMonth = DateTime(_start!.year, _start!.month, 1); _focusedMonth = DateTime(_start!.year, _start!.month, 1);
_preset = _QuickPreset.custom;
} else { } else {
final now = DateTime.now(); final now = DateTime.now();
final n = DateTime(now.year, now.month, now.day); final n = DateTime(now.year, now.month, now.day);
@ -94,6 +142,7 @@ class _CompactDateRangeDialogState extends State<_CompactDateRangeDialog> {
if (d.isBefore(widget.firstDate) || d.isAfter(widget.lastDate)) return; if (d.isBefore(widget.firstDate) || d.isAfter(widget.lastDate)) return;
setState(() { setState(() {
_preset = _QuickPreset.custom;
if (_start == null || (_start != null && _end != null)) { if (_start == null || (_start != null && _end != null)) {
_start = d; _start = d;
_end = null; _end = null;
@ -108,14 +157,68 @@ class _CompactDateRangeDialogState extends State<_CompactDateRangeDialog> {
}); });
} }
void _clampToBounds(DateTime s, DateTime e) {
var s0 = _norm(s);
var e0 = _norm(e);
if (s0.isBefore(widget.firstDate)) s0 = widget.firstDate;
if (e0.isAfter(widget.lastDate)) e0 = widget.lastDate;
if (s0.isAfter(e0)) e0 = s0;
_start = s0;
_end = e0;
}
void _setToday() {
final n = DateTime.now();
final d = DateTime(n.year, n.month, n.day);
setState(() {
_preset = _QuickPreset.today;
_clampToBounds(d, d);
_focusedMonth = DateTime(d.year, d.month, 1);
});
}
void _setYesterday() {
final n = DateTime.now().subtract(const Duration(days: 1));
final d = DateTime(n.year, n.month, n.day);
setState(() {
_preset = _QuickPreset.yesterday;
_clampToBounds(d, d);
_focusedMonth = DateTime(d.year, d.month, 1);
});
}
void _setThisMonth() {
final n = DateTime.now();
final start = DateTime(n.year, n.month, 1);
final end = DateTime(n.year, n.month + 1, 0);
setState(() {
_preset = _QuickPreset.month;
_clampToBounds(start, end);
_focusedMonth = DateTime(n.year, n.month, 1);
});
}
void _setCustomMode() {
setState(() => _preset = _QuickPreset.custom);
}
void _clear() {
setState(() {
_start = null;
_end = null;
_preset = _QuickPreset.custom;
});
}
bool get _canPrev { bool get _canPrev {
final lastOfPrevMonth = DateTime(_focusedMonth.year, _focusedMonth.month, 0); final lastOfPrevMonth = DateTime(_focusedMonth.year, _focusedMonth.month, 0);
return !lastOfPrevMonth.isBefore(widget.firstDate); return !lastOfPrevMonth.isBefore(widget.firstDate);
} }
/// Right panel shows month after [_focusedMonth]; allow next if that month's last day is not past [lastDate].
bool get _canNext { bool get _canNext {
final firstOfNextMonth = DateTime(_focusedMonth.year, _focusedMonth.month + 1, 1); final lastOfRightMonth = DateTime(_focusedMonth.year, _focusedMonth.month + 2, 0);
return !firstOfNextMonth.isAfter(widget.lastDate); return !lastOfRightMonth.isAfter(widget.lastDate);
} }
void _prevMonth() { void _prevMonth() {
@ -132,23 +235,24 @@ class _CompactDateRangeDialogState extends State<_CompactDateRangeDialog> {
}); });
} }
List<DateTime> _calendarCells() { /// Sunday-first week grid cells for [month] (first day of month).
final first = DateTime(_focusedMonth.year, _focusedMonth.month, 1); List<DateTime> _calendarCellsForMonth(DateTime monthFirst) {
final daysInMonth = DateTime(_focusedMonth.year, _focusedMonth.month + 1, 0).day; final first = DateTime(monthFirst.year, monthFirst.month, 1);
final leading = first.weekday - 1; final daysInMonth = DateTime(monthFirst.year, monthFirst.month + 1, 0).day;
final leading = first.weekday % 7;
final cells = <DateTime>[]; final cells = <DateTime>[];
final prevEnd = DateTime(_focusedMonth.year, _focusedMonth.month, 0); final prevEnd = DateTime(monthFirst.year, monthFirst.month, 0);
for (int i = 0; i < leading; i++) { for (int i = 0; i < leading; i++) {
final dayNum = prevEnd.day - leading + i + 1; final dayNum = prevEnd.day - leading + i + 1;
cells.add(DateTime(prevEnd.year, prevEnd.month, dayNum)); cells.add(DateTime(prevEnd.year, prevEnd.month, dayNum));
} }
for (int d = 1; d <= daysInMonth; d++) { for (int d = 1; d <= daysInMonth; d++) {
cells.add(DateTime(_focusedMonth.year, _focusedMonth.month, d)); cells.add(DateTime(monthFirst.year, monthFirst.month, d));
} }
var next = 1; var next = 1;
while (cells.length % 7 != 0 || cells.length < 42) { while (cells.length % 7 != 0 || cells.length < 42) {
cells.add(DateTime(_focusedMonth.year, _focusedMonth.month + 1, next)); cells.add(DateTime(monthFirst.year, monthFirst.month + 1, next));
next++; next++;
} }
return cells; return cells;
@ -163,8 +267,8 @@ class _CompactDateRangeDialogState extends State<_CompactDateRangeDialog> {
Navigator.of(context).pop(DateTimeRange(start: _start!, end: end)); Navigator.of(context).pop(DateTimeRange(start: _start!, end: end));
} }
Widget _dayCell(DateTime d) { Widget _dayCell(DateTime d, DateTime monthForGrid) {
final inMonth = d.month == _focusedMonth.month; final inMonth = d.month == monthForGrid.month && d.year == monthForGrid.year;
final disabled = final disabled =
d.isBefore(widget.firstDate) || d.isAfter(widget.lastDate); d.isBefore(widget.firstDate) || d.isAfter(widget.lastDate);
@ -173,7 +277,7 @@ class _CompactDateRangeDialogState extends State<_CompactDateRangeDialog> {
child: Text( child: Text(
'${d.day}', '${d.day}',
style: GoogleFonts.inter( style: GoogleFonts.inter(
fontSize: 13, fontSize: 12,
color: const Color(0xFF94A3B8), color: const Color(0xFF94A3B8),
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
@ -209,8 +313,8 @@ class _CompactDateRangeDialogState extends State<_CompactDateRangeDialog> {
), ),
alignment: Alignment.center, alignment: Alignment.center,
child: Container( child: Container(
width: 36, width: 32,
height: 36, height: 32,
alignment: Alignment.center, alignment: Alignment.center,
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
@ -222,7 +326,7 @@ class _CompactDateRangeDialogState extends State<_CompactDateRangeDialog> {
child: Text( child: Text(
'${d.day}', '${d.day}',
style: GoogleFonts.inter( style: GoogleFonts.inter(
fontSize: 13, fontSize: 12,
fontWeight: fontWeight:
(isStart || isEnd) ? FontWeight.w700 : FontWeight.w500, (isStart || isEnd) ? FontWeight.w700 : FontWeight.w500,
color: disabled color: disabled
@ -237,210 +341,331 @@ class _CompactDateRangeDialogState extends State<_CompactDateRangeDialog> {
); );
} }
@override Widget _monthPanel(DateTime monthFirst) {
Widget build(BuildContext context) { final cells = _calendarCellsForMonth(monthFirst);
final cells = _calendarCells(); return Column(
mainAxisSize: MainAxisSize.min,
return Dialog( children: [
insetPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 24), Padding(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), padding: const EdgeInsets.only(bottom: 6),
clipBehavior: Clip.antiAlias, child: Text(
child: ConstrainedBox( _headerMonth.format(monthFirst),
constraints: const BoxConstraints(maxWidth: 400), textAlign: TextAlign.center,
child: Padding( style: GoogleFonts.poppins(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16), fontSize: 13,
child: Column( fontWeight: FontWeight.w600,
mainAxisSize: MainAxisSize.min, color: const Color(0xFF1E293B),
children: [ ),
Row( ),
children: [ ),
IconButton( Row(
icon: const Icon(Icons.close, size: 22), children: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']
color: _muted, .map(
onPressed: () => Navigator.of(context).pop(), (w) => Expanded(
visualDensity: VisualDensity.compact, child: Center(
),
],
),
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( child: Text(
_headerMonth.format(_focusedMonth), w,
textAlign: TextAlign.center, style: GoogleFonts.inter(
style: GoogleFonts.poppins( fontSize: 10,
fontSize: 15,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: const Color(0xFF1E293B), color: _primary,
), ),
), ),
), ),
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]), .toList(),
), ),
const SizedBox(height: 16), const SizedBox(height: 4),
Row( GridView.builder(
children: [ shrinkWrap: true,
Expanded( physics: const NeverScrollableScrollPhysics(),
child: OutlinedButton( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
onPressed: () => Navigator.of(context).pop(), crossAxisCount: 7,
style: OutlinedButton.styleFrom( mainAxisSpacing: 2,
foregroundColor: _primary, crossAxisSpacing: 2,
side: const BorderSide(color: _primary), childAspectRatio: 1.05,
padding: const EdgeInsets.symmetric(vertical: 12), ),
shape: RoundedRectangleBorder( itemCount: cells.length,
borderRadius: BorderRadius.circular(24), itemBuilder: (context, i) => _dayCell(cells[i], monthFirst),
), ),
), ],
child: Text( );
'Cancel', }
style:
GoogleFonts.poppins(fontWeight: FontWeight.w600), Widget _sidebarItem(String label, _QuickPreset preset, VoidCallback onTap) {
), final selected = _preset == preset;
), return Material(
), color: Colors.transparent,
const SizedBox(width: 12), child: InkWell(
Expanded( onTap: () {
child: FilledButton( if (preset == _QuickPreset.custom) {
onPressed: _start != null ? _apply : null, _setCustomMode();
style: FilledButton.styleFrom( } else {
backgroundColor: _primary, onTap();
disabledBackgroundColor: const Color(0xFFCBD5E1), }
padding: const EdgeInsets.symmetric(vertical: 12), },
shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8),
borderRadius: BorderRadius.circular(24), child: Container(
), width: double.infinity,
), padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 10),
child: Text( decoration: BoxDecoration(
'Apply', color: selected ? _sidebarSelected : Colors.transparent,
style: GoogleFonts.poppins( borderRadius: BorderRadius.circular(8),
fontWeight: FontWeight.w600, ),
color: Colors.white, child: Text(
), label,
), style: GoogleFonts.inter(
), fontSize: 13,
), fontWeight: selected ? FontWeight.w600 : FontWeight.w500,
], color: selected ? Colors.white : const Color(0xFF334155),
), ),
],
), ),
), ),
), ),
); );
} }
@override
Widget build(BuildContext context) {
final leftMonth = _focusedMonth;
final rightMonth = DateTime(_focusedMonth.year, _focusedMonth.month + 1, 1);
return Material(
color: Colors.transparent,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: const [
BoxShadow(
color: Color(0x26000000),
blurRadius: 20,
offset: Offset(0, 8),
),
],
),
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: widget.maxWidth,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 148,
color: const Color(0xFFF8FAFC),
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 10),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'Quick select',
style: GoogleFonts.inter(
fontSize: 11,
fontWeight: FontWeight.w600,
color: _muted,
),
),
const SizedBox(height: 8),
_sidebarItem('Today', _QuickPreset.today, _setToday),
_sidebarItem('Yesterday', _QuickPreset.yesterday, _setYesterday),
_sidebarItem('Month', _QuickPreset.month, _setThisMonth),
_sidebarItem('Custom Range', _QuickPreset.custom, _setCustomMode),
],
),
),
const VerticalDivider(width: 1, thickness: 1, color: Color(0xFFE2E8F0)),
Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(12, 8, 12, 12),
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: 10),
Row(
children: [
IconButton(
onPressed: _canPrev ? _prevMonth : null,
icon: const Icon(Icons.chevron_left),
color: _primary,
),
Expanded(
child: Text(
'${_headerMonth.format(leftMonth)} ${_headerMonth.format(rightMonth)}',
textAlign: TextAlign.center,
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w600,
color: const Color(0xFF1E293B),
),
),
),
IconButton(
onPressed: _canNext ? _nextMonth : null,
icon: const Icon(Icons.chevron_right),
color: _primary,
),
],
),
const SizedBox(height: 8),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(child: _monthPanel(leftMonth)),
const SizedBox(width: 12),
Expanded(child: _monthPanel(rightMonth)),
],
),
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.symmetric(
vertical: 10, horizontal: 12),
decoration: BoxDecoration(
color: const Color(0xFFF1F5F9),
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Expanded(
child: Text(
_start != null
? '${_footerRange.format(_start!)} - ${_footerRange.format(_end ?? _start!)}'
: 'Select a range',
style: GoogleFonts.inter(
fontSize: 13,
fontWeight: FontWeight.w600,
color: const Color(0xFF0F172A),
),
),
),
TextButton(
onPressed: _clear,
child: Text(
'Clear',
style: GoogleFonts.poppins(
fontWeight: FontWeight.w600,
color: _muted,
),
),
),
const SizedBox(width: 8),
FilledButton(
onPressed: _start != null ? _apply : null,
style: FilledButton.styleFrom(
backgroundColor: _sidebarSelected,
disabledBackgroundColor: const Color(0xFFCBD5E1),
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 10),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Text(
'Apply',
style: GoogleFonts.poppins(
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
),
],
),
),
],
),
),
),
],
),
),
),
);
}
} }