e5981769b9
TaxBaik CI/CD / build-and-deploy (push) Successful in 2m11s
- Admin: replace the global @rendermode on <Routes>/<Router> with per-page render mode. Login.razor now prerenders (form visible before WASM loads); every other [Authorize] page stays prerender: false to avoid the AuthorizeRouteView blank-render regression from earlier attempts. Adds a "준비 중" -> "로그인" splash tied to WASM boot completion, and lets the authenticated-shell loading overlay stay up until AdminShell actually renders. - Contact.cshtml: fix the "Agree" checkbox missing value="true" - a checked box sent the browser-default "on", which bool model binding can't parse, so ModelState.IsValid silently went false and OnPostAsync returned a blank form with no visible error on every submission. Validation summary widened from ModelOnly to All so this class of failure isn't silent again. - TelegramInquiryNotificationService: read Telegram:InquiryChatId (falling back to ChatId) instead of only ChatId, matching the channel routing CLAUDE.md documents and deploy.yml already provisions as separate secrets. - Reconcile CLAUDE.md's self-contradicting Phase 8 prerender notes (Phase 9), rewrite validate_admin_render.sh for the per-page design, and add a SmartAdmin 5.5 design reference section to DOUZONE_UX_GUIDE.md for future admin screens (existing screens unchanged, tracked as WBS P4-03). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
178 lines
6.5 KiB
Plaintext
178 lines
6.5 KiB
Plaintext
@page "/admin/announcements/create"
|
|
@page "/admin/announcements/{Id:int}/edit"
|
|
@rendermode @(new InteractiveWebAssemblyRenderMode(prerender: false))
|
|
@attribute [Authorize]
|
|
@using TaxBaik.Application.DTOs
|
|
@using TaxBaik.Web.Services
|
|
@using TaxBaik.WasmClient.Components.Admin.Shared
|
|
@inject IAnnouncementBrowserClient AnnouncementClient
|
|
@inject NavigationManager Navigation
|
|
@inject ISnackbar Snackbar
|
|
|
|
<PageTitle>@(Id.HasValue ? "공지 수정" : "공지 등록")</PageTitle>
|
|
|
|
<section class="admin-page-hero">
|
|
<div>
|
|
<MudText Typo="Typo.caption" Class="admin-eyebrow">Homepage</MudText>
|
|
<MudText Typo="Typo.h4" Class="admin-page-title">@(Id.HasValue ? "공지 수정" : "공지 등록")</MudText>
|
|
</div>
|
|
</section>
|
|
|
|
<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">
|
|
<CommonCodeSelect @bind-Value="model.DisplayType"
|
|
Group="ANNOUNCEMENT_DISPLAY_TYPE"
|
|
Label="유형"
|
|
Class="mb-0" />
|
|
</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>
|
|
</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();
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
if (Id.HasValue)
|
|
{
|
|
try
|
|
{
|
|
var entity = await AnnouncementClient.GetByIdAsync(Id.Value);
|
|
if (entity is null)
|
|
{
|
|
Navigation.NavigateTo("/taxbaik/admin/announcements");
|
|
return;
|
|
}
|
|
model = new AnnouncementDto
|
|
{
|
|
Id = entity.Id,
|
|
Title = entity.Title,
|
|
Content = entity.Content,
|
|
DisplayType = entity.DisplayType,
|
|
IsActive = entity.IsActive,
|
|
SortOrder = entity.SortOrder
|
|
};
|
|
startsAtDate = entity.StartsAt?.ToLocalTime();
|
|
endsAtDate = entity.EndsAt?.ToLocalTime();
|
|
}
|
|
catch
|
|
{
|
|
Navigation.NavigateTo("/taxbaik/admin/announcements");
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
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)
|
|
{
|
|
Snackbar.Add($"저장 실패: {ex.Message}", Severity.Error);
|
|
}
|
|
finally
|
|
{
|
|
isSaving = false;
|
|
}
|
|
}
|
|
}
|