revert: rollback Fluent UI and Blazor homepage to last successful state (3be3794)
This commit is contained in:
@@ -5,47 +5,101 @@
|
||||
@using TaxBaik.Web.Services
|
||||
@inject IAnnouncementBrowserClient AnnouncementClient
|
||||
@inject NavigationManager Navigation
|
||||
@inject IJSRuntime JS
|
||||
@inject ISnackbar Snackbar
|
||||
|
||||
<PageTitle>@(Id.HasValue ? "공지 수정" : "공지 등록")</PageTitle>
|
||||
|
||||
<section class="admin-page-hero">
|
||||
<div>
|
||||
<div class="admin-eyebrow">Homepage</div>
|
||||
<h1 class="admin-page-title">@(Id.HasValue ? "공지 수정" : "공지 등록")</h1>
|
||||
<MudText Typo="Typo.caption" Class="admin-eyebrow">Homepage</MudText>
|
||||
<MudText Typo="Typo.h4" Class="admin-page-title">@(Id.HasValue ? "공지 수정" : "공지 등록")</MudText>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="admin-surface" style="max-width:720px;">
|
||||
<form class="admin-dialog-card" @onsubmit="SaveAsync" @onsubmit:preventDefault="true">
|
||||
<label>제목 * <input class="admin-input" @bind="model.Title" /></label>
|
||||
<label>상세 내용 (선택) <textarea class="admin-input" rows="3" @bind="model.Content"></textarea></label>
|
||||
<label>유형
|
||||
<select class="admin-input" @bind="model.DisplayType">
|
||||
<option value="info">일반 (파란색)</option>
|
||||
<option value="banner">배너 (주황색)</option>
|
||||
<option value="urgent">긴급 (빨간색)</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>노출 순서 <input class="admin-input" type="number" @bind="model.SortOrder" /></label>
|
||||
<label>게시 시작일 <input class="admin-input" type="text" placeholder="yyyy-MM-dd" @bind="StartsAtText" /></label>
|
||||
<label>게시 종료일 <input class="admin-input" type="text" placeholder="yyyy-MM-dd" @bind="EndsAtText" /></label>
|
||||
<label><input type="checkbox" @bind="model.IsActive" /> @(model.IsActive ? "활성화" : "비활성화")</label>
|
||||
<div class="admin-dialog-actions">
|
||||
<button type="submit" class="site-button primary" disabled="@isSaving">저장</button>
|
||||
<button type="button" class="site-button secondary" @onclick='() => Navigation.NavigateTo("/taxbaik/admin/announcements")'>취소</button>
|
||||
<MudPaper Class="admin-surface" Elevation="0">
|
||||
<MudForm @ref="form">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudTextField @bind-Value="model.Title"
|
||||
Label="제목"
|
||||
Variant="Variant.Outlined"
|
||||
Required="true"
|
||||
RequiredError="제목을 입력하세요."
|
||||
HelperText="홈페이지 상단 공지 바에 표시되는 텍스트입니다." />
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudTextField @bind-Value="model.Content"
|
||||
Label="상세 내용 (선택)"
|
||||
Variant="Variant.Outlined"
|
||||
Lines="3"
|
||||
HelperText="부가 설명이 있을 경우 입력합니다. 없으면 제목만 표시됩니다." />
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" sm="6">
|
||||
<MudSelect @bind-Value="model.DisplayType"
|
||||
Label="유형"
|
||||
Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="@("info")">일반 (파란색)</MudSelectItem>
|
||||
<MudSelectItem Value="@("banner")">배너 (주황색) — 중요 이벤트</MudSelectItem>
|
||||
<MudSelectItem Value="@("urgent")">긴급 (빨간색) — 마감 임박</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" sm="6">
|
||||
<MudNumericField @bind-Value="model.SortOrder"
|
||||
Label="노출 순서"
|
||||
Variant="Variant.Outlined"
|
||||
HelperText="숫자가 클수록 먼저 표시됩니다." />
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" sm="6">
|
||||
<MudDatePicker @bind-Date="startsAtDate"
|
||||
Label="게시 시작일 (비우면 즉시)"
|
||||
Variant="Variant.Outlined"
|
||||
DateFormat="yyyy-MM-dd"
|
||||
Clearable="true" />
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" sm="6">
|
||||
<MudDatePicker @bind-Date="endsAtDate"
|
||||
Label="게시 종료일 (비우면 무기한)"
|
||||
Variant="Variant.Outlined"
|
||||
DateFormat="yyyy-MM-dd"
|
||||
Clearable="true" />
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudSwitch @bind-Checked="model.IsActive"
|
||||
Label="@(model.IsActive ? "활성화 (홈페이지에 노출)" : "비활성화 (홈페이지 미노출)")"
|
||||
Color="Color.Primary" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<div class="d-flex gap-2 mt-4">
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Save"
|
||||
Disabled="isSaving"
|
||||
@onclick="SaveAsync">
|
||||
@(isSaving ? "저장 중..." : "저장")
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
@onclick="@(() => Navigation.NavigateTo("/taxbaik/admin/announcements"))">
|
||||
취소
|
||||
</MudButton>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</MudForm>
|
||||
</MudPaper>
|
||||
|
||||
@code {
|
||||
[Parameter] public int? Id { get; set; }
|
||||
|
||||
private MudForm? form;
|
||||
private bool isSaving;
|
||||
private DateTime? startsAtDate;
|
||||
private DateTime? endsAtDate;
|
||||
|
||||
private AnnouncementDto model = new();
|
||||
private string StartsAtText { get => startsAtDate?.ToString("yyyy-MM-dd") ?? ""; set => startsAtDate = DateTime.TryParse(value, out var dt) ? dt : null; }
|
||||
private string EndsAtText { get => endsAtDate?.ToString("yyyy-MM-dd") ?? ""; set => endsAtDate = DateTime.TryParse(value, out var dt) ? dt : null; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
@@ -61,15 +115,15 @@
|
||||
}
|
||||
model = new AnnouncementDto
|
||||
{
|
||||
Id = entity.Id,
|
||||
Title = entity.Title,
|
||||
Content = entity.Content,
|
||||
Id = entity.Id,
|
||||
Title = entity.Title,
|
||||
Content = entity.Content,
|
||||
DisplayType = entity.DisplayType,
|
||||
IsActive = entity.IsActive,
|
||||
SortOrder = entity.SortOrder
|
||||
IsActive = entity.IsActive,
|
||||
SortOrder = entity.SortOrder
|
||||
};
|
||||
startsAtDate = entity.StartsAt?.ToLocalTime();
|
||||
endsAtDate = entity.EndsAt?.ToLocalTime();
|
||||
endsAtDate = entity.EndsAt?.ToLocalTime();
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -80,18 +134,41 @@
|
||||
|
||||
private async Task SaveAsync()
|
||||
{
|
||||
if (form is null) return;
|
||||
await form.Validate();
|
||||
if (!form.IsValid) return;
|
||||
|
||||
isSaving = true;
|
||||
try
|
||||
{
|
||||
model.StartsAt = startsAtDate.HasValue ? DateTime.SpecifyKind(startsAtDate.Value.Date, DateTimeKind.Local).ToUniversalTime() : null;
|
||||
model.EndsAt = endsAtDate.HasValue ? DateTime.SpecifyKind(endsAtDate.Value.Date.AddDays(1).AddSeconds(-1), DateTimeKind.Local).ToUniversalTime() : null;
|
||||
var result = Id.HasValue ? await AnnouncementClient.UpdateAsync(Id.Value, model) : await AnnouncementClient.CreateAsync(model);
|
||||
await JS.InvokeVoidAsync("alert", result != null ? "공지사항이 저장되었습니다." : "저장 실패");
|
||||
model.StartsAt = startsAtDate.HasValue
|
||||
? DateTime.SpecifyKind(startsAtDate.Value.Date, DateTimeKind.Local).ToUniversalTime()
|
||||
: null;
|
||||
model.EndsAt = endsAtDate.HasValue
|
||||
? DateTime.SpecifyKind(endsAtDate.Value.Date.AddDays(1).AddSeconds(-1), DateTimeKind.Local).ToUniversalTime()
|
||||
: null;
|
||||
|
||||
if (Id.HasValue)
|
||||
{
|
||||
var result = await AnnouncementClient.UpdateAsync(Id.Value, model);
|
||||
if (result != null)
|
||||
Snackbar.Add("공지사항이 저장되었습니다.", Severity.Success);
|
||||
else
|
||||
Snackbar.Add("저장 실패", Severity.Error);
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = await AnnouncementClient.CreateAsync(model);
|
||||
if (result != null)
|
||||
Snackbar.Add("공지사항이 저장되었습니다.", Severity.Success);
|
||||
else
|
||||
Snackbar.Add("저장 실패", Severity.Error);
|
||||
}
|
||||
Navigation.NavigateTo("/taxbaik/admin/announcements");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await JS.InvokeVoidAsync("alert", $"저장 실패: {ex.Message}");
|
||||
Snackbar.Add($"저장 실패: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -4,77 +4,90 @@
|
||||
@using TaxBaik.Domain.Entities
|
||||
@inject IAnnouncementBrowserClient AnnouncementClient
|
||||
@inject NavigationManager Navigation
|
||||
@inject IJSRuntime JS
|
||||
@inject IDialogService DialogService
|
||||
@inject ISnackbar Snackbar
|
||||
|
||||
<PageTitle>공지사항 관리</PageTitle>
|
||||
|
||||
<section class="admin-page-hero">
|
||||
<div>
|
||||
<div class="admin-eyebrow">Homepage</div>
|
||||
<h1 class="admin-page-title">공지사항 관리</h1>
|
||||
<p class="admin-page-subtitle">홈페이지 상단에 노출되는 공지사항을 등록하고 관리합니다.</p>
|
||||
<MudText Typo="Typo.caption" Class="admin-eyebrow">Homepage</MudText>
|
||||
<MudText Typo="Typo.h4" Class="admin-page-title">공지사항 관리</MudText>
|
||||
<MudText Typo="Typo.body2" Class="admin-page-subtitle">홈페이지 상단에 노출되는 공지사항을 등록하고 관리합니다.</MudText>
|
||||
</div>
|
||||
<a class="site-button primary" href="/taxbaik/admin/announcements/create">공지 등록</a>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Add"
|
||||
Href="/taxbaik/admin/announcements/create">
|
||||
공지 등록
|
||||
</MudButton>
|
||||
</section>
|
||||
|
||||
<div class="admin-surface">
|
||||
<MudPaper Class="admin-surface" Elevation="0">
|
||||
@if (announcements is null)
|
||||
{
|
||||
<Skeleton Count="5" CssClass="taxbaik-skeleton-grid" />
|
||||
<MudProgressLinear Indeterminate="true" />
|
||||
}
|
||||
else if (!announcements.Any())
|
||||
{
|
||||
<div class="muted">등록된 공지사항이 없습니다.</div>
|
||||
<MudText Class="pa-4 text-muted">등록된 공지사항이 없습니다.</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="admin-table-wrap">
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
<MudSimpleTable Striped="true" Dense="true" Class="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>제목</th>
|
||||
<th>유형</th>
|
||||
<th>상태</th>
|
||||
<th>게시 기간</th>
|
||||
<th>순서</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in announcements)
|
||||
{
|
||||
<tr>
|
||||
<th>제목</th>
|
||||
<th>유형</th>
|
||||
<th>상태</th>
|
||||
<th>게시 기간</th>
|
||||
<th>순서</th>
|
||||
<th></th>
|
||||
<td>@item.Title</td>
|
||||
<td>
|
||||
<MudChip Size="Size.Small" Color="@GetTypeColor(item.DisplayType)">
|
||||
@GetTypeLabel(item.DisplayType)
|
||||
</MudChip>
|
||||
</td>
|
||||
<td>
|
||||
@if (IsCurrentlyActive(item))
|
||||
{
|
||||
<MudChip Size="Size.Small" Color="Color.Success">노출 중</MudChip>
|
||||
}
|
||||
else if (!item.IsActive)
|
||||
{
|
||||
<MudChip Size="Size.Small" Color="Color.Default">비활성</MudChip>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudChip Size="Size.Small" Color="Color.Warning">기간 외</MudChip>
|
||||
}
|
||||
</td>
|
||||
<td class="small">
|
||||
@FormatPeriod(item)
|
||||
</td>
|
||||
<td>@item.SortOrder</td>
|
||||
<td>
|
||||
<MudButtonGroup Size="Size.Small" Variant="Variant.Outlined">
|
||||
<MudButton @onclick="@(() => Navigation.NavigateTo($"/taxbaik/admin/announcements/{item.Id}/edit"))">
|
||||
수정
|
||||
</MudButton>
|
||||
<MudButton Color="Color.Error" @onclick="@(() => DeleteAsync(item))">
|
||||
삭제
|
||||
</MudButton>
|
||||
</MudButtonGroup>
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in announcements)
|
||||
{
|
||||
<tr>
|
||||
<td>@item.Title</td>
|
||||
<td><span class="status-pill info">@GetTypeLabel(item.DisplayType)</span></td>
|
||||
<td>
|
||||
@if (IsCurrentlyActive(item))
|
||||
{
|
||||
<span class="status-pill success">노출 중</span>
|
||||
}
|
||||
else if (!item.IsActive)
|
||||
{
|
||||
<span class="status-pill default">비활성</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="status-pill warning">기간 외</span>
|
||||
}
|
||||
</td>
|
||||
<td class="small">@FormatPeriod(item)</td>
|
||||
<td>@item.SortOrder</td>
|
||||
<td>
|
||||
<div class="admin-actions">
|
||||
<button type="button" class="admin-icon-button" @onclick="@(() => Navigation.NavigateTo($"/taxbaik/admin/announcements/{item.Id}/edit"))">✎</button>
|
||||
<button type="button" class="admin-icon-button danger" @onclick="@(() => DeleteAsync(item))">✕</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</tbody>
|
||||
</MudSimpleTable>
|
||||
}
|
||||
</div>
|
||||
</MudPaper>
|
||||
|
||||
@code {
|
||||
[CascadingParameter]
|
||||
@@ -84,13 +97,16 @@
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender && AuthStateTask != null)
|
||||
if (firstRender)
|
||||
{
|
||||
var authState = await AuthStateTask;
|
||||
if (authState.User.Identity?.IsAuthenticated == true)
|
||||
if (AuthStateTask != null)
|
||||
{
|
||||
await LoadAsync();
|
||||
StateHasChanged();
|
||||
var authState = await AuthStateTask;
|
||||
if (authState.User.Identity?.IsAuthenticated == true)
|
||||
{
|
||||
await LoadAsync();
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,32 +119,36 @@
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await JS.InvokeVoidAsync("alert", $"오류: {ex.Message}");
|
||||
Snackbar.Add($"오류: {ex.Message}", Severity.Error);
|
||||
announcements = [];
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteAsync(Announcement item)
|
||||
{
|
||||
var confirmed = await JS.InvokeAsync<bool>("confirm", $"'{item.Title}' 공지를 삭제하시겠습니까?");
|
||||
if (!confirmed) return;
|
||||
var confirmed = await DialogService.ShowMessageBox(
|
||||
"공지 삭제",
|
||||
$"'{item.Title}' 공지를 삭제하시겠습니까?",
|
||||
yesText: "삭제", cancelText: "취소");
|
||||
|
||||
if (confirmed != true) return;
|
||||
|
||||
try
|
||||
{
|
||||
var success = await AnnouncementClient.DeleteAsync(item.Id);
|
||||
if (success)
|
||||
{
|
||||
await JS.InvokeVoidAsync("alert", "공지사항이 삭제되었습니다.");
|
||||
Snackbar.Add("공지사항이 삭제되었습니다.", Severity.Success);
|
||||
await LoadAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await JS.InvokeVoidAsync("alert", "삭제 실패");
|
||||
Snackbar.Add("삭제 실패", Severity.Error);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await JS.InvokeVoidAsync("alert", $"오류: {ex.Message}");
|
||||
Snackbar.Add($"오류: {ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,21 +157,28 @@
|
||||
if (!a.IsActive) return false;
|
||||
var now = DateTime.UtcNow;
|
||||
if (a.StartsAt.HasValue && a.StartsAt > now) return false;
|
||||
if (a.EndsAt.HasValue && a.EndsAt < now) return false;
|
||||
if (a.EndsAt.HasValue && a.EndsAt < now) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string FormatPeriod(Announcement a)
|
||||
{
|
||||
var start = a.StartsAt?.ToLocalTime().ToString("MM/dd") ?? "즉시";
|
||||
var end = a.EndsAt?.ToLocalTime().ToString("MM/dd") ?? "무기한";
|
||||
var end = a.EndsAt?.ToLocalTime().ToString("MM/dd") ?? "무기한";
|
||||
return $"{start} ~ {end}";
|
||||
}
|
||||
|
||||
private static Color GetTypeColor(string type) => type switch
|
||||
{
|
||||
"urgent" => Color.Error,
|
||||
"banner" => Color.Warning,
|
||||
_ => Color.Info
|
||||
};
|
||||
|
||||
private static string GetTypeLabel(string type) => type switch
|
||||
{
|
||||
"urgent" => "긴급",
|
||||
"banner" => "배너",
|
||||
_ => "일반"
|
||||
_ => "일반"
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user