625 lines
20 KiB
Plaintext
625 lines
20 KiB
Plaintext
@page "/Calendar"
|
|
@using Template.Shared.Core.Dto
|
|
@using Template.Shared.Core.Interface
|
|
@using Template.Shared.Components.Layout
|
|
@using Template.Shared.Components.SingleElements
|
|
@using Template.Shared.Components.Layout.Spinner
|
|
@using Template.Shared.Components.SingleElements.BottomSheet
|
|
@using Template.Shared.Core.Entity
|
|
@using Template.Shared.Core.Messages.Activity.New
|
|
@inject IManageDataService ManageData
|
|
@inject IJSRuntime JS
|
|
@inject NewActivityService NewActivity
|
|
|
|
<HeaderLayout Title="@_headerTitle"
|
|
ShowFilter="true"
|
|
ShowCalendarToggle="true"
|
|
OnFilterToggle="ToggleFilter"
|
|
OnCalendarToggle="ToggleExpanded"/>
|
|
|
|
<div @ref="_weekSliderRef" class="container week-slider @(Expanded ? "expanded" : "") @(SliderAnimation)">
|
|
@if (Expanded)
|
|
{
|
|
<!-- Vista mensile -->
|
|
@foreach (var nomeGiorno in GiorniSettimana)
|
|
{
|
|
<div class="week-day">
|
|
<div>@nomeGiorno</div>
|
|
</div>
|
|
}
|
|
|
|
@foreach (var unused in Enumerable.Range(0, StartOffset))
|
|
{
|
|
<div class="day" style="visibility: hidden"></div>
|
|
}
|
|
|
|
@if (_isInitialized && _monthDaysData.Length > 0)
|
|
{
|
|
@for (var d = 1; d <= DaysInMonth; d++)
|
|
{
|
|
var day = new DateTime(CurrentMonth.Year, CurrentMonth.Month, d);
|
|
var dayData = _monthDaysData[d - 1];
|
|
|
|
<div class="day @dayData.CssClass"
|
|
@onclick="() => SelezionaDataDalMese(day)">
|
|
<div>@d</div>
|
|
@if (dayData.HasEvents)
|
|
{
|
|
<div class="event-dot-container" style="margin-top: 2px;">
|
|
@foreach (var cat in dayData.EventCategories)
|
|
{
|
|
<div class="event-dot @cat.CssClass" title="@cat.Title"></div>
|
|
}
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
}
|
|
else
|
|
{
|
|
@* Fallback rendering per prima inizializzazione *@
|
|
@for (var d = 1; d <= DaysInMonth; d++)
|
|
{
|
|
var day = new DateTime(CurrentMonth.Year, CurrentMonth.Month, d);
|
|
var isSelected = IsSameDay(day, SelectedDate);
|
|
var isToday = IsSameDay(day, DateTime.Today);
|
|
var events = GetEventsForDay(day);
|
|
|
|
<div class="day @(isSelected ? "selected" : (isToday ? "today" : ""))"
|
|
@onclick="() => SelezionaDataDalMese(day)">
|
|
<div>@d</div>
|
|
@if (events.Any())
|
|
{
|
|
<div class="event-dot-container" style="margin-top: 2px;">
|
|
@foreach (var cat in events.Select(x => x.Category).Distinct())
|
|
{
|
|
<div class="event-dot @cat.ConvertToHumanReadable()" title="@cat.ConvertToHumanReadable()"></div>
|
|
}
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
}
|
|
|
|
@foreach (var unused in Enumerable.Range(0, EndOffset))
|
|
{
|
|
<div class="day" style="visibility: hidden"></div>
|
|
}
|
|
}
|
|
else
|
|
{
|
|
<!-- Vista settimanale -->
|
|
@if (_isInitialized && _weekDaysData.Length == 7 && _weekDaysData[0].Date != default)
|
|
{
|
|
@for (int i = 0; i < 7; i++)
|
|
{
|
|
var dayData = _weekDaysData[i];
|
|
var day = dayData.Date;
|
|
|
|
<div class="week-day">
|
|
<div>@dayData.DayName</div>
|
|
<div class="day @dayData.CssClass"
|
|
@onclick="() => SelezionaData(day)">
|
|
<div>@day.Day</div>
|
|
@if (dayData.HasEvents)
|
|
{
|
|
<div class="event-dot-container" style="margin-top: 2px;">
|
|
@foreach (var cat in dayData.EventCategories)
|
|
{
|
|
<div class="event-dot @cat.CssClass" title="@cat.Title"></div>
|
|
}
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var start = GetStartOfWeek(SelectedDate);
|
|
var culture = new System.Globalization.CultureInfo("it-IT");
|
|
|
|
|
|
for (var i = 0; i < 7; i++)
|
|
{
|
|
var day = start.AddDays(i);
|
|
var isSelected = IsSameDay(day, SelectedDate);
|
|
var isToday = IsSameDay(day, DateTime.Today);
|
|
var events = GetEventsForDay(day);
|
|
|
|
<div class="week-day">
|
|
<div>@day.ToString("ddd", culture)</div>
|
|
<div class="day @(isSelected ? "selected" : (isToday ? "today" : ""))"
|
|
@onclick="() => SelezionaData(day)"
|
|
aria-label="@day.ToString("dddd d MMMM", culture)">
|
|
<div>@day.Day</div>
|
|
@if (events.Any())
|
|
{
|
|
<div class="event-dot-container" style="margin-top: 2px;">
|
|
@foreach (var cat in events.Select(x => x.Category).Distinct())
|
|
{
|
|
<div class="event-dot @cat.ConvertToHumanReadable()" title="@cat.ConvertToHumanReadable()"></div>
|
|
}
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
}
|
|
}
|
|
}
|
|
</div>
|
|
|
|
<div class="container appointments">
|
|
@if (IsLoading)
|
|
{
|
|
<SpinnerLayout FullScreen="false"/>
|
|
}
|
|
else if (FilteredActivities is { Count: > 0 })
|
|
{
|
|
<Virtualize Items="FilteredActivities" Context="activity">
|
|
<ActivityCard Activity="activity" ActivityChanged="OnActivityChanged" ActivityDeleted="OnActivityDeleted" />
|
|
</Virtualize>
|
|
}
|
|
else
|
|
{
|
|
<NoDataAvailable Text="Nessuna attività trovata" ImageSource="_content/Template.Shared/images/undraw_file-search_cbur.svg"/>
|
|
}
|
|
</div>
|
|
|
|
<FilterActivity @bind-IsSheetVisible="OpenFilter" @bind-Filter="Filter" @bind-Filter:after="ApplyFilter"/>
|
|
|
|
@code {
|
|
|
|
// Modelli per ottimizzazione rendering
|
|
private record DayData(DateTime Date, string CssClass, bool HasEvents, CategoryData[] EventCategories, string DayName = "");
|
|
|
|
private record CategoryData(string CssClass, string Title);
|
|
|
|
// Cache per rendering
|
|
private DayData[] _monthDaysData = [];
|
|
private readonly DayData[] _weekDaysData = new DayData[7];
|
|
private string _headerTitle = string.Empty;
|
|
private readonly Dictionary<DateTime, List<ActivityDTO>> _eventsCache = new();
|
|
private readonly Dictionary<DateTime, CategoryData[]> _categoriesCache = new();
|
|
private bool _isInitialized = false;
|
|
|
|
// Stato UI
|
|
private bool Expanded { get; set; }
|
|
private string SliderAnimation { get; set; } = string.Empty;
|
|
private ElementReference _weekSliderRef;
|
|
private DotNetObjectReference<Calendar>? _dotNetHelper;
|
|
|
|
// Stato calendario
|
|
private DateTime SelectedDate { get; set; } = DateTime.Today;
|
|
private DateTime _internalMonth = DateTime.Today;
|
|
private DateTime CurrentMonth => new(_internalMonth.Year, _internalMonth.Month, 1);
|
|
|
|
// Stato attività
|
|
private List<ActivityDTO> MonthActivities { get; set; } = [];
|
|
private List<ActivityDTO> FilteredActivities { get; set; } = [];
|
|
private bool IsLoading { get; set; } = true;
|
|
|
|
// Supporto rendering mese
|
|
private static readonly string[] GiorniSettimana = ["Lu", "Ma", "Me", "Gi", "Ve", "Sa", "Do"];
|
|
private int DaysInMonth => DateTime.DaysInMonth(CurrentMonth.Year, CurrentMonth.Month);
|
|
private int StartOffset => (int)CurrentMonth.DayOfWeek == 0 ? 6 : (int)CurrentMonth.DayOfWeek - 1;
|
|
|
|
//Filtri
|
|
private bool OpenFilter { get; set; }
|
|
private FilterActivityDTO Filter { get; set; } = new();
|
|
|
|
private int EndOffset
|
|
{
|
|
get
|
|
{
|
|
var totalCells = (int)Math.Ceiling((DaysInMonth + StartOffset) / 7.0) * 7;
|
|
return totalCells - (DaysInMonth + StartOffset);
|
|
}
|
|
}
|
|
|
|
protected override void OnInitialized()
|
|
{
|
|
PrepareRenderingData();
|
|
|
|
NewActivity.OnActivityCreated += async activityId => await OnActivityCreated(activityId);
|
|
}
|
|
|
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
{
|
|
if (firstRender)
|
|
{
|
|
Filter.User = new HashSet<string> { UserSession.User.Username };
|
|
|
|
_dotNetHelper = DotNetObjectReference.Create(this);
|
|
await JS.InvokeVoidAsync("calendarSwipe.register", _weekSliderRef, _dotNetHelper);
|
|
_internalMonth = new DateTime(SelectedDate.Year, SelectedDate.Month, 1);
|
|
await LoadMonthData();
|
|
|
|
_isInitialized = true;
|
|
ApplyFilter();
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
|
|
// Metodo per preparare i dati di rendering una sola volta
|
|
private void PrepareRenderingData()
|
|
{
|
|
PrepareHeaderTitle();
|
|
PrepareEventsCache();
|
|
|
|
if (Expanded)
|
|
{
|
|
PrepareMonthDaysData();
|
|
}
|
|
else
|
|
{
|
|
PrepareWeekDaysData();
|
|
}
|
|
}
|
|
|
|
private void PrepareHeaderTitle()
|
|
{
|
|
_headerTitle = CurrentMonth.ToString("MMMM yyyy", new System.Globalization.CultureInfo("it-IT")).FirstCharToUpper();
|
|
}
|
|
|
|
private void PrepareEventsCache()
|
|
{
|
|
_eventsCache.Clear();
|
|
_categoriesCache.Clear();
|
|
|
|
// Raggruppa le attività per data
|
|
var activitiesByDate = MonthActivities
|
|
.GroupBy(x => (x.EffectiveDate ?? x.EstimatedDate!).Value.Date)
|
|
.ToDictionary(g => g.Key, g => g.ToList());
|
|
|
|
foreach (var (date, activities) in activitiesByDate)
|
|
{
|
|
_eventsCache[date] = activities;
|
|
|
|
// Pre-calcola le categorie per ogni giorno
|
|
var categories = activities
|
|
.Select(x => x.Category)
|
|
.Distinct()
|
|
.Select(cat => new CategoryData(cat.ConvertToHumanReadable(), cat.ConvertToHumanReadable()))
|
|
.ToArray();
|
|
|
|
_categoriesCache[date] = categories;
|
|
}
|
|
}
|
|
|
|
private void PrepareMonthDaysData()
|
|
{
|
|
_monthDaysData = new DayData[DaysInMonth];
|
|
var today = DateTime.Today;
|
|
|
|
for (var d = 1; d <= DaysInMonth; d++)
|
|
{
|
|
var day = new DateTime(CurrentMonth.Year, CurrentMonth.Month, d);
|
|
var isSelected = day.Date == SelectedDate.Date;
|
|
var isToday = day.Date == today;
|
|
|
|
var cssClass = isSelected ? "selected" : (isToday ? "today" : "");
|
|
var hasEvents = _eventsCache.ContainsKey(day.Date);
|
|
var eventCategories = hasEvents ? GetFilteredCategoriesForDay(day.Date) : [];
|
|
|
|
_monthDaysData[d - 1] = new DayData(day, cssClass, eventCategories.Length > 0, eventCategories);
|
|
}
|
|
}
|
|
|
|
private void PrepareWeekDaysData()
|
|
{
|
|
var start = GetStartOfWeek(SelectedDate);
|
|
var today = DateTime.Today;
|
|
var culture = new System.Globalization.CultureInfo("it-IT");
|
|
|
|
for (var i = 0; i < 7; i++)
|
|
{
|
|
var day = start.AddDays(i);
|
|
var isSelected = day.Date == SelectedDate.Date;
|
|
var isToday = day.Date == today;
|
|
|
|
var cssClass = isSelected ? "selected" : (isToday ? "today" : "");
|
|
var dayName = day.ToString("ddd", culture);
|
|
var hasEvents = _eventsCache.ContainsKey(day.Date);
|
|
var eventCategories = hasEvents ? GetFilteredCategoriesForDay(day.Date) : [];
|
|
|
|
_weekDaysData[i] = new DayData(day, cssClass, eventCategories.Length > 0, eventCategories, dayName);
|
|
}
|
|
}
|
|
|
|
private CategoryData[] GetFilteredCategoriesForDay(DateTime date)
|
|
{
|
|
if (!_categoriesCache.TryGetValue(date, out var categories))
|
|
return [];
|
|
|
|
if (Filter.ClearFilter)
|
|
return categories;
|
|
|
|
// Applica i filtri alle categorie
|
|
var filteredActivities = GetFilteredActivitiesForDay(date);
|
|
if (!filteredActivities.Any())
|
|
return [];
|
|
|
|
return filteredActivities
|
|
.Select(x => x.Category)
|
|
.Distinct()
|
|
.Select(cat => new CategoryData(cat.ConvertToHumanReadable(), cat.ConvertToHumanReadable()))
|
|
.ToArray();
|
|
}
|
|
|
|
private List<ActivityDTO> GetFilteredActivitiesForDay(DateTime date)
|
|
{
|
|
if (!_eventsCache.TryGetValue(date, out var activities))
|
|
return [];
|
|
|
|
if (Filter.ClearFilter)
|
|
return activities;
|
|
|
|
var filteredActivity = activities.AsQueryable();
|
|
|
|
filteredActivity = filteredActivity
|
|
.Where(x => Filter.Text.IsNullOrEmpty() || (x.ActivityDescription != null && x.ActivityDescription.ContainsIgnoreCase(Filter.Text!)));
|
|
|
|
filteredActivity = filteredActivity
|
|
.Where(x => Filter.Type.IsNullOrEmpty() || (x.ActivityTypeId != null && x.ActivityTypeId.Equals(Filter.Type)));
|
|
|
|
filteredActivity = filteredActivity
|
|
.Where(x => Filter.Result.IsNullOrEmpty() || (x.ActivityResultId != null && x.ActivityResultId.Equals(Filter.Result)));
|
|
|
|
filteredActivity = filteredActivity
|
|
.Where(x => Filter.User.IsNullOrEmpty() || (x.UserName != null && Filter.User!.Contains(x.UserName)));
|
|
|
|
filteredActivity = filteredActivity
|
|
.Where(x => Filter.Category == null || x.Category.Equals(Filter.Category));
|
|
|
|
return filteredActivity.ToList();
|
|
}
|
|
|
|
[JSInvokable]
|
|
public async Task OnSwipeLeft()
|
|
{
|
|
await CambiaPeriodo(1);
|
|
PrepareRenderingData();
|
|
StateHasChanged();
|
|
if (Expanded)
|
|
{
|
|
await LoadMonthData();
|
|
}
|
|
}
|
|
|
|
[JSInvokable]
|
|
public async Task OnSwipeRight()
|
|
{
|
|
await CambiaPeriodo(-1);
|
|
PrepareRenderingData();
|
|
StateHasChanged();
|
|
if (Expanded)
|
|
{
|
|
await LoadMonthData();
|
|
}
|
|
}
|
|
|
|
[JSInvokable]
|
|
public async Task OnSwipeDown()
|
|
{
|
|
if (!Expanded)
|
|
ToggleExpanded();
|
|
}
|
|
|
|
[JSInvokable]
|
|
public async Task OnSwipeUp()
|
|
{
|
|
if (Expanded)
|
|
ToggleExpanded();
|
|
}
|
|
|
|
// Cambio periodo mese/settimana
|
|
private async Task CambiaPeriodo(int direzione)
|
|
{
|
|
if (Expanded)
|
|
{
|
|
var y = CurrentMonth.Year;
|
|
var m = CurrentMonth.Month + direzione;
|
|
if (m < 1)
|
|
{
|
|
y--;
|
|
m = 12;
|
|
}
|
|
|
|
if (m > 12)
|
|
{
|
|
y++;
|
|
m = 1;
|
|
}
|
|
|
|
_internalMonth = new DateTime(y, m, 1);
|
|
}
|
|
else
|
|
{
|
|
await SelezionaData(SelectedDate.AddDays(7 * direzione));
|
|
_internalMonth = new DateTime(SelectedDate.Year, SelectedDate.Month, 1);
|
|
}
|
|
}
|
|
|
|
// Cambio modalità
|
|
private void ToggleExpanded()
|
|
{
|
|
if (Expanded)
|
|
{
|
|
SliderAnimation = "collapse-animation";
|
|
Expanded = false;
|
|
}
|
|
else
|
|
{
|
|
Expanded = true;
|
|
SliderAnimation = "expand-animation";
|
|
}
|
|
|
|
PrepareRenderingData();
|
|
StateHasChanged();
|
|
|
|
SliderAnimation = "";
|
|
StateHasChanged();
|
|
}
|
|
|
|
// Caricamento attività al cambio mese
|
|
private async Task LoadMonthData()
|
|
{
|
|
IsLoading = true;
|
|
StateHasChanged();
|
|
|
|
var start = CurrentMonth;
|
|
var end = start.AddDays(DaysInMonth - 1);
|
|
var activities = await ManageData.GetActivity(x =>
|
|
(x.EffectiveDate == null && x.EstimatedDate >= start && x.EstimatedDate <= end) ||
|
|
(x.EffectiveDate >= start && x.EffectiveDate <= end));
|
|
MonthActivities = activities.OrderBy(x => x.EffectiveDate ?? x.EstimatedDate).ToList();
|
|
|
|
PrepareRenderingData();
|
|
IsLoading = false;
|
|
StateHasChanged();
|
|
}
|
|
|
|
// Selezione giorno in settimana
|
|
private async Task SelezionaData(DateTime day)
|
|
{
|
|
SelectedDate = day;
|
|
|
|
var cacheInternalMonth = _internalMonth;
|
|
_internalMonth = new DateTime(day.Year, day.Month, 1);
|
|
|
|
if (cacheInternalMonth != _internalMonth)
|
|
{
|
|
await LoadMonthData();
|
|
}
|
|
else
|
|
{
|
|
PrepareRenderingData();
|
|
}
|
|
|
|
ApplyFilter();
|
|
StateHasChanged();
|
|
}
|
|
|
|
// Selezione giorno dal mese (chiude la vista mese!)
|
|
private async Task SelezionaDataDalMese(DateTime day)
|
|
{
|
|
SelectedDate = day;
|
|
SliderAnimation = "collapse-animation";
|
|
Expanded = false;
|
|
_internalMonth = new DateTime(day.Year, day.Month, 1);
|
|
|
|
PrepareRenderingData();
|
|
ApplyFilter();
|
|
StateHasChanged();
|
|
|
|
SliderAnimation = "";
|
|
StateHasChanged();
|
|
}
|
|
|
|
// Utility
|
|
private static bool IsSameDay(DateTime d1, DateTime d2) => d1.Date == d2.Date;
|
|
|
|
private static DateTime GetStartOfWeek(DateTime date)
|
|
{
|
|
var day = date.DayOfWeek == DayOfWeek.Sunday ? 7 : (int)date.DayOfWeek;
|
|
return date.AddDays(-day + 1).Date;
|
|
}
|
|
|
|
private List<ActivityDTO> GetEventsForDay(DateTime day)
|
|
=> _eventsCache.TryGetValue(day.Date, out var events) ? events : [];
|
|
|
|
public void Dispose()
|
|
{
|
|
_dotNetHelper?.Dispose();
|
|
}
|
|
|
|
private async Task OnActivityDeleted(ActivityDTO activity)
|
|
{
|
|
IsLoading = true;
|
|
|
|
await ManageData.DeleteActivity(activity);
|
|
|
|
var indexActivity = MonthActivities?.FindIndex(x => x.ActivityId.Equals(activity.ActivityId));
|
|
|
|
if (indexActivity != null)
|
|
{
|
|
MonthActivities?.RemoveAt(indexActivity.Value);
|
|
PrepareRenderingData();
|
|
|
|
ApplyFilter();
|
|
}
|
|
|
|
IsLoading = false;
|
|
}
|
|
|
|
private async Task OnActivityCreated(string activityId)
|
|
{
|
|
IsLoading = true;
|
|
|
|
var activity = (await ManageData.GetActivity(x => x.ActivityId.Equals(activityId))).LastOrDefault();
|
|
|
|
if (activity == null)
|
|
{
|
|
IsLoading = false;
|
|
return;
|
|
}
|
|
|
|
var date = activity.EffectiveDate ?? activity.EstimatedDate;
|
|
|
|
if (CurrentMonth.Month != date!.Value.Month)
|
|
{
|
|
IsLoading = false;
|
|
return;
|
|
}
|
|
|
|
MonthActivities.Add(activity);
|
|
PrepareRenderingData();
|
|
IsLoading = false;
|
|
|
|
ApplyFilter();
|
|
}
|
|
|
|
private async Task OnActivityChanged(string activityId)
|
|
{
|
|
var newActivity = await ManageData.GetActivity(x => x.ActivityId.Equals(activityId));
|
|
var indexActivity = MonthActivities?.FindIndex(x => x.ActivityId.Equals(activityId));
|
|
|
|
if (indexActivity != null && !newActivity.IsNullOrEmpty())
|
|
{
|
|
MonthActivities![indexActivity.Value] = newActivity[0];
|
|
PrepareRenderingData(); // Ricalcola i dati di rendering
|
|
ApplyFilter();
|
|
}
|
|
}
|
|
|
|
private void ToggleFilter()
|
|
{
|
|
OpenFilter = !OpenFilter;
|
|
StateHasChanged();
|
|
}
|
|
|
|
private void ApplyFilter()
|
|
{
|
|
FilteredActivities = GetFilteredActivitiesForDay(SelectedDate);
|
|
|
|
// Aggiorna i dati di rendering se il filtro è cambiato
|
|
if (Expanded)
|
|
{
|
|
PrepareMonthDaysData();
|
|
}
|
|
else
|
|
{
|
|
PrepareWeekDaysData();
|
|
}
|
|
|
|
StateHasChanged();
|
|
}
|
|
|
|
// Metodo ottimizzato per il rendering dei filtri
|
|
private List<ActivityDTO> ReturnFilteredActivity(DateTime day)
|
|
{
|
|
return GetFilteredActivitiesForDay(day);
|
|
}
|
|
|
|
} |