Refactor Home and Notes components to improve note display and pagination

This commit enhances the Home.razor and Notes.razor components by restructuring the note display logic. The Home component now correctly wraps the pinned notes section in a MudPaper component, ensuring consistent styling. The Notes component has been updated to utilize ClientSidePagination for better performance and user experience, replacing the previous pagination logic. This change simplifies the code and improves the overall maintainability of the note management interface.
This commit is contained in:
2026-01-17 09:34:12 -05:00
parent 8b0451c2ec
commit 947d95893f
3 changed files with 214 additions and 172 deletions
+1 -2
View File
@@ -35,14 +35,13 @@
@if (_pinnedNotes.Any()) @if (_pinnedNotes.Any())
{ {
<MudPaper Elevation="0" Class="mb-4"> <MudPaper Elevation="0" Class="mb-4">
<MudText Typo="Typo.h4">Pinned Notes</MudText>
</MudPaper>
<MudGrid> <MudGrid>
@foreach (var note in _pinnedNotes) @foreach (var note in _pinnedNotes)
{ {
<NoteCard Note="@note" OnNoteChanged="HandleNoteChanged" /> <NoteCard Note="@note" OnNoteChanged="HandleNoteChanged" />
} }
</MudGrid> </MudGrid>
</MudPaper>
} }
@if (!_hasStudents) @if (!_hasStudents)
+89 -170
View File
@@ -39,124 +39,100 @@
} }
else else
{ {
<MudExpansionPanels MultiExpansion="true"> <ClientSidePagination T="Note" Items="@_notes" DefaultPageSize="@DefaultPageSize">
@foreach (var note in _paginatedNotes) <ChildContent Context="paginatedNotes">
{ <MudExpansionPanels MultiExpansion="true">
var noteId = note.Id; @foreach (var note in paginatedNotes)
var initialExpanded = _selectedNoteId.HasValue && noteId == _selectedNoteId.Value; {
if (!_expandedNotes.ContainsKey(noteId)) var noteId = note.Id;
{ var initialExpanded = _selectedNoteId.HasValue && noteId == _selectedNoteId.Value;
_expandedNotes[noteId] = initialExpanded; if (!_expandedNotes.ContainsKey(noteId))
}
var isExpanded = GetNoteExpanded(noteId);
<MudExpansionPanel @key="@($"note-{noteId}")"
Icon="@Icons.Material.Filled.Note"
IsExpanded="@isExpanded"
ExpandedChanged="@((bool expanded) => OnPanelExpandedChanged(noteId, expanded))">
<TitleContent>
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="flex-grow-1">
<MudText Typo="Typo.body1" Class="flex-grow-1" Style="min-width: 0;">
<span style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: block;">
@GetNoteHeaderText(note, isExpanded)
</span>
</MudText>
@if (!note.Title.StartsWith("@") && !note.IsDeleted && !isExpanded)
{
<MudButton StartIcon="@(note.IsPinned ? Icons.Material.Filled.PushPin : Icons.Material.Outlined.PushPin)"
OnClick="() => TogglePin(note)"
OnClick:StopPropagation="true"
Variant="Variant.Text"
Size="Size.Small"
Color="@(note.IsPinned ? Color.Primary : Color.Default)"
Disabled="@(IsPinDisabled(note))"
Title="@(note.IsPinned ? "Unpin note" : "Pin note")"
Class="flex-shrink-0" />
}
</MudStack>
</TitleContent>
<ChildContent>
<MudStack Spacing="2">
@if (!string.IsNullOrWhiteSpace(note.Content))
{ {
<MudPaper Elevation="0" Class="pa-3" Style="background-color: var(--mud-palette-background-grey);"> _expandedNotes[noteId] = initialExpanded;
<div class="markdown-content">
@((MarkupString)MarkdownHelper.ToHtml(note.Content))
</div>
</MudPaper>
} }
<MudStack Row="true" Spacing="2" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center"> var isExpanded = GetNoteExpanded(noteId);
<MudText Typo="Typo.body2" Color="Color.Secondary"> <MudExpansionPanel @key="@($"note-{noteId}")"
Last updated: @note.UpdatedAt.ToString("g") by @(note.LastModifiedBy ?? "Unknown") Icon="@Icons.Material.Filled.Note"
</MudText> IsExpanded="@isExpanded"
<MudStack Row="true" Spacing="2"> ExpandedChanged="@((bool expanded) => OnPanelExpandedChanged(noteId, expanded))">
@if (!note.IsDeleted) <TitleContent>
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="flex-grow-1">
<MudText Typo="Typo.body1" Class="flex-grow-1" Style="min-width: 0;">
<span style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: block;">
@GetNoteHeaderText(note, isExpanded)
</span>
</MudText>
@if (!note.Title.StartsWith("@") && !note.IsDeleted && !isExpanded)
{
<MudButton StartIcon="@(note.IsPinned ? Icons.Material.Filled.PushPin : Icons.Material.Outlined.PushPin)"
OnClick="() => TogglePin(note)"
OnClick:StopPropagation="true"
Variant="Variant.Text"
Size="Size.Small"
Color="@(note.IsPinned ? Color.Primary : Color.Default)"
Disabled="@(IsPinDisabled(note))"
Title="@(note.IsPinned ? "Unpin note" : "Pin note")"
Class="flex-shrink-0" />
}
</MudStack>
</TitleContent>
<ChildContent>
<MudStack Spacing="2">
@if (!string.IsNullOrWhiteSpace(note.Content))
{ {
<MudButton StartIcon="@Icons.Material.Filled.History" <MudPaper Elevation="0" Class="pa-3" Style="background-color: var(--mud-palette-background-grey);">
OnClick="() => OpenHistoryDialog(note.Id)" <div class="markdown-content">
Variant="Variant.Text" @((MarkupString)MarkdownHelper.ToHtml(note.Content))
Size="Size.Small"> </div>
History </MudPaper>
</MudButton>
<MudButton StartIcon="@Icons.Material.Filled.Edit"
OnClick="() => OpenEditDialog(note)"
Variant="Variant.Text"
Size="Size.Small"
Color="Color.Primary">
Edit
</MudButton>
<MudButton StartIcon="@Icons.Material.Outlined.Delete"
OnClick="() => DeleteNote(note)"
Variant="Variant.Text"
Size="Size.Small"
Color="Color.Error">
Delete
</MudButton>
}
else
{
<MudButton StartIcon="@Icons.Material.Filled.Restore"
OnClick="() => RestoreNote(note)"
Variant="Variant.Text"
Size="Size.Small"
Color="Color.Success">
Restore
</MudButton>
} }
<MudStack Row="true" Spacing="2" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudText Typo="Typo.body2" Color="Color.Secondary">
Last updated: @note.UpdatedAt.ToString("g") by @(note.LastModifiedBy ?? "Unknown")
</MudText>
<MudStack Row="true" Spacing="2">
@if (!note.IsDeleted)
{
<MudButton StartIcon="@Icons.Material.Filled.History"
OnClick="() => OpenHistoryDialog(note.Id)"
Variant="Variant.Text"
Size="Size.Small">
History
</MudButton>
<MudButton StartIcon="@Icons.Material.Filled.Edit"
OnClick="() => OpenEditDialog(note)"
Variant="Variant.Text"
Size="Size.Small"
Color="Color.Primary">
Edit
</MudButton>
<MudButton StartIcon="@Icons.Material.Outlined.Delete"
OnClick="() => DeleteNote(note)"
Variant="Variant.Text"
Size="Size.Small"
Color="Color.Error">
Delete
</MudButton>
}
else
{
<MudButton StartIcon="@Icons.Material.Filled.Restore"
OnClick="() => RestoreNote(note)"
Variant="Variant.Text"
Size="Size.Small"
Color="Color.Success">
Restore
</MudButton>
}
</MudStack>
</MudStack>
</MudStack> </MudStack>
</MudStack> </ChildContent>
</MudStack> </MudExpansionPanel>
</ChildContent> }
</MudExpansionPanel> </MudExpansionPanels>
} </ChildContent>
</MudExpansionPanels> </ClientSidePagination>
@if (_totalPages > 1 || _currentPageSize != DefaultPageSize)
{
<MudStack Row="true" Spacing="2" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center" Class="mt-4">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudText Typo="Typo.body2">Items per page:</MudText>
<MudSelect T="int" @bind-Value="CurrentPageSize" Variant="Variant.Outlined" Dense="true" Style="width: 100px;">
<MudSelectItem Value="10">10</MudSelectItem>
<MudSelectItem Value="25">25</MudSelectItem>
<MudSelectItem Value="50">50</MudSelectItem>
<MudSelectItem Value="100">100</MudSelectItem>
</MudSelect>
</MudStack>
@if (_totalPages > 1)
{
<MudPagination Count="@_totalPages"
Selected="@(_currentPage + 1)"
SelectedChanged="OnPageChanged"
ShowFirstButton="true"
ShowLastButton="true" />
}
<MudText Typo="Typo.body2" Color="Color.Secondary">
Showing @_displayStart to @_displayEnd of @_totalNotes
</MudText>
</MudStack>
}
} }
</MudPaper> </MudPaper>
@@ -167,31 +143,9 @@
private const int DefaultPageSize = 25; private const int DefaultPageSize = 25;
private List<Note> _notes = []; private List<Note> _notes = [];
private List<Note> _paginatedNotes = [];
private bool _isLoading = true; private bool _isLoading = true;
private bool _showRemoved = false; private bool _showRemoved = false;
private int _currentPage = 0;
private int _currentPageSize = DefaultPageSize;
private int _pinnedCount = 0; private int _pinnedCount = 0;
private int CurrentPageSize
{
get => _currentPageSize;
set
{
if (_currentPageSize != value)
{
_currentPageSize = value;
_currentPage = 0; // Reset to first page when page size changes
UpdatePagination();
StateHasChanged();
}
}
}
private int _totalNotes = 0;
private int _totalPages = 0;
private int _displayStart = 0;
private int _displayEnd = 0;
private CancellationTokenSource? _cancellationTokenSource; private CancellationTokenSource? _cancellationTokenSource;
private bool _isDisposed = false; private bool _isDisposed = false;
private int? _selectedNoteId; private int? _selectedNoteId;
@@ -271,8 +225,6 @@
{ {
_expandedNotes.Remove(id); _expandedNotes.Remove(id);
} }
UpdatePagination();
} }
catch (TaskCanceledException) catch (TaskCanceledException)
{ {
@@ -321,39 +273,6 @@
return MarkdownHelper.StripMarkdownPreview(content, MaxPreviewLength); return MarkdownHelper.StripMarkdownPreview(content, MaxPreviewLength);
} }
private void UpdatePagination()
{
_totalNotes = _notes.Count;
_totalPages = (int)Math.Ceiling((double)_totalNotes / _currentPageSize);
// Ensure current page is valid
if (_currentPage >= _totalPages && _totalPages > 0)
{
_currentPage = _totalPages - 1;
}
else if (_currentPage < 0)
{
_currentPage = 0;
}
// Calculate paginated notes
var startIndex = _currentPage * _currentPageSize;
_paginatedNotes = _notes.Skip(startIndex).Take(_currentPageSize).ToList();
// Calculate display range
_displayStart = _totalNotes > 0 ? startIndex + 1 : 0;
_displayEnd = Math.Min(startIndex + _currentPageSize, _totalNotes);
}
private void OnPageChanged(int newPage)
{
if (_isDisposed) return;
_currentPage = newPage - 1; // MudPagination is 1-based, we use 0-based
UpdatePagination();
StateHasChanged();
}
private async Task ToggleRemovedFilter() private async Task ToggleRemovedFilter()
{ {
if (_isDisposed) return; if (_isDisposed) return;
@@ -0,0 +1,124 @@
@namespace WebApp.Components.Shared.Components
@typeparam T
@using MudBlazor
@ChildContent(_paginatedItems)
@if (_totalPages > 1 || _currentPageSize != DefaultPageSize)
{
<MudStack Row="true" Spacing="2" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center" Class="mt-4">
@if (ShowPageSizeSelector)
{
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudText Typo="Typo.body2">Items per page:</MudText>
<MudSelect T="int" Value="@_currentPageSize" ValueChanged="OnPageSizeChanged" Variant="Variant.Outlined" Dense="true" Style="width: 100px;">
@foreach (var option in PageSizeOptions)
{
<MudSelectItem Value="@option">@option</MudSelectItem>
}
</MudSelect>
</MudStack>
}
@if (_totalPages > 1)
{
<MudPagination Count="@_totalPages"
Selected="@(_currentPage + 1)"
SelectedChanged="OnPageChanged"
ShowFirstButton="true"
ShowLastButton="true" />
}
@if (ShowItemCount)
{
<MudText Typo="Typo.body2" Color="Color.Secondary">
Showing @_displayStart to @_displayEnd of @_totalItems
</MudText>
}
</MudStack>
}
@code {
private List<T> _paginatedItems = new();
private int _currentPage = 0;
private int _currentPageSize;
private int _totalItems = 0;
private int _totalPages = 0;
private int _displayStart = 0;
private int _displayEnd = 0;
private IEnumerable<T>? _previousItems;
[Parameter] public IEnumerable<T> Items { get; set; } = Enumerable.Empty<T>();
[Parameter] public int DefaultPageSize { get; set; } = 25;
[Parameter] public int[] PageSizeOptions { get; set; } = new[] { 10, 25, 50, 100 };
[Parameter] public bool ShowPageSizeSelector { get; set; } = true;
[Parameter] public bool ShowItemCount { get; set; } = true;
[Parameter] public RenderFragment<IEnumerable<T>> ChildContent { get; set; } = null!;
public IEnumerable<T> PaginatedItems => _paginatedItems;
public int CurrentPage => _currentPage;
public int CurrentPageSize => _currentPageSize;
protected override void OnInitialized()
{
_currentPageSize = DefaultPageSize;
}
protected override void OnParametersSet()
{
// Check if items collection has changed (reference or count)
var itemsChanged = _previousItems != Items ||
(_previousItems?.Count() ?? 0) != (Items?.Count() ?? 0);
if (itemsChanged)
{
_previousItems = Items?.ToList();
_currentPage = 0; // Reset to first page when items change
}
UpdatePagination();
}
private void OnPageSizeChanged(int newPageSize)
{
if (_currentPageSize != newPageSize)
{
_currentPageSize = newPageSize;
_currentPage = 0; // Reset to first page when page size changes
UpdatePagination();
StateHasChanged();
}
}
private void UpdatePagination()
{
var itemsList = Items?.ToList() ?? new List<T>();
_totalItems = itemsList.Count;
_totalPages = _totalItems > 0 ? (int)Math.Ceiling((double)_totalItems / _currentPageSize) : 0;
// Ensure current page is valid
if (_totalPages > 0 && _currentPage >= _totalPages)
{
_currentPage = _totalPages - 1;
}
else if (_currentPage < 0)
{
_currentPage = 0;
}
// Calculate paginated items
var startIndex = _currentPage * _currentPageSize;
_paginatedItems = itemsList.Skip(startIndex).Take(_currentPageSize).ToList();
// Calculate display range
_displayStart = _totalItems > 0 ? startIndex + 1 : 0;
_displayEnd = Math.Min(startIndex + _currentPageSize, _totalItems);
}
private void OnPageChanged(int newPage)
{
_currentPage = newPage - 1; // MudPagination is 1-based, we use 0-based
UpdatePagination();
StateHasChanged();
}
}