@page "/Calendar" @using salesbook.Shared.Components.Layout @using salesbook.Shared.Components.Layout.Spinner @using salesbook.Shared.Components.SingleElements @using salesbook.Shared.Components.SingleElements.BottomSheet @using salesbook.Shared.Core.Dto.Activity @using salesbook.Shared.Core.Interface @using salesbook.Shared.Core.Messages.Activity.New @inject IManageDataService ManageData @inject IJSRuntime JS @inject NewActivityService NewActivity
@if (Expanded) { @foreach (var nomeGiorno in GiorniSettimana) {
@nomeGiorno
} @foreach (var unused in Enumerable.Range(0, StartOffset)) { } @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];
@d
@if (dayData.HasEvents) {
@foreach (var cat in dayData.EventCategories) {
}
}
} } 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);
@d
@if (events.Any()) {
@foreach (var cat in events.Select(x => x.Category).Distinct()) {
}
}
} } @foreach (var unused in Enumerable.Range(0, EndOffset)) { } } else { @if (_isInitialized && _weekDaysData.Length == 7 && _weekDaysData[0].Date != default) { @for (int i = 0; i < 7; i++) { var dayData = _weekDaysData[i]; var day = dayData.Date;
@dayData.DayName
@day.Day
@if (dayData.HasEvents) {
@foreach (var cat in dayData.EventCategories) {
}
}
} } 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);
@day.ToString("ddd", culture)
@day.Day
@if (events.Any()) {
@foreach (var cat in events.Select(x => x.Category).Distinct()) {
}
}
} } }
@if (IsLoading) { } else if (FilteredActivities is { Count: > 0 }) { } else { }
@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> _eventsCache = new(); private readonly Dictionary _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? _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 MonthActivities { get; set; } = []; private List 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 { 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 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 .OrderBy(x => x.EffectiveTime ?? x.EstimatedTime) .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(new WhereCondActivity{Start = start, End = 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 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(new WhereCondActivity {ActivityId = activityId}, true)).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(new WhereCondActivity { ActivityId = activityId }, true); 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 ReturnFilteredActivity(DateTime day) { return GetFilteredActivitiesForDay(day); } }