From bc54b090eb3b3d0d11a30c8087997e32de4037ea Mon Sep 17 00:00:00 2001 From: Sara Gowen <9001998+dynamictulip@users.noreply.github.com> Date: Fri, 18 Apr 2025 18:34:28 +0100 Subject: [PATCH 1/4] Make smiley icon in header white --- .../Features/HeaderBar/Components/HeaderBar.razor | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/HeaderBar/Components/HeaderBar.razor b/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/HeaderBar/Components/HeaderBar.razor index 0da6dad..5c6b5d8 100644 --- a/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/HeaderBar/Components/HeaderBar.razor +++ b/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/HeaderBar/Components/HeaderBar.razor @@ -21,7 +21,8 @@ { + OnClick="HandleShowEventFeedback" + Color="Color.Inherit" /> } From b6a1cb954621d9f0912f09b1e2714305fc9350fe Mon Sep 17 00:00:00 2001 From: Sara Gowen <9001998+dynamictulip@users.noreply.github.com> Date: Sat, 19 Apr 2025 15:55:11 +0100 Subject: [PATCH 2/4] Add bookmark toggle to home page --- .../Features/Home/Components/EventData.razor | 21 ++++++++++-- .../Features/Home/Store/HomeActions.cs | 1 + .../Features/Home/Store/HomeEffects.cs | 25 ++++++++++----- .../Features/Home/Store/HomeState.cs | 32 ++----------------- 4 files changed, 39 insertions(+), 40 deletions(-) diff --git a/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Components/EventData.razor b/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Components/EventData.razor index 32844c9..bee2738 100644 --- a/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Components/EventData.razor +++ b/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Components/EventData.razor @@ -1,6 +1,5 @@ -@using PocketDDD.BlazorClient.Features.Home.Store; -@using System.Text.Json; -@using PocketDDD.BlazorClient.Features.Session.Store; +@using PocketDDD.BlazorClient.Features.Home.Store +@using Session = PocketDDD.BlazorClient.Features.Home.Store.Session @inherits FluxorComponent @inject NavigationManager NavigationManager @@ -53,4 +52,20 @@ @code { void HandleViewSession(int sessionId) => NavigationManager.NavigateTo($"session/{sessionId}"); + + void HandleToggleSessionBookmarked(Session session) + { + Dispatcher.Dispatch(new ToggleBookmarkedAction(session.Id, !session.IsBookmarked)); + } + + private static string GetTimeSpanDisplayText(TimeSpan timeSpan) + { + if (timeSpan < TimeSpan.FromHours(1)) + { + return $"{timeSpan.Minutes} minutes"; + } + + return timeSpan == TimeSpan.FromHours(1) ? "1 hour" : $"{timeSpan.TotalHours:F1} hours"; + } + } diff --git a/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Store/HomeActions.cs b/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Store/HomeActions.cs index cac7e15..8c36290 100644 --- a/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Store/HomeActions.cs +++ b/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Store/HomeActions.cs @@ -7,3 +7,4 @@ public record SetCurrentUser(LoginResultDTO Result); public record LoadDataAction(); public record SetUserLoggedInAction(); public record SetEventMetaDataAction(EventDataResponseDTO EventData, ICollection SessionBookmarks); +public record ToggleBookmarkedAction(int SessionId, bool Bookmarked); diff --git a/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Store/HomeEffects.cs b/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Store/HomeEffects.cs index 35a5197..d57e738 100644 --- a/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Store/HomeEffects.cs +++ b/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Store/HomeEffects.cs @@ -1,22 +1,18 @@ using Fluxor; using MudBlazor; -using PocketDDD.BlazorClient.Features.Session.Store; -using PocketDDD.BlazorClient.Features.Sync.Store; using PocketDDD.BlazorClient.Services; -using PocketDDD.Shared.API.RequestDTOs; -using System.Collections.ObjectModel; namespace PocketDDD.BlazorClient.Features.Home.Store; public class HomeEffects { - private readonly IState _state; + private readonly IDialogService _dialog; private readonly LocalStorageContext _localStorage; private readonly IPocketDDDApiService _pocketDDDAPI; - private readonly IDialogService _dialog; + private readonly IState _state; - public HomeEffects(IState state, IDispatcher dispatcher, LocalStorageContext localStorage, - IPocketDDDApiService pocketDDDAPI, IDialogService dialog) + public HomeEffects(IState state, IDispatcher dispatcher, LocalStorageContext localStorage, + IPocketDDDApiService pocketDDDAPI, IDialogService dialog) { _state = state; _localStorage = localStorage; @@ -38,4 +34,17 @@ public async Task OnLoadData(LoadDataAction action, IDispatcher dispatcher) if (eventData is not null) dispatcher.Dispatch(new SetEventMetaDataAction(eventData, sessionBookmarks)); } + + [EffectMethod] + public async Task OnToggleSessionBookmarked(ToggleBookmarkedAction action, IDispatcher dispatcher) + { + var bookmarks = await _localStorage.SessionBookmarks.GetOrDefaultAsync(); + + if (action.Bookmarked && !bookmarks.Contains(action.SessionId)) + bookmarks.Add(action.SessionId); + else if (!action.Bookmarked && bookmarks.Contains(action.SessionId)) + bookmarks.Remove(action.SessionId); + + await _localStorage.SessionBookmarks.SetAsync(bookmarks); + } } diff --git a/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Store/HomeState.cs b/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Store/HomeState.cs index bf3021d..d1cb2c6 100644 --- a/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Store/HomeState.cs +++ b/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Store/HomeState.cs @@ -1,6 +1,5 @@ -using Fluxor; -using MudBlazor; -using System.Collections.Immutable; +using System.Collections.Immutable; +using Fluxor; namespace PocketDDD.BlazorClient.Features.Home.Store; @@ -30,30 +29,5 @@ public record Session public string TrackName { get; init; } = string.Empty; public string RoomName { get; init; } = string.Empty; public bool IsBookmarked { get; set; } = false; - - public string? BookmarkIconIfBookmarked => IsBookmarked - ? Icons.Material.Filled.Bookmark - : null; + public TimeSpan Length { get; init; } } - -//export interface SessionItemVM -//{ -// session: SessionDTO, -// track: TrackDTO -// isBookmarked: boolean; -//} - -//export interface MetaDataVM -//{ -// timeSlots: TimeSlotVM[]; -//} - -//export interface TimeSlotVM -//{ -// id: number; -// from: Date; -// to: Date; -// info: string; - -// sessions: SessionItemVM[]; -//} \ No newline at end of file From 2cc3ca2798d53d3901159a73d298447546f0250a Mon Sep 17 00:00:00 2001 From: Sara Gowen <9001998+dynamictulip@users.noreply.github.com> Date: Sat, 19 Apr 2025 16:31:55 +0100 Subject: [PATCH 3/4] Use cards on event list --- .../Features/Home/Components/EventData.razor | 75 ++++++++++++++----- .../Features/Home/Store/EventDataMapper.cs | 4 +- 2 files changed, 59 insertions(+), 20 deletions(-) diff --git a/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Components/EventData.razor b/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Components/EventData.razor index bee2738..89660b7 100644 --- a/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Components/EventData.razor +++ b/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Components/EventData.razor @@ -7,7 +7,7 @@ @inject IDispatcher Dispatcher - @foreach(var timeSlot in State.Value.EventMetaData) + @foreach (var timeSlot in State.Value.EventMetaData) { @@ -15,28 +15,52 @@ @timeSlot.From.ToString("tt").ToLowerInvariant() - @if(timeSlot.Info is not null) + @if (timeSlot.Info is not null) { @timeSlot.Info } - @foreach(var sessionItem in timeSlot.Sessions.Select((session, index) => (session, index))) + + @foreach (var sessionItem in timeSlot.Sessions.Select((session, index) => (session, index))) { - - - - @WhenBookmarkedShowIcon(sessionItem.session) - @sessionItem.session.Title - - @sessionItem.session.SpeakerName - @sessionItem.session.RoomName - + + + + + + @sessionItem.session.Title + + + + + + + + + @sessionItem.session.SpeakerName + @sessionItem.session.RoomName + + @ShowSessionLength(sessionItem.session) + + + + More details + + + - @if (sessionItem.index != timeSlot.Sessions.Count -1) + @if (sessionItem.index != timeSlot.Sessions.Count - 1) { - + } } } @@ -44,14 +68,27 @@ @code{ - RenderFragment? WhenBookmarkedShowIcon(Features.Home.Store.Session session) => - session.IsBookmarked - ? @ - : null; + + RenderFragment ShowSessionLength(Session session) + { + var colour = Color.Info; + + if (session.Length == TimeSpan.FromMinutes(30)) + colour = Color.Secondary; + else if (session.Length == TimeSpan.FromMinutes(15)) + colour = Color.Tertiary; + + return @@GetTimeSpanDisplayText(session.Length); + } + } @code { - void HandleViewSession(int sessionId) => NavigationManager.NavigateTo($"session/{sessionId}"); + + void HandleViewSession(int sessionId) + { + NavigationManager.NavigateTo($"session/{sessionId}"); + } void HandleToggleSessionBookmarked(Session session) { diff --git a/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Store/EventDataMapper.cs b/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Store/EventDataMapper.cs index aff1af0..c706f60 100644 --- a/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Store/EventDataMapper.cs +++ b/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Store/EventDataMapper.cs @@ -1,5 +1,6 @@ using PocketDDD.Shared.API.ResponseDTOs; using System.Collections.Immutable; +using System.Globalization; namespace PocketDDD.BlazorClient.Features.Home.Store; @@ -21,7 +22,8 @@ public static IImmutableList ToHomeStateModel(this EventDataResponseDT Title = s.Title, TrackName = eventData.Tracks.Single(tr => tr.Id == s.TrackId).Name, RoomName = eventData.Tracks.Single(tr => tr.Id == s.TrackId).RoomName, - IsBookmarked = sessionBookmarks.Contains(s.Id) + IsBookmarked = sessionBookmarks.Contains(s.Id), + Length = ts.To.Subtract(ts.From) }) .OrderBy(s => s.TrackName) .ToImmutableList() From f7add479b9c9dcffac026b5e0e16eb4f54b0201c Mon Sep 17 00:00:00 2001 From: Sara Gowen <9001998+dynamictulip@users.noreply.github.com> Date: Sat, 19 Apr 2025 17:09:09 +0100 Subject: [PATCH 4/4] Group by room and redo UI --- .../HeaderBar/Components/HeaderBar.razor | 19 ++- .../Features/Home/Components/EventData.razor | 136 +++++++++++------- .../Features/Home/Store/EventDataMapper.cs | 85 +++++++---- .../Features/Home/Store/HomeReducer.cs | 2 +- .../Features/Home/Store/HomeState.cs | 18 ++- 5 files changed, 170 insertions(+), 90 deletions(-) diff --git a/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/HeaderBar/Components/HeaderBar.razor b/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/HeaderBar/Components/HeaderBar.razor index 5c6b5d8..ad0f072 100644 --- a/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/HeaderBar/Components/HeaderBar.razor +++ b/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/HeaderBar/Components/HeaderBar.razor @@ -5,24 +5,29 @@ @inject IDispatcher Dispatcher @inject IState State - + @if (State.Value.ShowBackButton) { + Edge="Edge.Start" + Color="Color.Inherit" + OnClick="HandleNavigateBack"/> } - @State.Value.Title + @State.Value.Title @if (State.Value.ShowFeedbackButton) { - + Color="Color.Inherit" + Variant="Variant.Outlined"> + Feedback + } diff --git a/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Components/EventData.razor b/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Components/EventData.razor index 89660b7..529c47b 100644 --- a/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Components/EventData.razor +++ b/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Components/EventData.razor @@ -1,70 +1,80 @@ @using PocketDDD.BlazorClient.Features.Home.Store -@using Session = PocketDDD.BlazorClient.Features.Home.Store.Session @inherits FluxorComponent @inject NavigationManager NavigationManager @inject IState State @inject IDispatcher Dispatcher - - @foreach (var timeSlot in State.Value.EventMetaData) + + @foreach (var timeSlot in State.Value.Timeslots) { - - - @timeSlot.From.ToString("h:mm") - @timeSlot.From.ToString("tt").ToLowerInvariant() - + + + + @timeSlot.From.LocalDateTime.ToString("h:mm") + @timeSlot.From.LocalDateTime.ToString("tt").ToLowerInvariant() + + - @if (timeSlot.Info is not null) + + if (timeSlot.Info is not null) { - - @timeSlot.Info + + + @timeSlot.Info + } - @foreach (var sessionItem in timeSlot.Sessions.Select((session, index) => (session, index))) + foreach (var room in timeSlot.Rooms) { - - + + - - @sessionItem.session.Title + + @room.RoomName + - - - - - - @sessionItem.session.SpeakerName - @sessionItem.session.RoomName - - @ShowSessionLength(sessionItem.session) + + @for (var i = 0; i < room.Sessions.Count; i++) + { + var session = room.Sessions[i]; + + + @session.Title + @session.SpeakerName + + + + @ShowSessionLength(session) + + + + More details + + @if (i < room.Sessions.Count - 1) + { + + } + } - - - More details - - - - @if (sessionItem.index != timeSlot.Sessions.Count - 1) - { - - } } } - @code{ @@ -81,18 +91,30 @@ return @@GetTimeSpanDisplayText(session.Length); } -} - -@code { + private string GetBorderColourForRoom(Room room) + { + string colour; + + if (room.RoomName.Contains('1')) + colour = "mud-border-tertiary"; + else if (room.RoomName.Contains('2')) + colour = "mud-border-primary"; + else if (room.RoomName.Contains('3')) + colour = "mud-border-secondary"; + else + colour = "mud-border-info"; + + return colour; + } - void HandleViewSession(int sessionId) + private string GetTrackDividerStylesForRoom(Room room) { - NavigationManager.NavigateTo($"session/{sessionId}"); + return $"{GetBorderColourForRoom(room)}"; } - void HandleToggleSessionBookmarked(Session session) + private string GetMultiTalkDividerStylesForRoom(Room room) { - Dispatcher.Dispatch(new ToggleBookmarkedAction(session.Id, !session.IsBookmarked)); + return $"{GetBorderColourForRoom(room)} ma-4"; } private static string GetTimeSpanDisplayText(TimeSpan timeSpan) @@ -106,3 +128,17 @@ } } + +@code { + + void HandleViewSession(int sessionId) + { + NavigationManager.NavigateTo($"session/{sessionId}"); + } + + void HandleToggleSessionBookmarked(Session session) + { + Dispatcher.Dispatch(new ToggleBookmarkedAction(session.Id, !session.IsBookmarked)); + } + +} diff --git a/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Store/EventDataMapper.cs b/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Store/EventDataMapper.cs index c706f60..5765913 100644 --- a/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Store/EventDataMapper.cs +++ b/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Store/EventDataMapper.cs @@ -1,33 +1,66 @@ -using PocketDDD.Shared.API.ResponseDTOs; -using System.Collections.Immutable; -using System.Globalization; +using System.Collections.Immutable; +using PocketDDD.Shared.API.ResponseDTOs; namespace PocketDDD.BlazorClient.Features.Home.Store; public static class EventDataMapper { - public static IImmutableList ToHomeStateModel(this EventDataResponseDTO eventData, ICollection sessionBookmarks) => - eventData.TimeSlots.Select(ts => new TimeSlot + public static IImmutableList ToHomeStateModel(this EventDataResponseDTO eventData, + ICollection sessionBookmarks) + { + // The break timeslots don't have any sessions but still need to be included so starting from eventData.TimeSlots + var allTimeSlotDtosSortedByTimeAndLength = eventData.TimeSlots.OrderBy(t => t.From).ThenByDescending(t => t.To); + + // Group together the timeslots, rooms and sessions + Dictionary> timeslotRoomSessions = + allTimeSlotDtosSortedByTimeAndLength.ToDictionary( + timeSlotDto => new TimeSlot { From = timeSlotDto.From, To = timeSlotDto.To, Info = timeSlotDto.Info }, + timeSlotDto => eventData.Sessions.Where(s => s.TimeSlotId == timeSlotDto.Id) + .Select(s => + ( + new Room + { + RoomName = eventData.Tracks.Single(track => track.Id == s.TrackId).RoomName + }, + new Session + { + Id = s.Id, + From = timeSlotDto.From, + To = timeSlotDto.To, + Title = s.Title, + SpeakerName = s.Speaker, + IsBookmarked = sessionBookmarks.Contains(s.Id) + }) + ).ToList() + ); + + //Deduplicate time slots + // Sometimes one timeslot may overlap another e.g. 2 x 15 min sessions in one room and 1 x 30 min session in another at the same time + // We want to take the longer timeslot + var deduplicatedTimeSlotRoomSessions = new Dictionary>(); + foreach (var (timeSlot, roomsAndSessionsInTimeslot) in timeslotRoomSessions) { - Id = ts.Id, - From = ts.From.LocalDateTime, - To = ts.To.LocalDateTime, - Info = ts.Info, - Sessions = eventData.Sessions - .Where(s => s.TimeSlotId == ts.Id) - .Select(s => new Session - { - Id = s.Id, - SpeakerName = s.Speaker, - Title = s.Title, - TrackName = eventData.Tracks.Single(tr => tr.Id == s.TrackId).Name, - RoomName = eventData.Tracks.Single(tr => tr.Id == s.TrackId).RoomName, - IsBookmarked = sessionBookmarks.Contains(s.Id), - Length = ts.To.Subtract(ts.From) - }) - .OrderBy(s => s.TrackName) - .ToImmutableList() - }) - .OrderBy(ts => ts.From) - .ToImmutableList(); + var encompassingTimeslot = + deduplicatedTimeSlotRoomSessions.Keys.SingleOrDefault(k => + k.From <= timeSlot.From && k.To >= timeSlot.To); + if (encompassingTimeslot is not null) + deduplicatedTimeSlotRoomSessions[encompassingTimeslot].AddRange(roomsAndSessionsInTimeslot); + else + deduplicatedTimeSlotRoomSessions.Add(timeSlot, roomsAndSessionsInTimeslot); + } + + //And now group by room and fit them into the actual types we want to use + var homeStateModel = deduplicatedTimeSlotRoomSessions.Select(timeslotRoomSession => timeslotRoomSession.Key with + { + Rooms = timeslotRoomSession.Value.GroupBy(tuple => tuple.room, tuple => tuple.session) + .Select(g => g.Key with + { + Sessions = g.OrderBy(s => s.From).ToImmutableList() + }) + .OrderBy(r => r.RoomName) + .ToImmutableList() + }).ToImmutableList(); + + return homeStateModel; + } } diff --git a/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Store/HomeReducer.cs b/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Store/HomeReducer.cs index 2e9f13a..404b1cb 100644 --- a/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Store/HomeReducer.cs +++ b/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Store/HomeReducer.cs @@ -16,7 +16,7 @@ state with { Loading = false, FailedToLoad = false, - EventMetaData = action.EventData.ToHomeStateModel(action.SessionBookmarks) + Timeslots = action.EventData.ToHomeStateModel(action.SessionBookmarks) }; //[ReducerMethod] diff --git a/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Store/HomeState.cs b/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Store/HomeState.cs index d1cb2c6..1e89fe0 100644 --- a/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Store/HomeState.cs +++ b/PocketDDD.BlazorClient/PocketDDD.BlazorClient/Features/Home/Store/HomeState.cs @@ -9,25 +9,31 @@ public record HomeState public bool Loading { get; init; } = true; public bool FailedToLoad { get; init; } = false; - public IImmutableList EventMetaData { get; init; } = ImmutableList.Empty; + public IImmutableList Timeslots { get; init; } = ImmutableList.Empty; } public record TimeSlot { - public int Id { get; init; } public DateTimeOffset From { get; init; } public DateTimeOffset To { get; init; } + public TimeSpan Length => To.Subtract(From); public string? Info { get; init; } = null; - public IImmutableList Sessions { get; init; } = ImmutableList.Empty; + public IImmutableList Rooms { get; init; } = ImmutableList.Empty; } public record Session { public int Id { get; init; } + public DateTimeOffset From { get; init; } + public DateTimeOffset To { get; init; } public string Title { get; init; } = string.Empty; public string SpeakerName { get; init; } = string.Empty; - public string TrackName { get; init; } = string.Empty; - public string RoomName { get; init; } = string.Empty; public bool IsBookmarked { get; set; } = false; - public TimeSpan Length { get; init; } + public TimeSpan Length => To.Subtract(From); +} + +public record Room +{ + public string RoomName { get; init; } = string.Empty; + public IImmutableList Sessions { get; init; } = ImmutableList.Empty; }