Merge with suren
This commit is contained in:
commit
44499e14fd
File diff suppressed because one or more lines are too long
@ -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(
|
||||||
@ -473,9 +507,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),
|
||||||
@ -501,7 +535,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,
|
||||||
@ -523,17 +557,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',
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -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 {
|
||||||
@ -1526,28 +1559,16 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
return DateFormat('dd-MM-yyyy').format(dateTime); // 24-hour format
|
return DateFormat('dd-MM-yyyy').format(dateTime); // 24-hour format
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return rawDate; // fallback if parsing fails
|
return rawDate; // fallback if parsing fails
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
@ -184,29 +184,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 + 12 + 10 + 20 + 16 + 11 + 11;
|
|
||||||
return [
|
|
||||||
usable * (2 / totalFlex), // S.NO
|
|
||||||
usable * (14 / totalFlex), // POLICY NO
|
|
||||||
usable * (12 / totalFlex), // VEHICLE TYPE
|
|
||||||
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,
|
||||||
@ -222,13 +206,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,
|
||||||
@ -242,7 +226,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),
|
||||||
@ -252,20 +236,20 @@ 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', 10),
|
||||||
_headerText('VEHICLE TYPE', w[2]),
|
_headerText('VEHICLE TYPE', 10),
|
||||||
_headerText('POLICY DATE', w[3]),
|
_headerText('POLICY DATE', 10, align: TextAlign.center),
|
||||||
_headerText('PARTNER', w[4]),
|
_headerText('PARTNER', 20),
|
||||||
_headerText('CUSTOMER', w[5]),
|
_headerText('CUSTOMER', 10),
|
||||||
_headerText('PREMIUM (₹)', w[6], align: TextAlign.right),
|
_headerText('PREMIUM (₹)', 11, align: TextAlign.right),
|
||||||
_headerText('PAYOUT (₹)', w[7], 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),
|
||||||
@ -275,25 +259,25 @@ 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(), 10),
|
||||||
_cell(p['vehicle_type']?.toString(), w[2]),
|
_cell(p['vehicle_type']?.toString(), 10),
|
||||||
_cell(_formatDate(p['issued_date']?.toString()), w[3]),
|
_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[4],
|
20,
|
||||||
),
|
),
|
||||||
_cell(p['customer_name']?.toString(), w[5]),
|
_cell(p['customer_name']?.toString(), 10),
|
||||||
_cell(
|
_cell(
|
||||||
_amountText(p['premium_amount']),
|
_amountText(p['premium_amount']),
|
||||||
w[6],
|
11,
|
||||||
align: TextAlign.right,
|
align: TextAlign.right,
|
||||||
),
|
),
|
||||||
_cell(
|
_cell(
|
||||||
_amountText(p['commission_amount']),
|
_amountText(p['commission_amount']),
|
||||||
w[7],
|
11,
|
||||||
align: TextAlign.right,
|
align: TextAlign.right,
|
||||||
color: const Color(0xFF009B77),
|
color: const Color(0xFF009B77),
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
@ -355,9 +339,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(
|
||||||
@ -475,7 +459,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
|
||||||
@ -483,7 +466,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(
|
||||||
@ -501,7 +484,6 @@ class _PayOutDetailsViewState extends ConsumerState<PayOutDetailsView> {
|
|||||||
return _policyRow(
|
return _policyRow(
|
||||||
rows[index],
|
rows[index],
|
||||||
startIdx + index,
|
startIdx + index,
|
||||||
widths,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user