Skip to content

Commit

Permalink
[Admin] Component: Create event details - UI component #214 (#293)
Browse files Browse the repository at this point in the history
* Feat: 어드민 UI 컴포넌트 Page 추가

* Test: 어드민 UI 컴포넌트 테스트 추가

* Feat: 버튼 추가

* Fix: 피드백 적용
- AzureOpenAIProxy.sln 파일 이전 내용으로 되돌리기
- AzureOpenAIProxy.PlaygroundApp.Tests/Pages/NewEventDetailsTests.cs 테스트 코드 삭제

* Update PlaygroundApp Model 최신화

* Refactor: 컴포넌트 수정
- Id 파라미터 추가
- 버튼 Id 추가
- TextFieldType 추가
- Id 값 kebab-casing 수정

* Feat: NodaTime을 사용하여 Time Zone Option 추가

* Feat: 기본값 및 이벤트 설정
- 기본 날짜/시간 설정 추가
- 이벤트 추가/취소 버튼 이벤트 바인딩 추가
- Max Token Cap, Daily Request Cap 값 바인딩

* Refactor: 불필요한 코드 정리

* Refactor: Time Zone Select 높이 수정

* Refactor: CSS 적용 방식 수정 - 외부 스타일 시트 적용

* Revert "Refactor: CSS 적용 방식 수정 - 외부 스타일 시트 적용"

This reverts commit 197d7ee.

* Refactor: 이벤트 종료 날짜 기본값 수정 (오늘 기준 다음날로 적용)

* Refactor: NewEventDetailsComponent.razor

* Refactor: Remove @temp~ variables

* Fix: FluentDatePicker, FluentTimePicker ValueChanged error fix

* Refactor: NewEventDetailsComponent.razor

* Test: Add NewEventDetailsComponent.razor test

* Test: Add input test

* Fix: delete inject

* Feat: Get local browser timezone

* Refactor: Add JS error handling

* Refactor: NewEventDetailsComponent.razor

* Test: Add init timezone test

* Fix: Browser Timezone > System Timezone

* Fix: Test error fix (Now > UtcNow)

* Fix: add attribute Culture to FluentDatePicker

* Fix: Add culture info in OnAfterRenderAsync

* Feat: Convert from Windows timezone to IANA timezone using TimeZoneConverter

* Test: Convert from Windows timezone to IANA timezone using TimeZoneConverter

* Fix: Check OS to get timezone

* Test: Refactoring NewEventDetailsPageTests

* Test: Refactoring NewEventDetailsPageTests

* Refactor: Refactoring NewEventDetailsComponent and test
- Delete TimeZoneConverter
- Split input event datetime test

* Test: Add NewEventDetailsPageTests.cs to AppHost test
  • Loading branch information
KYJKY authored Oct 12, 2024
1 parent 83f0f3b commit 97a3b53
Show file tree
Hide file tree
Showing 8 changed files with 536 additions and 97 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components" Version="$(FluentUIVersion)" />
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components.Emoji" Version="$(FluentUIVersion)" />
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components.Icons" Version="$(FluentUIVersion)" />
<PackageReference Include="NodaTime" Version="3.1.12" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
<PageTitle>New event</PageTitle>

<h1>New event</h1>

<NewEventDetailsComponent Id="admin-new-event" @rendermode="InteractiveServer" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
@using AzureOpenAIProxy.PlaygroundApp.Clients
@using AzureOpenAIProxy.PlaygroundApp.Models;

@using System.Globalization

@using NodaTime
@using NodaTime.Extensions
@using NodaTime.TimeZones

<FluentLayout Id="@Id">
@if (adminEventDetails == null)
{
<p><em>Loading...</em></p>
}
else
{
<FluentHeader>New Event</FluentHeader>
<FluentBodyContent>
<section>
<h2>Event Infomation</h2>

<FluentStack Class="create-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-title" Class="create-input-label">Title</FluentLabel>
<FluentTextField Id="event-title" Name="title" TextFieldType="TextFieldType.Text" Required />
</FluentStack>

<FluentStack Class="create-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-summary" Class="create-input-label">Summary</FluentLabel>
<FluentTextField id="event-summary" TextFieldType="TextFieldType.Text" Required />
</FluentStack>

<FluentStack Class="create-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-description" Class="create-input-label">Description</FluentLabel>
<FluentTextArea Id="event-description" Style="width:300px" />
</FluentStack>

<FluentStack Class="create-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-start-date" Class="create-input-label">Event Start Date</FluentLabel>
<FluentDatePicker Id="event-start-date" Value="@adminEventDetails.DateStart.DateTime" ValueChanged="@(e => adminEventDetails.DateStart = e.Value)" Culture="System.Globalization.CultureInfo.CurrentCulture" />

Check warning on line 39 in src/AzureOpenAIProxy.PlaygroundApp/Components/UI/Admin/NewEventDetailsComponent.razor

View workflow job for this annotation

GitHub Actions / build-test-deploy

Nullable value type may be null.
<FluentTimePicker Id="event-start-time" Value="@adminEventDetails.DateStart.DateTime" ValueChanged="@(e => adminEventDetails.DateStart = e.Value)" />

Check warning on line 40 in src/AzureOpenAIProxy.PlaygroundApp/Components/UI/Admin/NewEventDetailsComponent.razor

View workflow job for this annotation

GitHub Actions / build-test-deploy

Nullable value type may be null.
</FluentStack>

<FluentStack Class="create-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-end-date" Class="create-input-label">Event End Date</FluentLabel>
<FluentDatePicker Id="event-end-date" Value="@adminEventDetails.DateEnd.DateTime" ValueChanged="@(e => adminEventDetails.DateEnd = e.Value)" Culture="System.Globalization.CultureInfo.CurrentCulture" />

Check warning on line 45 in src/AzureOpenAIProxy.PlaygroundApp/Components/UI/Admin/NewEventDetailsComponent.razor

View workflow job for this annotation

GitHub Actions / build-test-deploy

Nullable value type may be null.
<FluentTimePicker Id="event-end-time" Value="@adminEventDetails.DateEnd.DateTime" ValueChanged="@(e => adminEventDetails.DateEnd = e.Value)" />

Check warning on line 46 in src/AzureOpenAIProxy.PlaygroundApp/Components/UI/Admin/NewEventDetailsComponent.razor

View workflow job for this annotation

GitHub Actions / build-test-deploy

Nullable value type may be null.
</FluentStack>

<FluentStack Class="create-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-timezone" Class="create-input-label">Time Zone</FluentLabel>
<FluentSelect Id="event-timezone" @bind-Value="@adminEventDetails.TimeZone" Height="500px" TOption="string" Required>
@foreach (var timeZone in timeZoneList)

Check warning on line 52 in src/AzureOpenAIProxy.PlaygroundApp/Components/UI/Admin/NewEventDetailsComponent.razor

View workflow job for this annotation

GitHub Actions / build-test-deploy

Dereference of a possibly null reference.
{
<FluentOption Value="@timeZone.Id">@timeZone.Id</FluentOption>
}
</FluentSelect>
</FluentStack>
</section>

<section>
<h2>Event Organizer</h2>

<FluentStack Class="create-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-organizer-name" Class="create-input-label">Organizer Name</FluentLabel>
<FluentTextField Id="event-organizer-name" TextFieldType="TextFieldType.Text" Required />
</FluentStack>


<FluentStack Class="create-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-organizer-email" Class="create-input-label">Organizer Email</FluentLabel>
<FluentTextField Id="event-organizer-email" TextFieldType="TextFieldType.Email" Required />
</FluentStack>
</section>

<section>
<h2>Event Coorganizers</h2>

<FluentStack Class="create-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-coorgnizer-name" Class="create-input-label">Coorgnizer Name</FluentLabel>
<FluentTextField Id="event-coorgnizer-name" TextFieldType="TextFieldType.Text" Required />
</FluentStack>

<FluentStack Class="create-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-coorgnizer-email" Class="create-input-label">Coorgnizer Email</FluentLabel>
<FluentTextField Id="event-coorgnizer-email" TextFieldType="TextFieldType.Email" Required />
</FluentStack>
</section>

<section>
<h2>Event Configuration</h2>

<FluentStack Class="create-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-max-token-cap" Class="create-input-label">Max Token Cap</FluentLabel>
<FluentNumberField Id="event-max-token-cap" @bind-Value="adminEventDetails.MaxTokenCap" Required />
</FluentStack>

<FluentStack Class="create-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-daily-request-cap" Class="create-input-label">Daily Request Cap</FluentLabel>
<FluentNumberField Id="event-daily-request-cap" @bind-Value="adminEventDetails.DailyRequestCap" Required />
</FluentStack>
</section>

<section class="button-section">
<FluentButton Id="admin-event-detail-add" Appearance="Appearance.Accent" Class="button" OnClick="AddEvent">Add Event</FluentButton>
<FluentButton Id="admin-event-detail-cancel" Appearance="Appearance.Outline" Class="button" OnClick="CancelEvent">Cancel</FluentButton>
</section>
</FluentBodyContent>
}
</FluentLayout>


@code {
private List<DateTimeZone>? timeZoneList;
private AdminEventDetails? adminEventDetails;
private DateTimeOffset currentTime = DateTimeOffset.UtcNow;

[Parameter]
public string? Id { get; set; }

protected override async Task OnInitializedAsync()

Check warning on line 120 in src/AzureOpenAIProxy.PlaygroundApp/Components/UI/Admin/NewEventDetailsComponent.razor

View workflow job for this annotation

GitHub Actions / build-test-deploy

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
{
adminEventDetails = adminEventDetails == null ? new() : adminEventDetails;

timeZoneList = DateTimeZoneProviders.Tzdb.GetAllZones().ToList();

CultureInfo customCulture = (CultureInfo)CultureInfo.CurrentCulture.Clone();
customCulture.DateTimeFormat.ShortDatePattern = "yyyy-MM-dd";
customCulture.DateTimeFormat.ShortTimePattern = "HH:mm";

CultureInfo.DefaultThreadCurrentCulture = customCulture;
CultureInfo.DefaultThreadCurrentUICulture = customCulture;
}

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var timezoneId = GetIanaTimezoneId();
currentTime = GetCurrentDateTimeOffset(timezoneId);

adminEventDetails.DateStart = currentTime.AddHours(1).AddMinutes(-currentTime.Minute);
adminEventDetails.DateEnd = currentTime.AddDays(1).AddHours(1).AddMinutes(-currentTime.Minute);
adminEventDetails.TimeZone = timezoneId;

await InvokeAsync(StateHasChanged);
}
}

private async Task AddEvent()
{
await Task.CompletedTask;
}

private async Task CancelEvent()
{
await Task.CompletedTask;
}

private string GetIanaTimezoneId()
{
string timezoneId = TimeZoneInfo.Local.Id;

if (OperatingSystem.IsWindows())
{
if (TimeZoneInfo.TryConvertWindowsIdToIanaId(timezoneId, out var ianaTimezoneId))
{
timezoneId = ianaTimezoneId;
}
}

return timezoneId;
}

private DateTimeOffset GetCurrentDateTimeOffset(string timezoneId)
{
var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timezoneId);

return TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, timeZoneInfo);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
section {
margin-bottom: 100px
}

::deep .create-input-label {
width: 200px;
--type-ramp-base-font-size: 22px;
}

::deep .create-fluent-stack {
height: 100px;
}

.button-section {
display: flex;
justify-content: center;
gap: 50px;
}

.button {
width: 150px;
height: 50px;
font-size: 16px;
margin: 0 10px;
}
118 changes: 59 additions & 59 deletions src/AzureOpenAIProxy.PlaygroundApp/Models/AdminEventDetails.cs
Original file line number Diff line number Diff line change
@@ -1,60 +1,60 @@
using System.Text.Json.Serialization;

namespace AzureOpenAIProxy.PlaygroundApp.Models;

/// <summary>
/// This represent the event detail data for response by admin event endpoint.
/// </summary>
public class AdminEventDetails : EventDetails
{
/// <summary>
/// Gets or sets the event description.
/// </summary>
public string? Description { get; set; }

/// <summary>
/// Gets or sets the event start date.
/// </summary>
[JsonRequired]
public DateTimeOffset DateStart { get; set; }

/// <summary>
/// Gets or sets the event end date.
/// </summary>
[JsonRequired]
public DateTimeOffset DateEnd { get; set; }

/// <summary>
/// Gets or sets the event start to end date timezone.
/// </summary>
[JsonRequired]
public string TimeZone { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the event active status.
/// </summary>
[JsonRequired]
public bool IsActive { get; set; }

/// <summary>
/// Gets or sets the event organizer name.
/// </summary>
[JsonRequired]
public string OrganizerName { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the event organizer email.
/// </summary>
[JsonRequired]
public string OrganizerEmail { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the event coorganizer name.
/// </summary>
public string? CoorganizerName { get; set; }

/// <summary>
/// Gets or sets the event coorganizer email.
/// </summary>
public string? CoorganizerEmail { get; set; }
using System.Text.Json.Serialization;

namespace AzureOpenAIProxy.PlaygroundApp.Models;

/// <summary>
/// This represent the event detail data for response by admin event endpoint.
/// </summary>
public class AdminEventDetails : EventDetails
{
/// <summary>
/// Gets or sets the event description.
/// </summary>
public string? Description { get; set; }

/// <summary>
/// Gets or sets the event start date.
/// </summary>
[JsonRequired]
public DateTimeOffset DateStart { get; set; }

/// <summary>
/// Gets or sets the event end date.
/// </summary>
[JsonRequired]
public DateTimeOffset DateEnd { get; set; }

/// <summary>
/// Gets or sets the event start to end date timezone.
/// </summary>
[JsonRequired]
public string TimeZone { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the event active status.
/// </summary>
[JsonRequired]
public bool IsActive { get; set; }

/// <summary>
/// Gets or sets the event organizer name.
/// </summary>
[JsonRequired]
public string OrganizerName { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the event organizer email.
/// </summary>
[JsonRequired]
public string OrganizerEmail { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the event coorganizer name.
/// </summary>
public string? CoorganizerName { get; set; }

/// <summary>
/// Gets or sets the event coorganizer email.
/// </summary>
public string? CoorganizerEmail { get; set; }
}
Loading

0 comments on commit 97a3b53

Please sign in to comment.