feat: 시즌별 마케팅 + 공지사항 관리 기능 추가
- 연간 세무 캘린더(7개 시즌) 기반 자동 Hero 섹션 전환 - 시즌 감지 시 D-Day 카운트다운, 긴박감 배지, 시즌 CTA 표시 - 서비스 카드 순서 시즌 관련 항목 우선 정렬 - 어드민 공지사항 CRUD (등록·수정·삭제, 기간·유형 설정) - 홈페이지 상단 공지 배너 자동 노출 (일반/배너/긴급) - CLAUDE.md에 세무 캘린더 및 마케팅 방향 하네스 추가 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
@page "/admin/announcements/create"
|
||||
@page "/admin/announcements/{Id:int}/edit"
|
||||
@attribute [Authorize]
|
||||
@using TaxBaik.Application.DTOs
|
||||
@using TaxBaik.Application.Services
|
||||
@inject AnnouncementService AnnouncementService
|
||||
@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">
|
||||
<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>
|
||||
</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)
|
||||
{
|
||||
var entity = await AnnouncementService.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();
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
await AnnouncementService.UpdateAsync(model);
|
||||
else
|
||||
await AnnouncementService.CreateAsync(model);
|
||||
|
||||
Snackbar.Add("공지사항이 저장되었습니다.", Severity.Success);
|
||||
Navigation.NavigateTo("/taxbaik/admin/announcements");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"저장 실패: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
isSaving = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user