feat: sales range date
This commit is contained in:
@@ -0,0 +1,363 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncfusion_flutter_datepicker/datepicker.dart';
|
||||
|
||||
class DateRangePickerBottomSheet {
|
||||
static Future<DateRangePickerSelectionChangedArgs?> show({
|
||||
required BuildContext context,
|
||||
String title = 'Pilih Rentang Tanggal',
|
||||
DateTime? initialStartDate,
|
||||
DateTime? initialEndDate,
|
||||
DateTime? minDate,
|
||||
DateTime? maxDate,
|
||||
String confirmText = 'Pilih',
|
||||
String cancelText = 'Batal',
|
||||
Color primaryColor = Colors.blue,
|
||||
Function(DateTime? startDate, DateTime? endDate)? onChanged,
|
||||
}) async {
|
||||
return await showModalBottomSheet<DateRangePickerSelectionChangedArgs?>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
isDismissible: false,
|
||||
enableDrag: false,
|
||||
builder: (BuildContext context) => _DateRangePickerBottomSheet(
|
||||
title: title,
|
||||
initialStartDate: initialStartDate,
|
||||
initialEndDate: initialEndDate,
|
||||
minDate: minDate,
|
||||
maxDate: maxDate,
|
||||
confirmText: confirmText,
|
||||
cancelText: cancelText,
|
||||
primaryColor: primaryColor,
|
||||
onChanged: onChanged,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DateRangePickerBottomSheet extends StatefulWidget {
|
||||
final String title;
|
||||
final DateTime? initialStartDate;
|
||||
final DateTime? initialEndDate;
|
||||
final DateTime? minDate;
|
||||
final DateTime? maxDate;
|
||||
final String confirmText;
|
||||
final String cancelText;
|
||||
final Color primaryColor;
|
||||
final Function(DateTime? startDate, DateTime? endDate)? onChanged;
|
||||
|
||||
const _DateRangePickerBottomSheet({
|
||||
required this.title,
|
||||
this.initialStartDate,
|
||||
this.initialEndDate,
|
||||
this.minDate,
|
||||
this.maxDate,
|
||||
required this.confirmText,
|
||||
required this.cancelText,
|
||||
required this.primaryColor,
|
||||
this.onChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
State<_DateRangePickerBottomSheet> createState() =>
|
||||
_DateRangePickerBottomSheetState();
|
||||
}
|
||||
|
||||
class _DateRangePickerBottomSheetState
|
||||
extends State<_DateRangePickerBottomSheet>
|
||||
with TickerProviderStateMixin {
|
||||
DateRangePickerSelectionChangedArgs? _selectionChangedArgs;
|
||||
late AnimationController _animationController;
|
||||
late Animation<double> _slideAnimation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_animationController = AnimationController(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
vsync: this,
|
||||
);
|
||||
_slideAnimation = Tween<double>(begin: 1.0, end: 0.0).animate(
|
||||
CurvedAnimation(parent: _animationController, curve: Curves.easeOutCubic),
|
||||
);
|
||||
_animationController.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_animationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onSelectionChanged(DateRangePickerSelectionChangedArgs args) {
|
||||
setState(() {
|
||||
_selectionChangedArgs = args;
|
||||
});
|
||||
}
|
||||
|
||||
String _getSelectionText() {
|
||||
if (_selectionChangedArgs?.value is PickerDateRange) {
|
||||
final PickerDateRange range = _selectionChangedArgs!.value;
|
||||
if (range.startDate != null && range.endDate != null) {
|
||||
return '${_formatDate(range.startDate!)} - ${_formatDate(range.endDate!)}';
|
||||
} else if (range.startDate != null) {
|
||||
return _formatDate(range.startDate!);
|
||||
}
|
||||
}
|
||||
return 'Belum ada tanggal dipilih';
|
||||
}
|
||||
|
||||
String _formatDate(DateTime date) {
|
||||
final months = [
|
||||
'Jan',
|
||||
'Feb',
|
||||
'Mar',
|
||||
'Apr',
|
||||
'Mei',
|
||||
'Jun',
|
||||
'Jul',
|
||||
'Agu',
|
||||
'Sep',
|
||||
'Okt',
|
||||
'Nov',
|
||||
'Des',
|
||||
];
|
||||
return '${date.day} ${months[date.month - 1]} ${date.year}';
|
||||
}
|
||||
|
||||
bool get _isValidSelection {
|
||||
if (_selectionChangedArgs?.value is PickerDateRange) {
|
||||
final PickerDateRange range = _selectionChangedArgs!.value;
|
||||
return range.startDate != null && range.endDate != null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenHeight = MediaQuery.of(context).size.height;
|
||||
final bottomSheetHeight = screenHeight * 0.75;
|
||||
|
||||
return AnimatedBuilder(
|
||||
animation: _animationController,
|
||||
builder: (context, child) {
|
||||
return Transform.translate(
|
||||
offset: Offset(0, _slideAnimation.value * bottomSheetHeight),
|
||||
child: Container(
|
||||
height: bottomSheetHeight,
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(24),
|
||||
topRight: Radius.circular(24),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Drag Handle
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 12, bottom: 8),
|
||||
width: 40,
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade300,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
|
||||
// Content
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
children: [
|
||||
// Selection Info
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: widget.primaryColor.withOpacity(0.08),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: widget.primaryColor.withOpacity(0.2),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Tanggal Terpilih:',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: widget.primaryColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
_getSelectionText(),
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Date Picker
|
||||
Container(
|
||||
height: 320,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: Colors.grey.withOpacity(0.2),
|
||||
),
|
||||
),
|
||||
child: SfDateRangePicker(
|
||||
onSelectionChanged: _onSelectionChanged,
|
||||
selectionMode: DateRangePickerSelectionMode.range,
|
||||
initialSelectedRange:
|
||||
(widget.initialStartDate != null &&
|
||||
widget.initialEndDate != null)
|
||||
? PickerDateRange(
|
||||
widget.initialStartDate,
|
||||
widget.initialEndDate,
|
||||
)
|
||||
: null,
|
||||
minDate: widget.minDate,
|
||||
maxDate: widget.maxDate,
|
||||
startRangeSelectionColor: widget.primaryColor,
|
||||
endRangeSelectionColor: widget.primaryColor,
|
||||
rangeSelectionColor: widget.primaryColor
|
||||
.withOpacity(0.2),
|
||||
todayHighlightColor: widget.primaryColor,
|
||||
headerStyle: DateRangePickerHeaderStyle(
|
||||
backgroundColor: Colors.transparent,
|
||||
textAlign: TextAlign.center,
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
monthViewSettings: DateRangePickerMonthViewSettings(
|
||||
viewHeaderStyle: DateRangePickerViewHeaderStyle(
|
||||
backgroundColor: Colors.grey.withOpacity(0.1),
|
||||
textStyle: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: widget.primaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
selectionTextStyle: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
),
|
||||
rangeTextStyle: TextStyle(
|
||||
color: widget.primaryColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Bottom Fixed Action Buttons
|
||||
Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
border: Border(
|
||||
top: BorderSide(
|
||||
color: Colors.grey.withOpacity(0.2),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
side: BorderSide(color: Colors.grey.shade400),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
widget.cancelText,
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: _isValidSelection
|
||||
? () {
|
||||
// Call onChanged when confirm button is pressed
|
||||
if (widget.onChanged != null &&
|
||||
_selectionChangedArgs?.value
|
||||
is PickerDateRange) {
|
||||
final PickerDateRange range =
|
||||
_selectionChangedArgs!.value;
|
||||
widget.onChanged!(
|
||||
range.startDate,
|
||||
range.endDate,
|
||||
);
|
||||
}
|
||||
Navigator.of(
|
||||
context,
|
||||
).pop(_selectionChangedArgs);
|
||||
}
|
||||
: null,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: widget.primaryColor,
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
disabledBackgroundColor: Colors.grey.shade300,
|
||||
),
|
||||
child: Text(
|
||||
widget.confirmText,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: _isValidSelection
|
||||
? Colors.white
|
||||
: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,531 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncfusion_flutter_datepicker/datepicker.dart';
|
||||
|
||||
import '../../../common/theme/theme.dart';
|
||||
import '../bottom_sheet/date_range_bottom_sheet.dart';
|
||||
|
||||
class DateRangePickerField extends StatefulWidget {
|
||||
final String? label;
|
||||
final String placeholder;
|
||||
final DateTime? startDate;
|
||||
final DateTime? endDate;
|
||||
final DateTime? minDate;
|
||||
final DateTime? maxDate;
|
||||
final Function(DateTime? startDate, DateTime? endDate)? onChanged;
|
||||
final Color primaryColor;
|
||||
final bool enabled;
|
||||
final String? errorText;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
final TextStyle? textStyle;
|
||||
final TextStyle? placeholderStyle;
|
||||
final BoxDecoration? decoration;
|
||||
final double height;
|
||||
|
||||
const DateRangePickerField({
|
||||
Key? key,
|
||||
this.label,
|
||||
this.placeholder = 'Pilih rentang tanggal',
|
||||
this.startDate,
|
||||
this.endDate,
|
||||
this.minDate,
|
||||
this.maxDate,
|
||||
this.onChanged,
|
||||
this.primaryColor = AppColor.primary,
|
||||
this.enabled = true,
|
||||
this.errorText,
|
||||
this.padding,
|
||||
this.textStyle,
|
||||
this.placeholderStyle,
|
||||
this.decoration,
|
||||
this.height = 52.0,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<DateRangePickerField> createState() => _DateRangePickerFieldState();
|
||||
}
|
||||
|
||||
class _DateRangePickerFieldState extends State<DateRangePickerField> {
|
||||
bool _isPressed = false;
|
||||
|
||||
String get _displayText {
|
||||
if (widget.startDate != null && widget.endDate != null) {
|
||||
return '${_formatDate(widget.startDate!)} - ${_formatDate(widget.endDate!)}';
|
||||
} else if (widget.startDate != null) {
|
||||
return _formatDate(widget.startDate!);
|
||||
}
|
||||
return widget.placeholder;
|
||||
}
|
||||
|
||||
bool get _hasValue {
|
||||
return widget.startDate != null || widget.endDate != null;
|
||||
}
|
||||
|
||||
String _formatDate(DateTime date) {
|
||||
final months = [
|
||||
'Jan',
|
||||
'Feb',
|
||||
'Mar',
|
||||
'Apr',
|
||||
'Mei',
|
||||
'Jun',
|
||||
'Jul',
|
||||
'Agu',
|
||||
'Sep',
|
||||
'Okt',
|
||||
'Nov',
|
||||
'Des',
|
||||
];
|
||||
return '${date.day} ${months[date.month - 1]} ${date.year}';
|
||||
}
|
||||
|
||||
Future<void> _showDateRangePicker() async {
|
||||
if (!widget.enabled) return;
|
||||
|
||||
final result = await DateRangePickerBottomSheet.show(
|
||||
context: context,
|
||||
title: widget.label ?? 'Pilih Rentang Tanggal',
|
||||
initialStartDate: widget.startDate,
|
||||
initialEndDate: widget.endDate,
|
||||
minDate: widget.minDate,
|
||||
maxDate: widget.maxDate,
|
||||
primaryColor: widget.primaryColor,
|
||||
onChanged: widget.onChanged,
|
||||
);
|
||||
|
||||
if (result != null && widget.onChanged != null) {
|
||||
if (result.value is PickerDateRange) {
|
||||
final PickerDateRange range = result.value;
|
||||
widget.onChanged!(range.startDate, range.endDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hasError = widget.errorText != null && widget.errorText!.isNotEmpty;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Label
|
||||
if (widget.label != null) ...[
|
||||
Text(
|
||||
widget.label!,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: hasError ? AppColor.error : AppColor.textSecondary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
|
||||
// Input Field
|
||||
GestureDetector(
|
||||
onTap: _showDateRangePicker,
|
||||
onTapDown: widget.enabled
|
||||
? (_) => setState(() => _isPressed = true)
|
||||
: null,
|
||||
onTapUp: widget.enabled
|
||||
? (_) => setState(() => _isPressed = false)
|
||||
: null,
|
||||
onTapCancel: widget.enabled
|
||||
? () => setState(() => _isPressed = false)
|
||||
: null,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
height: widget.height,
|
||||
padding:
|
||||
widget.padding ?? const EdgeInsets.symmetric(horizontal: 16),
|
||||
decoration:
|
||||
widget.decoration ??
|
||||
BoxDecoration(
|
||||
color: widget.enabled
|
||||
? (_isPressed ? AppColor.backgroundLight : AppColor.white)
|
||||
: AppColor.background,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: hasError
|
||||
? AppColor.error
|
||||
: (_isPressed ? widget.primaryColor : AppColor.border),
|
||||
width: _isPressed ? 2 : 1,
|
||||
),
|
||||
boxShadow: _isPressed && widget.enabled
|
||||
? [
|
||||
BoxShadow(
|
||||
color: widget.primaryColor.withOpacity(0.1),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
]
|
||||
: null,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// Date Text
|
||||
Expanded(
|
||||
child: Text(
|
||||
_displayText,
|
||||
style:
|
||||
widget.textStyle ??
|
||||
TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: _hasValue
|
||||
? FontWeight.w500
|
||||
: FontWeight.w400,
|
||||
color: widget.enabled
|
||||
? (_hasValue
|
||||
? AppColor.textPrimary
|
||||
: AppColor.textSecondary)
|
||||
: AppColor.textLight,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Calendar Icon
|
||||
Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: widget.primaryColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.calendar_today_rounded,
|
||||
size: 20,
|
||||
color: widget.enabled
|
||||
? widget.primaryColor
|
||||
: AppColor.textLight,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Error Text
|
||||
if (hasError) ...[
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
widget.errorText!,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColor.error,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Variasi dengan style yang berbeda
|
||||
class DateRangePickerFieldOutlined extends StatefulWidget {
|
||||
final String? label;
|
||||
final String placeholder;
|
||||
final DateTime? startDate;
|
||||
final DateTime? endDate;
|
||||
final DateTime? minDate;
|
||||
final DateTime? maxDate;
|
||||
final Function(DateTime? startDate, DateTime? endDate)? onChanged;
|
||||
final Color primaryColor;
|
||||
final bool enabled;
|
||||
final String? errorText;
|
||||
|
||||
const DateRangePickerFieldOutlined({
|
||||
Key? key,
|
||||
this.label,
|
||||
this.placeholder = 'Pilih rentang tanggal',
|
||||
this.startDate,
|
||||
this.endDate,
|
||||
this.minDate,
|
||||
this.maxDate,
|
||||
this.onChanged,
|
||||
this.primaryColor = AppColor.primary,
|
||||
this.enabled = true,
|
||||
this.errorText,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<DateRangePickerFieldOutlined> createState() =>
|
||||
_DateRangePickerFieldOutlinedState();
|
||||
}
|
||||
|
||||
class _DateRangePickerFieldOutlinedState
|
||||
extends State<DateRangePickerFieldOutlined> {
|
||||
bool _isFocused = false;
|
||||
|
||||
String get _displayText {
|
||||
if (widget.startDate != null && widget.endDate != null) {
|
||||
return '${_formatDate(widget.startDate!)} - ${_formatDate(widget.endDate!)}';
|
||||
} else if (widget.startDate != null) {
|
||||
return _formatDate(widget.startDate!);
|
||||
}
|
||||
return widget.placeholder;
|
||||
}
|
||||
|
||||
bool get _hasValue {
|
||||
return widget.startDate != null || widget.endDate != null;
|
||||
}
|
||||
|
||||
String _formatDate(DateTime date) {
|
||||
final months = [
|
||||
'Jan',
|
||||
'Feb',
|
||||
'Mar',
|
||||
'Apr',
|
||||
'Mei',
|
||||
'Jun',
|
||||
'Jul',
|
||||
'Agu',
|
||||
'Sep',
|
||||
'Okt',
|
||||
'Nov',
|
||||
'Des',
|
||||
];
|
||||
return '${date.day} ${months[date.month - 1]} ${date.year}';
|
||||
}
|
||||
|
||||
Future<void> _showDateRangePicker() async {
|
||||
if (!widget.enabled) return;
|
||||
|
||||
setState(() => _isFocused = true);
|
||||
|
||||
final result = await DateRangePickerBottomSheet.show(
|
||||
context: context,
|
||||
title: widget.label ?? 'Pilih Rentang Tanggal',
|
||||
initialStartDate: widget.startDate,
|
||||
initialEndDate: widget.endDate,
|
||||
minDate: widget.minDate,
|
||||
maxDate: widget.maxDate,
|
||||
primaryColor: widget.primaryColor,
|
||||
onChanged: widget.onChanged,
|
||||
);
|
||||
|
||||
setState(() => _isFocused = false);
|
||||
|
||||
if (result != null && widget.onChanged != null) {
|
||||
if (result.value is PickerDateRange) {
|
||||
final PickerDateRange range = result.value;
|
||||
widget.onChanged!(range.startDate, range.endDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hasError = widget.errorText != null && widget.errorText!.isNotEmpty;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: _showDateRangePicker,
|
||||
child: Container(
|
||||
height: 56,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: hasError
|
||||
? AppColor.error
|
||||
: (_isFocused || _hasValue
|
||||
? widget.primaryColor
|
||||
: AppColor.border),
|
||||
width: _isFocused ? 2 : 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(width: 16),
|
||||
|
||||
// Date Text
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (widget.label != null && (_isFocused || _hasValue))
|
||||
Text(
|
||||
widget.label!,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: hasError
|
||||
? AppColor.error
|
||||
: widget.primaryColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
_hasValue
|
||||
? _displayText
|
||||
: (widget.label ?? widget.placeholder),
|
||||
style: TextStyle(
|
||||
fontSize: _hasValue ? 16 : 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: widget.enabled
|
||||
? (_hasValue
|
||||
? AppColor.textPrimary
|
||||
: AppColor.textSecondary)
|
||||
: AppColor.textLight,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Calendar Icon
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 16),
|
||||
child: Icon(
|
||||
Icons.calendar_today_rounded,
|
||||
size: 24,
|
||||
color: widget.enabled
|
||||
? (_isFocused
|
||||
? widget.primaryColor
|
||||
: AppColor.textSecondary)
|
||||
: AppColor.textLight,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Error Text
|
||||
if (hasError) ...[
|
||||
const SizedBox(height: 6),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16),
|
||||
child: Text(
|
||||
widget.errorText!,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColor.error,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Usage Example Widget
|
||||
class DateRangePickerExample extends StatefulWidget {
|
||||
@override
|
||||
_DateRangePickerExampleState createState() => _DateRangePickerExampleState();
|
||||
}
|
||||
|
||||
class _DateRangePickerExampleState extends State<DateRangePickerExample> {
|
||||
DateTime? _startDate;
|
||||
DateTime? _endDate;
|
||||
DateTime? _startDate2;
|
||||
DateTime? _endDate2;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Date Range Picker Example'),
|
||||
backgroundColor: AppColor.primary,
|
||||
foregroundColor: AppColor.white,
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Default Style',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
DateRangePickerField(
|
||||
label: 'Periode Laporan',
|
||||
placeholder: 'Pilih tanggal mulai - selesai',
|
||||
startDate: _startDate,
|
||||
endDate: _endDate,
|
||||
primaryColor: AppColor.primary,
|
||||
onChanged: (start, end) {
|
||||
setState(() {
|
||||
_startDate = start;
|
||||
_endDate = end;
|
||||
});
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: 32),
|
||||
|
||||
Text(
|
||||
'Outlined Style',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
DateRangePickerFieldOutlined(
|
||||
label: 'Rentang Waktu',
|
||||
placeholder: 'Pilih rentang tanggal',
|
||||
startDate: _startDate2,
|
||||
endDate: _endDate2,
|
||||
primaryColor: AppColor.secondary,
|
||||
onChanged: (start, end) {
|
||||
setState(() {
|
||||
_startDate2 = start;
|
||||
_endDate2 = end;
|
||||
});
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Display selected dates
|
||||
if (_startDate != null ||
|
||||
_endDate != null ||
|
||||
_startDate2 != null ||
|
||||
_endDate2 != null)
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.background,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Selected Dates:',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (_startDate != null)
|
||||
Text(
|
||||
'Default: ${_startDate!} - ${_endDate ?? 'Not selected'}',
|
||||
),
|
||||
if (_startDate2 != null)
|
||||
Text(
|
||||
'Outlined: ${_startDate2!} - ${_endDate2 ?? 'Not selected'}',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user