diff --git a/src/DotNetElements.Core/Core/Extensions/DateTimeOffsetExtensions.cs b/src/DotNetElements.Core/Core/Extensions/DateTimeOffsetExtensions.cs index 576ba38..afc35b9 100644 --- a/src/DotNetElements.Core/Core/Extensions/DateTimeOffsetExtensions.cs +++ b/src/DotNetElements.Core/Core/Extensions/DateTimeOffsetExtensions.cs @@ -23,4 +23,33 @@ public static string ToFriendlyLocalDateTime(this DateTimeOffset? dateTimeOffset { return dateTimeOffset?.LocalDateTime.ToFriendlyDateTime() ?? Placeholder; } + + public static string ToFriendlyDateDiff(this DateTimeOffset dateTimeOffset, DateTime now) + { + TimeSpan timeSpan = new DateTimeOffset(now).Subtract(dateTimeOffset); + + if (timeSpan.TotalMinutes < 1) + return "just now"; + if (timeSpan.TotalMinutes < 2) + return "a minute ago"; + if (timeSpan.TotalHours < 1) + return $"{timeSpan.Minutes} minutes ago"; + if (timeSpan.TotalHours < 2) + return "an hour ago"; + if (timeSpan.TotalDays < 1) + return $"{timeSpan.Hours} hours ago"; + if (timeSpan.TotalDays < 2) + return "yesterday"; + if (timeSpan.TotalDays < 30) + return $"{timeSpan.Days} days ago"; + if (timeSpan.TotalDays < 60) + return "a month ago"; + if (timeSpan.TotalDays < 365) + return $"{Math.Round(timeSpan.TotalDays / 30)} months ago"; + if (timeSpan.TotalDays < 730) + return "last year"; + + // Handle dates more than 2 years ago + return string.Format("{0} years ago", Math.Round(timeSpan.TotalDays / 365)); + } } diff --git a/src/DotNetElements.Web.AspNetCore/CurrentUserProviderWeb.cs b/src/DotNetElements.Web.AspNetCore/CurrentUserProviderWeb.cs index 15ad45e..51117e2 100644 --- a/src/DotNetElements.Web.AspNetCore/CurrentUserProviderWeb.cs +++ b/src/DotNetElements.Web.AspNetCore/CurrentUserProviderWeb.cs @@ -1,5 +1,4 @@ -using DotNetElements.Core; -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http; namespace DotNetElements.Web.AspNetCore; @@ -15,6 +14,6 @@ public CurrentUserProviderWeb(IHttpContextAccessor contextAccessor) // todo public Guid GetCurrentUserId() { - return new Guid("FF4F759C-0916-4611-9B66-306543A51B2A"); + return new Guid("e8d118e0-18c6-4fff-9d86-e91a915d8198"); } } diff --git a/src/DotNetElements.Web.AspNetCore/FakeCurrentUserProviderWeb.cs b/src/DotNetElements.Web.AspNetCore/FakeCurrentUserProviderWeb.cs new file mode 100644 index 0000000..0a4aa9d --- /dev/null +++ b/src/DotNetElements.Web.AspNetCore/FakeCurrentUserProviderWeb.cs @@ -0,0 +1,16 @@ +namespace DotNetElements.Web.AspNetCore; + +public class FakeCurrentUserProviderWeb : ICurrentUserProvider +{ + private readonly Guid fakeUserId; + + public FakeCurrentUserProviderWeb(Guid fakeUserId) + { + this.fakeUserId = fakeUserId; + } + + public Guid GetCurrentUserId() + { + return fakeUserId; + } +} diff --git a/src/DotNetElements.Web.Blazor/CrudService.cs b/src/DotNetElements.Web.Blazor/CrudService.cs index 2606df2..4cac7d2 100644 --- a/src/DotNetElements.Web.Blazor/CrudService.cs +++ b/src/DotNetElements.Web.Blazor/CrudService.cs @@ -4,7 +4,6 @@ public interface ICrudService : IReadOnlyCru where TKey : notnull, IEquatable where TModel : IModel where TDetails : ModelDetails - where TEditModel : IMapFromModel, ICreateNew { Task> CreateEntryAsync(TEditModel editModel); Task DeleteEntryAsync(TModel model); @@ -15,7 +14,6 @@ public class CrudService : ReadOnlyCrudServi where TKey : notnull, IEquatable where TModel : IModel where TDetails : ModelDetails - where TEditModel : IMapFromModel, ICreateNew { public CrudService(ISnackbar snackbar, HttpClient httpClient, CrudOptions options) : base(snackbar, httpClient, options) { @@ -77,4 +75,20 @@ public virtual async Task DeleteEntryAsync(TModel model) return result; } + + // todo use everywhere + protected void NotifyUser(Result result, string messageOk, string messageFail) + { + // todo add logging + // todo wrap Snackbar call in bool option NotifyUser + // todo add function OnDeleteSuccess + if (result.IsOk) + { + Snackbar.Add(messageOk, Severity.Success); + } + else + { + Snackbar.Add(messageFail, Severity.Error); + } + } } diff --git a/src/DotNetElements.Web.Blazor/CrudServiceBase.cs b/src/DotNetElements.Web.Blazor/CrudServiceBase.cs index a3471ad..2439d10 100644 --- a/src/DotNetElements.Web.Blazor/CrudServiceBase.cs +++ b/src/DotNetElements.Web.Blazor/CrudServiceBase.cs @@ -68,4 +68,16 @@ public virtual async Task>> GetAllEntriesAsync() return result; } + + // todo use everywhere + protected void NotifyUserIfFailed(Result result, string message= "Failed to fetch entries from server") + { + // todo add logging + // todo wrap Snackbar call in bool option NotifyUser + // todo add function OnDeleteSuccess + if (result.IsFail) + { + Snackbar.Add(message, Severity.Error); + } + } } diff --git a/src/DotNetElements.Web.Blazor/CrudTable.cs b/src/DotNetElements.Web.Blazor/CrudTable.cs index be2fdb0..02de1de 100644 --- a/src/DotNetElements.Web.Blazor/CrudTable.cs +++ b/src/DotNetElements.Web.Blazor/CrudTable.cs @@ -74,5 +74,43 @@ protected override async Task OnEditEntry(ModelWithDetails con Snackbar.Add("Failed to save changes", Severity.Error); } } + + protected async Task> ShowCrudEditDialogAsync( + bool isEditMode, + string title, + string apiEndpoint, + TDialogEditModel editModel, + DialogParameters? additionalParameters = null, + DialogOptions? dialogOptions = null) + where TDialog : CrudEditDialog + where TDialogEditModel : notnull + { + DialogParameters parameters = new() + { + { x => x.IsEditMode, isEditMode }, + { x => x.Model, editModel }, + { x => x.EditContext, new EditContext(editModel) }, + { x => x.ApiEndpoint, apiEndpoint } + }; + + foreach ((string key, object value) in additionalParameters ?? []) + parameters.Add(key, value); + + IDialogReference dialog = await DialogService.ShowAsync(title, parameters, dialogOptions ?? CrudTable.DefaultEditDialogOptions); + DialogResult result = await dialog.Result; + + Result dialogResult = (Result)result.Data; + + if (dialogResult.IsOk) + { + Snackbar.Add("Entry saved", Severity.Success); + } + else + { + Snackbar.Add("Failed to save entry", Severity.Error); + } + + return dialogResult; + } } diff --git a/src/DotNetElements.Web.Blazor/CrudTableNotRecords.razor b/src/DotNetElements.Web.Blazor/CrudTableNotRecords.razor new file mode 100644 index 0000000..b3d0baa --- /dev/null +++ b/src/DotNetElements.Web.Blazor/CrudTableNotRecords.razor @@ -0,0 +1 @@ +No Records found \ No newline at end of file diff --git a/src/DotNetElements.Web.Blazor/DialogeServiceExtensions.cs b/src/DotNetElements.Web.Blazor/Extensions/DialogeServiceExtensions.cs similarity index 97% rename from src/DotNetElements.Web.Blazor/DialogeServiceExtensions.cs rename to src/DotNetElements.Web.Blazor/Extensions/DialogeServiceExtensions.cs index 9490c04..43a2b55 100644 --- a/src/DotNetElements.Web.Blazor/DialogeServiceExtensions.cs +++ b/src/DotNetElements.Web.Blazor/Extensions/DialogeServiceExtensions.cs @@ -1,6 +1,6 @@ using System.Linq.Expressions; -namespace DotNetElements.Web.Blazor; +namespace DotNetElements.Web.Blazor.Extensions; public static class DialogeServiceExtensions { diff --git a/src/DotNetElements.Web.Blazor/Extensions/ServiceCollectionExtensions.cs b/src/DotNetElements.Web.Blazor/Extensions/ServiceCollectionExtensions.cs index a2bba27..92d08ce 100644 --- a/src/DotNetElements.Web.Blazor/Extensions/ServiceCollectionExtensions.cs +++ b/src/DotNetElements.Web.Blazor/Extensions/ServiceCollectionExtensions.cs @@ -48,7 +48,6 @@ public static IServiceCollection AddCrudService where TModel : IModel where TDetails : ModelDetails - where TEditModel : IMapFromModel, ICreateNew { // todo consider using the options pattern // Action> configureOptions as parameter