Having some issues with Telerik Blazor Scheduler

0 Answers 80 Views
Scheduler
Rob
Top achievements
Rank 1
Rob asked on 07 Dec 2024, 04:00 AM

I am trying to use Telerik Blazor Scheduler to display a Google Calendar.  Need to be able to add, edit, delete events from Scheduler.  Need to be able to manage recurrence events.  I am new to Telerik Scheduler and running into a few issues that might be easily spotted to someone else.

The code will connect to the Google Calendar API and access the data.  There is issues with recurrence event as they do not stop when the "UNTIL" directs the event to stop.  They go on forever.  Also issues when it comes when updating and adding new events that need to be set as recurring.

- There is one recurring event in the Google Calendar that is set for Wed Dec 4 2024  1:15PM to 2:15PM.  Set to repeat on Wednesdays  and set to stop on 12/12/24.  So it is really only 2 Wednesdays 12/4 and 12/11.  Scheduler is showing this event every Wednesday forever.
- Also, based on the Console output, it looks like it is pulling the information twice upon page load.
- In Postman, the api seems to show the output correctly.

- If you try to create a new event or update an event with recurrence settings, you get an error.  You can add/update a non-recurring event just fine.

Below is the code.  (You may see some trial code and debuging items as I have been trying to figure out why)

Any help you can provide would be greatly appriciated.


RAZOR PAGE: 

@page "/gscheduler"
@rendermode RenderMode.InteractiveServer
@using Telerik.Blazor
@using Telerik.Blazor.Components
@using Telerik.Blazor.Components.Scheduler
@inject GSchedulerService GSchedulerService

<TelerikRootComponent>
    <TelerikScheduler Height="600px"
                      @bind-Date="@StartDate"
                      @bind-View="@CurrView"
                      Data="@GSchedulerService.Appointments"
                      OnUpdate="OnUpdateHandler"
                      OnCreate="OnCreateHandler"
                      OnDelete="OnDeleteHandler"
                      AllowCreate="true"
                      AllowDelete="true"
                      AllowUpdate="true"
                      IdField="@(nameof(GSchedulerDto.Id))"
                      RecurrenceRuleField="@(nameof(GSchedulerDto.RecurrenceRule))"
                      RecurrenceExceptionsField="@(nameof(GSchedulerDto.RecurrenceExceptions))"
                      RecurrenceIdField="@(nameof(GSchedulerDto.RecurrenceId))">
        <SchedulerViews>
            <SchedulerDayView StartTime="@DayStart" />
            <SchedulerWeekView StartTime="@DayStart" />
            <SchedulerMultiDayView StartTime="@DayStart" />
            <SchedulerMonthView></SchedulerMonthView>
            <SchedulerTimelineView StartTime="@DayStart" />
            <SchedulerAgendaView></SchedulerAgendaView>
        </SchedulerViews>
    </TelerikScheduler>
</TelerikRootComponent>

@code {
    private DateTime StartDate { get; set; } = DateTime.Today;
    private SchedulerView CurrView { get; set; } = SchedulerView.Week;
    private DateTime DayStart { get; set; } = new DateTime(2000, 1, 1, 7, 0, 0);

   // Initialize Scheduler Data
    protected override async Task OnInitializedAsync()
   {
        await GSchedulerService.InitializeAppointmentsAsync();

   }

    // Create Event Handler
    private async Task OnCreateHandler(SchedulerCreateEventArgs args)
    {
        if (args?.Item is GSchedulerDto newItem)
        {
            await GSchedulerService.CreateEventAsync(newItem);
        }
    }

    // Update Event Handler
    private async Task OnUpdateHandler(SchedulerUpdateEventArgs args)
    {
        if (args?.Item is GSchedulerDto updatedItem)
        {
            await GSchedulerService.UpdateEventAsync(updatedItem);
        }
    }

    // Delete Event Handler
    private async Task OnDeleteHandler(SchedulerDeleteEventArgs args)
    {
        if (args?.Item is GSchedulerDto itemToDelete && await GSchedulerService.ConfirmDeletionAsync())
        {
            await GSchedulerService.DeleteEventAsync(itemToDelete.Id);
        }
    }
}

 

CS file:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Calendar.v3;
using Google.Apis.Calendar.v3.Data;
using Google.Apis.Services;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Configuration;
using Microsoft.JSInterop;
using Telerik.SvgIcons;

public class GSchedulerService
{
    private readonly IConfiguration _configuration;
    private readonly IJSRuntime _jsRuntime;

    public List<GSchedulerDto> Appointments { get; private set; } = new();

    public GSchedulerService(IConfiguration configuration, IJSRuntime jsRuntime)
    {
        _configuration = configuration;
        _jsRuntime = jsRuntime;
    }


    public async Task InitializeAppointmentsAsync()
    {
        await LoadEventsAsync();
        // await PrintEventsAsync();
    }



    public async Task LoadEventsAsync()
    {

        var service = await GetCalendarServiceAsync();
        var events = await service.Events.List(_configuration["Google:CalendarId"]).ExecuteAsync();

        foreach (var e in events.Items)
        {
            Console.WriteLine($"Event: {e.Summary}");
            Console.WriteLine($"Start: {e.Start.DateTime ?? DateTime.Parse(e.Start.Date)}");
            Console.WriteLine($"End: {e.End.DateTime ?? DateTime.Parse(e.End.Date)}");
            Console.WriteLine($"Recurrence Rule: {e.Recurrence?.FirstOrDefault()}");
            Console.WriteLine($"Recurrence ID: {e.RecurringEventId}");
            Console.WriteLine($"Description: {e.Description}");
            Console.WriteLine(new string('-', 40));
        }

        Appointments = events.Items.Select(e => new GSchedulerDto
        {
            Id = e.Id,
            Title = e.Summary,
            Description = e.Description,
            Start = e.Start.DateTime ?? DateTime.Parse(e.Start.Date),
            End = e.End.DateTime ?? DateTime.Parse(e.End.Date),
            RecurrenceRule = FormatRecurrenceRuleForDisplay(e.Recurrence?.FirstOrDefault()),
            RecurrenceId = e.RecurringEventId // Make sure to assign RecurrenceId
        }).ToList();



    }


    public async Task PrintEventsAsync()
    {
        var service = await GetCalendarServiceAsync();
        var events = await service.Events.List(_configuration["Google:CalendarId"]).ExecuteAsync();

        foreach (var e in events.Items)
        {
            Console.WriteLine($"Event: {e.Summary}");
            Console.WriteLine($"Start: {e.Start.DateTime ?? DateTime.Parse(e.Start.Date)}");
            Console.WriteLine($"End: {e.End.DateTime ?? DateTime.Parse(e.End.Date)}");
            Console.WriteLine($"Recurrence Rule: {e.Recurrence?.FirstOrDefault()}");
            Console.WriteLine($"Recurrence ID: {e.RecurringEventId}");
            Console.WriteLine($"Description: {e.Description}");
            Console.WriteLine(new string('-', 40));
        }
    }


    public async Task CreateEventAsync(GSchedulerDto appointment)
    {
        var service = await GetCalendarServiceAsync();

        var newEvent = new Event
        {
            Summary = appointment.Title,
            Description = appointment.Description,
            Start = new EventDateTime
            {
                DateTime = appointment.Start,
                TimeZone = "UTC"
            },
            End = new EventDateTime
            {
                DateTime = appointment.End,
                TimeZone = "UTC"
            },
            Recurrence = !string.IsNullOrEmpty(appointment.RecurrenceRule)
                ? new List<string> { SanitizeRecurrenceRule(appointment.RecurrenceRule) }
                : null
        };

        Console.WriteLine($"Creating event with recurrence rule: {newEvent.Recurrence?.FirstOrDefault()}");

        await service.Events.Insert(newEvent, _configuration["Google:CalendarId"]).ExecuteAsync();
        await LoadEventsAsync();
    }

    public async Task UpdateEventAsync(GSchedulerDto appointment)
    {
        var service = await GetCalendarServiceAsync();

        var existingEvent = await service.Events.Get(_configuration["Google:CalendarId"], appointment.Id).ExecuteAsync();
        existingEvent.Summary = appointment.Title;
        existingEvent.Description = appointment.Description;
        existingEvent.Start = new EventDateTime
        {
            DateTime = appointment.Start,
            TimeZone = "UTC"
        };
        existingEvent.End = new EventDateTime
        {
            DateTime = appointment.End,
            TimeZone = "UTC"
        };
        existingEvent.Recurrence = !string.IsNullOrEmpty(appointment.RecurrenceRule)
            ? new List<string> { SanitizeRecurrenceRule(appointment.RecurrenceRule) }
            : null;

        Console.WriteLine($"Updating event with recurrence rule: {existingEvent.Recurrence?.FirstOrDefault()}");

        await service.Events.Update(existingEvent, _configuration["Google:CalendarId"], appointment.Id).ExecuteAsync();
        await LoadEventsAsync();
    }



    public async Task DeleteEventAsync(string eventId)
    {
        var service = await GetCalendarServiceAsync();
        await service.Events.Delete(_configuration["Google:CalendarId"], eventId).ExecuteAsync();
        await LoadEventsAsync();
    }

    public async Task<bool> ConfirmDeletionAsync()
    {
        return await _jsRuntime.InvokeAsync<bool>("confirm", "Are you sure you want to delete this event?");
    }

    private async Task<CalendarService> GetCalendarServiceAsync()
    {
        var initializer = new ClientSecrets
        {
            ClientId = _configuration["Google:ClientId"],
            ClientSecret = _configuration["Google:ClientSecret"]
        };

        var credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
            initializer,
            new[] { CalendarService.Scope.Calendar },
            _configuration["Google:User"],
            CancellationToken.None);

        return new CalendarService(new BaseClientService.Initializer
        {
            HttpClientInitializer = credential,
            ApplicationName = _configuration["Google:ApplicationName"]
        });
    }

    private string SanitizeRecurrenceRule(string rule)
    {
        if (string.IsNullOrEmpty(rule))
            return null;

        // Normalize case and remove extra spaces
        rule = rule.ToUpperInvariant().Trim();
        rule = Regex.Replace(rule, @"\s+", " "); // Remove extra spaces

        // Ensure the rule starts with "RRULE:"
        if (!rule.StartsWith("RRULE:"))
            rule = "RRULE:" + rule;

        // Handle UNTIL formatting
        var untilMatch = Regex.Match(rule, @"UNTIL=(\d{8}T\d{6}Z)");

        if (untilMatch.Success)
        {
            // If UNTIL exists, ensure it is correctly formatted
            var untilValue = untilMatch.Groups[1].Value;
            if (!DateTime.TryParseExact(untilValue, "yyyyMMdd'T'HHmmss'Z'", null, System.Globalization.DateTimeStyles.AdjustToUniversal, out _))
            {
                throw new FormatException($"Invalid UNTIL format: {untilValue}. Expected format is 'YYYYMMDDTHHMMSSZ'.");
            }
        }
        else if (!rule.Contains("COUNT=")) // If neither UNTIL nor COUNT is present, add a default UNTIL
        {
            var defaultUntil = DateTime.UtcNow.AddYears(1).ToString("yyyyMMdd'T'HHmmss'Z'");
            rule += $";UNTIL={defaultUntil}";
        }

        // Optionally validate additional fields like FREQ, INTERVAL, COUNT, etc.
        return rule;
    }

    private string FormatRecurrenceRuleForDisplay(string rule)
    {
        if (string.IsNullOrEmpty(rule))
            return string.Empty;

        // Remove the "RRULE:" prefix for display purposes
        rule = rule.StartsWith("RRULE:") ? rule.Substring(6) : rule;

        // Convert the UNTIL field to a human-readable format
        var untilMatch = Regex.Match(rule, @"UNTIL=(\d{8}T\d{6}Z)");
        if (untilMatch.Success)
        {
            var untilValue = untilMatch.Groups[1].Value;
            if (DateTime.TryParseExact(untilValue, "yyyyMMdd'T'HHmmss'Z'", null, System.Globalization.DateTimeStyles.AdjustToUniversal, out var untilDate))
            {
                // Replace the UNTIL field with a readable format
                rule = rule.Replace($"UNTIL={untilValue}", $"UNTIL={untilDate:yyyyMMddTHHmmssZ}");
            }
        }

        Console.WriteLine($"Formatted Recurrence Rule: {rule}");
        return rule;
    }





}
public class GSchedulerDto
{
    public string Id { get; set; }
    public string Title { get; set; }

    public string Description { get; set; }
    public DateTime Start { get; set; }
    public DateTime End { get; set; }
    public bool IsAllDay { get; set; }
    public string RecurrenceRule { get; set; }
    public string RecurrenceId { get; set; }
    public string RecurrenceExceptions { get; set; }

    //  public List<DateTime>? RecurrenceExceptions { get; set; }
    public string RecurrenceType { get; set; } // Recurrence type: Daily, Weekly, Monthly, Yearly
    public int Interval { get; set; }          // Interval for recurrence (e.g., every X days/weeks)
    public int? Count { get; set; }            // Number of occurrences (optional)
    public DateTime? Until { get; set; }       // End date for recurrence (optional)

    public string ByDay { get; set; } // Optional: Specific days (e.g., "MO,WE,FR")
    public int? ByMonthDay { get; set; } // Optional: Specific day of the month
    public int? ByMonth { get; set; } // Optional: Specific month

}

 

CONSOLE OUTPUT:

Event: test
Start: 12/4/2024 1:15:00 PM
End: 12/4/2024 2:15:00 PM
Recurrence Rule: RRULE:FREQ=WEEKLY;WKST=SU;UNTIL=20241212T045959Z;BYDAY=WE
Recurrence ID:
Description:
----------------------------------------
Formatted Recurrence Rule: FREQ=WEEKLY;WKST=SU;UNTIL=20241212T045959Z;BYDAY=WE
Event: test
Start: 12/4/2024 1:15:00 PM
End: 12/4/2024 2:15:00 PM
Recurrence Rule: RRULE:FREQ=WEEKLY;WKST=SU;UNTIL=20241212T045959Z;BYDAY=WE
Recurrence ID:
Description:
----------------------------------------
Formatted Recurrence Rule: FREQ=WEEKLY;WKST=SU;UNTIL=20241212T045959Z;BYDAY=WE

                            
Rob
Top achievements
Rank 1
commented on 15 Dec 2024, 05:06 PM

If anyone could provide any insight on how I could fix this, I would be greatful.  

No answers yet. Maybe you can help?

Tags
Scheduler
Asked by
Rob
Top achievements
Rank 1
Share this question
or