Skip to content

Commit 7a2c3b8

Browse files
authored
[Admin] Component: List event details - UI component #219 (#266)
1 parent c0aa264 commit 7a2c3b8

File tree

9 files changed

+304
-1
lines changed

9 files changed

+304
-1
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
@page "/admin/events"
2+
3+
<PageTitle>AdminEvents</PageTitle>
4+
5+
<h1>AdminEvents</h1>
6+
7+
<p>This component demonstrates showing admin events.</p>
8+
9+
<AdminEventsComponent @rendermode="InteractiveServer"/>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<div class="admin-event-active-state">
2+
<div class="@GetActiveClass(IsActive)"></div>
3+
</div>
4+
5+
@code {
6+
[Parameter]
7+
public required bool IsActive { get; set; }
8+
9+
private string GetActiveClass(bool? isActive)
10+
{
11+
if (!isActive.HasValue)
12+
{
13+
return "deactivated";
14+
}
15+
16+
return isActive.Value ? "activated" : "deactivated";
17+
}
18+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
.admin-event-active-state {
2+
display: flex;
3+
flex-direction: column;
4+
justify-content: center;
5+
align-items: center;
6+
height: 100%;
7+
}
8+
9+
.activated {
10+
width: 10px;
11+
height: 10px;
12+
background-color: green;
13+
border-radius: 50%;
14+
display: inline-block;
15+
}
16+
17+
.deactivated {
18+
width: 10px;
19+
height: 10px;
20+
background-color: red;
21+
border-radius: 50%;
22+
display: inline-block;
23+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
@using AzureOpenAIProxy.PlaygroundApp.Models
2+
3+
<div id="admin-events-component">
4+
@if (eventDetails == null)
5+
{
6+
<p><em>Loading...</em></p>
7+
}
8+
else
9+
{
10+
<div id="admin-events-table">
11+
<FluentDataGrid Items="@eventDetails" Pagination="@pagination" >
12+
<PropertyColumn Class="fluent-datagrid-cell" Property="@(p => p.Title)" Align="@Align.Center" Sortable="true" />
13+
<PropertyColumn Class="fluent-datagrid-cell" Property="@(p => p.DateStart)" Format="yyyy-MM-dd" Align="@Align.Center" Sortable="true" />
14+
<PropertyColumn Class="fluent-datagrid-cell" Property="@(p => p.DateEnd)" Format="yyyy-MM-dd" Align="@Align.Center" Sortable="true" />
15+
<PropertyColumn Class="fluent-datagrid-cell" Property="@(p => p.TimeZone)" Align="@Align.Center" Sortable="true" />
16+
<PropertyColumn Class="fluent-datagrid-cell" Property="@(p => p.OrganizerName)" Align="@Align.Center" Sortable="true" />
17+
<PropertyColumn Class="fluent-datagrid-cell" Property="@(p => p.CoorganizerName)" Align="@Align.Center" Sortable="true" />
18+
<PropertyColumn Class="fluent-datagrid-cell" Property="@(p => p.MaxTokenCap)" Align="@Align.Center" Sortable="true" />
19+
<PropertyColumn Class="fluent-datagrid-cell" Property="@(p => p.DailyRequestCap)" Align="@Align.Center" Sortable="true" />
20+
<TemplateColumn Class="fluent-datagrid-cell" Title="Active" Align="@Align.Center">
21+
<AdminEventIsActiveComponent IsActive="@(((AdminEventDetails)@context).IsActive)" />
22+
</TemplateColumn>
23+
<TemplateColumn Class="fluent-datagrid-cell" Title="Actions" Align="@Align.Center">
24+
<FluentButton aria-label="Edit item" IconEnd="@(new Icons.Regular.Size16.Edit())" />
25+
<FluentButton aria-label="Delete item" IconEnd="@(new Icons.Regular.Size16.Delete())" />
26+
</TemplateColumn>
27+
</FluentDataGrid>
28+
</div>
29+
30+
<div class="page-button-box">
31+
@if (pagination.TotalItemCount.HasValue)
32+
{
33+
for (var pageIndex = 0; pageIndex <= pagination.LastPageIndex; pageIndex++)
34+
{
35+
var capturedIndex = pageIndex;
36+
<FluentButton class="page-button" @onclick="@(() => GoToPageAsync(capturedIndex))" Appearance="@PageButtonAppearance(capturedIndex)"
37+
aria-current="@AriaCurrentValue(capturedIndex)">
38+
@(capturedIndex + 1)
39+
</FluentButton>
40+
}
41+
}
42+
</div>
43+
}
44+
</div>
45+
46+
@code {
47+
private IQueryable<AdminEventDetails>? eventDetails;
48+
private PaginationState pagination = new PaginationState { ItemsPerPage = 10 };
49+
50+
protected override async Task OnInitializedAsync()
51+
{
52+
// Simulate asynchronous loading to demonstrate streaming rendering
53+
await Task.Delay(100);
54+
55+
var startDate = DateOnly.FromDateTime(DateTime.Now);
56+
57+
// make dummy data
58+
eventDetails = Enumerable.Range(1, 150).Select(index => new AdminEventDetails
59+
{
60+
EventId = Guid.NewGuid(),
61+
Title = $"event title #{index}",
62+
Summary = "dummy summary",
63+
Description = "dummy description",
64+
DateStart = DateTimeOffset.Now,
65+
DateEnd = DateTimeOffset.Now.AddDays(7 + index),
66+
TimeZone = "KST",
67+
IsActive = index % 3 == 0,
68+
OrganizerName = $"Charlie_{index}",
69+
OrganizerEmail = $"user_{index}@gmail.com",
70+
CoorganizerName = $"Bravo_{index}",
71+
CoorganizerEmail = $"support_{index}@gmail.com",
72+
MaxTokenCap = (100 + index) * 100,
73+
DailyRequestCap = index * 10
74+
}).AsQueryable();
75+
76+
pagination.TotalItemCountChanged += (sender, eventArgs) => StateHasChanged();
77+
}
78+
79+
private async Task GoToPageAsync(int pageIndex)
80+
{
81+
await pagination.SetCurrentPageIndexAsync(pageIndex);
82+
}
83+
84+
private Appearance PageButtonAppearance(int pageIndex)
85+
=> pagination.CurrentPageIndex == pageIndex ? Appearance.Accent : Appearance.Neutral;
86+
87+
private string? AriaCurrentValue(int pageIndex)
88+
=> pagination.CurrentPageIndex == pageIndex ? "page" : null;
89+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
.fluent-datagrid-cell {
2+
display: flex;
3+
align-items: center;
4+
justify-content: center;
5+
height: 100%;
6+
}
7+
8+
.page-button-box {
9+
display: flex;
10+
justify-content: center;
11+
align-items: center;
12+
margin-top: 20px;
13+
}
14+
15+
.page-button {
16+
margin-left: 10px;
17+
}

src/AzureOpenAIProxy.PlaygroundApp/Components/_Imports.razor

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@
1414

1515
@using AzureOpenAIProxy.PlaygroundApp
1616
@using AzureOpenAIProxy.PlaygroundApp.Components
17-
@using AzureOpenAIProxy.PlaygroundApp.Components.UI
17+
@using AzureOpenAIProxy.PlaygroundApp.Components.UI
18+
@using AzureOpenAIProxy.PlaygroundApp.Components.UI.Admin
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace AzureOpenAIProxy.PlaygroundApp.Models;
4+
5+
/// <summary>
6+
/// This represent the event detail data for response by admin event endpoint.
7+
/// </summary>
8+
public class AdminEventDetails : EventDetails
9+
{
10+
/// <summary>
11+
/// Gets or sets the event description.
12+
/// </summary>
13+
public string? Description { get; set; }
14+
15+
/// <summary>
16+
/// Gets or sets the event start date.
17+
/// </summary>
18+
[JsonRequired]
19+
public DateTimeOffset DateStart { get; set; }
20+
21+
/// <summary>
22+
/// Gets or sets the event end date.
23+
/// </summary>
24+
[JsonRequired]
25+
public DateTimeOffset DateEnd { get; set; }
26+
27+
/// <summary>
28+
/// Gets or sets the event start to end date timezone.
29+
/// </summary>
30+
[JsonRequired]
31+
public string TimeZone { get; set; } = string.Empty;
32+
33+
/// <summary>
34+
/// Gets or sets the event active status.
35+
/// </summary>
36+
[JsonRequired]
37+
public bool IsActive { get; set; }
38+
39+
/// <summary>
40+
/// Gets or sets the event organizer name.
41+
/// </summary>
42+
[JsonRequired]
43+
public string OrganizerName { get; set; } = string.Empty;
44+
45+
/// <summary>
46+
/// Gets or sets the event organizer email.
47+
/// </summary>
48+
[JsonRequired]
49+
public string OrganizerEmail { get; set; } = string.Empty;
50+
51+
/// <summary>
52+
/// Gets or sets the event coorganizer name.
53+
/// </summary>
54+
public string? CoorganizerName { get; set; }
55+
56+
/// <summary>
57+
/// Gets or sets the event coorganizer email.
58+
/// </summary>
59+
public string? CoorganizerEmail { get; set; }
60+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace AzureOpenAIProxy.PlaygroundApp.Models;
4+
5+
/// <summary>
6+
/// This represents the event's detailed data for response by EventEndpoint.
7+
/// </summary>
8+
public class EventDetails
9+
{
10+
/// <summary>
11+
/// Gets or sets the event id.
12+
/// </summary>
13+
[JsonRequired]
14+
public Guid EventId { get; set; }
15+
16+
/// <summary>
17+
/// Gets or sets the event title name.
18+
/// </summary>
19+
[JsonRequired]
20+
public string Title { get; set; } = string.Empty;
21+
22+
/// <summary>
23+
/// Gets or sets the event summary.
24+
/// </summary>
25+
[JsonRequired]
26+
public string Summary { get; set; } = string.Empty;
27+
28+
/// <summary>
29+
/// Gets or sets the Azure OpenAI Service request max token capacity.
30+
/// </summary>
31+
[JsonRequired]
32+
public int MaxTokenCap { get; set; }
33+
34+
/// <summary>
35+
/// Gets or sets the Azure OpenAI Service daily request capacity.
36+
/// </summary>
37+
[JsonRequired]
38+
public int DailyRequestCap { get; set; }
39+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using FluentAssertions;
2+
3+
using Microsoft.Playwright;
4+
using Microsoft.Playwright.NUnit;
5+
6+
namespace AzureOpenAIProxy.PlaygroundApp.Tests.Pages;
7+
8+
[Parallelizable(ParallelScope.Self)]
9+
[TestFixture]
10+
[Property("Category", "Integration")]
11+
public class AdminEventsPageTests : PageTest
12+
{
13+
public override BrowserNewContextOptions ContextOptions() => new()
14+
{
15+
IgnoreHTTPSErrors = true,
16+
};
17+
18+
[SetUp]
19+
public async Task Setup()
20+
{
21+
await Page.GotoAsync("https://localhost:5001/admin/events");
22+
await Page.WaitForLoadStateAsync(LoadState.NetworkIdle);
23+
}
24+
25+
[Test]
26+
public async Task Given_Events_Page_When_Navigated_Then_It_Should_Have_ListEventDetailsComponent()
27+
{
28+
// Act
29+
var adminEventsComponent = await Page.QuerySelectorAsync("#admin-events-component");
30+
31+
// Assert
32+
adminEventsComponent.Should().NotBeNull();
33+
}
34+
35+
[Test]
36+
public async Task Given_Events_Page_When_Navigated_Then_It_Should_Have_EventDetailsTable()
37+
{
38+
// wait for construct table
39+
await Task.Delay(2000);
40+
41+
// Act
42+
var adminEventsTable = await Page.QuerySelectorAsync("#admin-events-table");
43+
44+
// Assert
45+
adminEventsTable.Should().NotBeNull();
46+
}
47+
}

0 commit comments

Comments
 (0)