diff --git a/TeamsTimecardHelperClient/Pages/Settings.razor b/TeamsTimecardHelperClient/Pages/Settings.razor index f8b9c20..28f767f 100644 --- a/TeamsTimecardHelperClient/Pages/Settings.razor +++ b/TeamsTimecardHelperClient/Pages/Settings.razor @@ -17,6 +17,8 @@ + + @code { string? theteamid { get => localstorage.GetItem("theteamid"); diff --git a/TeamsTimecardHelperClient/Shared/UploadCSV.razor b/TeamsTimecardHelperClient/Shared/UploadCSV.razor new file mode 100644 index 0000000..32cad62 --- /dev/null +++ b/TeamsTimecardHelperClient/Shared/UploadCSV.razor @@ -0,0 +1,138 @@ +@using Microsoft.Graph +@using Microsoft.VisualBasic.FileIO +@using System.Collections.Generic + +@inject Blazored.LocalStorage.ISyncLocalStorageService localstorage +@inject GraphServiceClient GraphClient + +
+

Upload CSV

+
+ + + +
+
+ + + + + + + + + + + + @foreach (var result in ActivityUploadResults) + { + + + + + + + + } + +
DescriptionProjectStart TimeEnd TimeStatus
@result.Description.Description@result.Description.Project@result.Description.StartTime@result.Description.EndTime@result.status
+
+
+ +@code { + public record Activity(string Description, string Project, DateTimeOffset StartTime, DateTimeOffset EndTime); +} +@code { + public record ActivityUploadResult(Activity Description, string status); +} +@code { + string? ActivityProject + { + get => localstorage.GetItem("activity-project"); + set => localstorage.SetItem("activity-project", value); + } + + private List ActivityUploadResults = new List(); + private async Task LoadFile(InputFileChangeEventArgs e) + { + ActivityUploadResults.Clear(); + var stream = e.File.OpenReadStream() ?? throw new Exception("Error uploading file"); + await foreach (var row in ParseCSV(stream)) + { + ActivityUploadResults.Add(await UploadActivity(row)); + StateHasChanged(); + } + } + + private async Task UploadActivity(Activity activity) + { + // Create a TimeCard based on the activity + var timeCard = new TimeCard + { + Notes = new ItemBody + { + Content = activity.Description, + ContentType = BodyType.Text + }, + ClockInEvent = new TimeCardEvent + { + DateTime = activity.StartTime, + AtApprovedLocation = true, + }, + ClockOutEvent = new TimeCardEvent + { + DateTime = activity.EndTime, + AtApprovedLocation = true, + }, + }; + + if (activity.Project != ActivityProject) + return new ActivityUploadResult(activity, "Skipped, project mismatch"); + + // Upload the TimeCard + Console.WriteLine($"Uploading {activity.Description}..."); + var request = GraphClient.Teams[localstorage.GetItem("theteamid")].Schedule.TimeCards.Request(); + var response = await request.AddAsync(timeCard); + + if (response.Id == null) + return new ActivityUploadResult(activity, $"Error: Failed to upload."); + + Console.WriteLine($"Uploaded {response}."); + + return new ActivityUploadResult(activity, "Uploaded"); + } + + private async IAsyncEnumerable ParseCSV(Stream csvData) + { + using var reader = new StreamReader(csvData); + var fileContent = await reader.ReadToEndAsync(); + + using var memoryStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(fileContent)); + using var streamReader = new StreamReader(memoryStream); + + using TextFieldParser parser = new TextFieldParser(streamReader); + parser.TextFieldType = FieldType.Delimited; + parser.SetDelimiters(","); + + string[] headers = parser.ReadFields() ?? throw new Exception("Error parsing CSV"); + if((headers[0] != "activity") + || (headers[1] != "project") + || (headers[9] != "start_time") + || (headers[10] != "end_time")) + { + throw new Exception("CSV header mismatch."); + } + + while (!parser.EndOfData) + { + string[] fields = parser.ReadFields() ?? throw new Exception("Error parsing CSV"); + Activity row = new Activity( + fields[0], + fields[1], + DateTimeOffset.Parse(fields[9]), + DateTimeOffset.Parse(fields[10]) + ); + yield return row; + } + } +} \ No newline at end of file