|
|
|
|
using Connected.Annotations;
|
|
|
|
|
using Connected.Extensions;
|
|
|
|
|
using Connected.Utilities;
|
|
|
|
|
using Microsoft.AspNetCore.Components;
|
|
|
|
|
|
|
|
|
|
namespace Connected.Components;
|
|
|
|
|
|
|
|
|
|
public partial class DateRangePicker : DatePickerBase
|
|
|
|
|
{
|
|
|
|
|
private DateTime? _firstDate = null, _secondDate;
|
|
|
|
|
private DateRange _dateRange;
|
|
|
|
|
private Range<string> _rangeText;
|
|
|
|
|
|
|
|
|
|
protected override bool IsRange => true;
|
|
|
|
|
|
|
|
|
|
public DateRangePicker()
|
|
|
|
|
{
|
|
|
|
|
DisplayMonths = 2;
|
|
|
|
|
AdornmentAriaLabel = "Open Date Range Picker";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Fired when the DateFormat changes.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter] public EventCallback<DateRange> DateRangeChanged { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The currently selected range (two-way bindable). If null, then nothing was selected.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.FormComponent.Data)]
|
|
|
|
|
public DateRange DateRange
|
|
|
|
|
{
|
|
|
|
|
get => _dateRange;
|
|
|
|
|
set => SetDateRangeAsync(value, true).AndForget();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected async Task SetDateRangeAsync(DateRange range, bool updateValue)
|
|
|
|
|
{
|
|
|
|
|
if (_dateRange != range)
|
|
|
|
|
{
|
|
|
|
|
var doesRangeContainDisabledDates = range?.Start != null && range?.End != null && Enumerable
|
|
|
|
|
.Range(0, int.MaxValue)
|
|
|
|
|
.Select(index => range.Start.Value.AddDays(index))
|
|
|
|
|
.TakeWhile(date => date <= range.End.Value)
|
|
|
|
|
.Any(date => IsDateDisabledFunc(date.Date));
|
|
|
|
|
|
|
|
|
|
if (doesRangeContainDisabledDates)
|
|
|
|
|
{
|
|
|
|
|
_rangeText = null;
|
|
|
|
|
await SetTextAsync(null, false);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_dateRange = range;
|
|
|
|
|
_value = range?.End;
|
|
|
|
|
|
|
|
|
|
if (updateValue)
|
|
|
|
|
{
|
|
|
|
|
Converter.GetError = false;
|
|
|
|
|
if (_dateRange == null)
|
|
|
|
|
{
|
|
|
|
|
_rangeText = null;
|
|
|
|
|
await SetTextAsync(null, false);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
_rangeText = new Range<string>(
|
|
|
|
|
Converter.Set(_dateRange.Start),
|
|
|
|
|
Converter.Set(_dateRange.End));
|
|
|
|
|
await SetTextAsync(_dateRange.ToString(Converter), false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await DateRangeChanged.InvokeAsync(_dateRange);
|
|
|
|
|
BeginValidate();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Range<string> RangeText
|
|
|
|
|
{
|
|
|
|
|
get => _rangeText;
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
if (_rangeText?.Equals(value) ?? value == null)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
Touched = true;
|
|
|
|
|
_rangeText = value;
|
|
|
|
|
SetDateRangeAsync(ParseDateRangeValue(value?.Start, value?.End), false).AndForget();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private RangeInput<string> _rangeInput;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Focuses the start date of MudDateRangePicker
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public ValueTask FocusStartAsync() => _rangeInput.FocusStartAsync();
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Selects the start date of MudDateRangePicker
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public ValueTask SelectStartAsync() => _rangeInput.SelectStartAsync();
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Selects the specified range of the start date text
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="pos1">Start position of the selection</param>
|
|
|
|
|
/// <param name="pos2">End position of the selection</param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public ValueTask SelectRangeStartAsync(int pos1, int pos2) => _rangeInput.SelectRangeStartAsync(pos1, pos2);
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Focuses the end date of MudDateRangePicker
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public ValueTask FocusEndAsync() => _rangeInput.FocusEndAsync();
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Selects the end date of MudDateRangePicker
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public ValueTask SelectEndAsync() => _rangeInput.SelectEndAsync();
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Selects the specified range of the end date text
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="pos1">Start position of the selection</param>
|
|
|
|
|
/// <param name="pos2">End position of the selection</param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public ValueTask SelectRangeEndAsync(int pos1, int pos2) => _rangeInput.SelectRangeEndAsync(pos1, pos2);
|
|
|
|
|
|
|
|
|
|
protected override Task DateFormatChanged(string newFormat)
|
|
|
|
|
{
|
|
|
|
|
Touched = true;
|
|
|
|
|
return SetTextAsync(_dateRange?.ToString(Converter), false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override Task StringValueChanged(string value)
|
|
|
|
|
{
|
|
|
|
|
Touched = true;
|
|
|
|
|
// Update the daterange property (without updating back the Value property)
|
|
|
|
|
return SetDateRangeAsync(ParseDateRangeValue(value), false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override bool HasValue(DateTime? value)
|
|
|
|
|
{
|
|
|
|
|
return null != value && value.HasValue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private DateRange ParseDateRangeValue(string value)
|
|
|
|
|
{
|
|
|
|
|
return DateRange.TryParse(value, Converter, out var dateRange) ? dateRange : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private DateRange ParseDateRangeValue(string start, string end)
|
|
|
|
|
{
|
|
|
|
|
return DateRange.TryParse(start, end, Converter, out var dateRange) ? dateRange : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override void OnPickerClosed()
|
|
|
|
|
{
|
|
|
|
|
_firstDate = null;
|
|
|
|
|
base.OnPickerClosed();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override string GetDayClasses(int month, DateTime day)
|
|
|
|
|
{
|
|
|
|
|
var b = new CssBuilder("mud-day");
|
|
|
|
|
if (day < GetMonthStart(month) || day > GetMonthEnd(month))
|
|
|
|
|
{
|
|
|
|
|
return b.AddClass("mud-hidden").Build();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((_firstDate != null && _secondDate != null && _firstDate < day && _secondDate > day) ||
|
|
|
|
|
(_firstDate == null && _dateRange != null && _dateRange.Start < day && _dateRange.End > day))
|
|
|
|
|
{
|
|
|
|
|
return b
|
|
|
|
|
.AddClass("mud-range")
|
|
|
|
|
.AddClass("mud-range-between")
|
|
|
|
|
.Build();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((_firstDate != null && day == _firstDate) ||
|
|
|
|
|
(_firstDate == null && _dateRange != null && _dateRange.Start == day && DateRange.End != day))
|
|
|
|
|
{
|
|
|
|
|
return b.AddClass("mud-selected")
|
|
|
|
|
.AddClass("mud-range")
|
|
|
|
|
.AddClass("mud-range-start-selected")
|
|
|
|
|
.AddClass("mud-range-selection", _firstDate != null)
|
|
|
|
|
.AddClass($"mud-theme-{Color.ToDescriptionString()}")
|
|
|
|
|
.Build();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((_firstDate != null && _secondDate != null && day == _secondDate) ||
|
|
|
|
|
(_firstDate == null && _dateRange != null && _dateRange.Start != day && _dateRange.End == day))
|
|
|
|
|
{
|
|
|
|
|
return b.AddClass("mud-selected")
|
|
|
|
|
.AddClass("mud-range")
|
|
|
|
|
.AddClass("mud-range-end-selected")
|
|
|
|
|
.AddClass($"mud-theme-{Color.ToDescriptionString()}")
|
|
|
|
|
.Build();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_firstDate == null && _dateRange != null && _dateRange.Start == _dateRange.End && _dateRange.Start == day)
|
|
|
|
|
{
|
|
|
|
|
return b.AddClass("mud-selected").AddClass($"mud-theme-{Color.ToDescriptionString()}").Build();
|
|
|
|
|
}
|
|
|
|
|
else if (_firstDate != null && day > _firstDate)
|
|
|
|
|
{
|
|
|
|
|
return b.AddClass("mud-range")
|
|
|
|
|
.AddClass("mud-range-selection", _secondDate == null)
|
|
|
|
|
.AddClass($"mud-range-selection-{Color.ToDescriptionString()}", _firstDate != null)
|
|
|
|
|
.Build();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (day == DateTime.Today)
|
|
|
|
|
{
|
|
|
|
|
return b.AddClass("mud-current")
|
|
|
|
|
.AddClass("mud-range", _firstDate != null && day > _firstDate)
|
|
|
|
|
.AddClass("mud-range-selection", _firstDate != null && day > _firstDate)
|
|
|
|
|
.AddClass($"mud-range-selection-{Color.ToDescriptionString()}", _firstDate != null && day > _firstDate)
|
|
|
|
|
.AddClass($"mud-{Color.ToDescriptionString()}-text")
|
|
|
|
|
.Build();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return b.Build();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override async void OnDayClicked(DateTime dateTime)
|
|
|
|
|
{
|
|
|
|
|
if (_firstDate == null || _firstDate > dateTime || _secondDate != null)
|
|
|
|
|
{
|
|
|
|
|
_secondDate = null;
|
|
|
|
|
_firstDate = dateTime;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_secondDate = dateTime;
|
|
|
|
|
if (PickerActions == null || AutoClose)
|
|
|
|
|
{
|
|
|
|
|
Submit();
|
|
|
|
|
|
|
|
|
|
if (PickerVariant != PickerVariant.Static)
|
|
|
|
|
{
|
|
|
|
|
await Task.Delay(ClosingDelay);
|
|
|
|
|
Close(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override void OnOpened()
|
|
|
|
|
{
|
|
|
|
|
_secondDate = null;
|
|
|
|
|
base.OnOpened();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected internal override async void Submit()
|
|
|
|
|
{
|
|
|
|
|
if (ReadOnly)
|
|
|
|
|
return;
|
|
|
|
|
if (_firstDate == null || _secondDate == null)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
await SetDateRangeAsync(new DateRange(_firstDate, _secondDate), true);
|
|
|
|
|
|
|
|
|
|
_firstDate = null;
|
|
|
|
|
_secondDate = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void Clear(bool close = true)
|
|
|
|
|
{
|
|
|
|
|
DateRange = null;
|
|
|
|
|
_firstDate = _secondDate = null;
|
|
|
|
|
base.Clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override string GetTitleDateString()
|
|
|
|
|
{
|
|
|
|
|
if (_firstDate != null)
|
|
|
|
|
return $"{FormatTitleDate(_firstDate)} - {FormatTitleDate(_secondDate)}";
|
|
|
|
|
|
|
|
|
|
return DateRange?.Start != null
|
|
|
|
|
? $"{FormatTitleDate(DateRange.Start)} - {FormatTitleDate(DateRange.End)}"
|
|
|
|
|
: "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override DateTime GetCalendarStartOfMonth()
|
|
|
|
|
{
|
|
|
|
|
var date = StartMonth ?? DateRange?.Start ?? DateTime.Today;
|
|
|
|
|
return date.StartOfMonth(Culture);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override int GetCalendarYear(int year)
|
|
|
|
|
{
|
|
|
|
|
var date = DateRange?.Start ?? DateTime.Today;
|
|
|
|
|
var diff = date.Year - year;
|
|
|
|
|
var calenderYear = Culture.Calendar.GetYear(date);
|
|
|
|
|
return calenderYear - diff;
|
|
|
|
|
}
|
|
|
|
|
}
|