feat(admin): stabilize blog and admin patterns
TaxBaik CI/CD / build-and-deploy (push) Has been cancelled

This commit is contained in:
2026-07-02 10:46:27 +09:00
parent b3cab87539
commit cb47349a25
67 changed files with 1354 additions and 486 deletions
@@ -1,11 +1,9 @@
@page "/admin/blog/create"
@attribute [Authorize]
@rendermode @(new InteractiveServerRenderMode(prerender: false))
@using TaxBaik.Application.DTOs
@using TaxBaik.Application.Services
@using TaxBaik.Domain.Interfaces
@inject BlogService BlogService
@inject ICategoryRepository CategoryRepository
@using TaxBaik.Web.Components.Admin.Pages.Blog
@inject IBlogBrowserClient BlogClient
@inject ICategoryBrowserClient CategoryClient
@inject NavigationManager Navigation
@inject ISnackbar Snackbar
@@ -21,62 +19,16 @@
</section>
<MudPaper Class="pa-4 mt-4" Elevation="1">
<MudForm @ref="form">
<MudTextField @bind-Value="model.Title" Label="제목 *"
Variant="Variant.Outlined" Class="mb-4" Required="true" RequiredError="제목을 입력하세요." Counter="100" MaxLength="100" />
<MudSelect T="int?" @bind-Value="model.CategoryId" Label="카테고리"
Variant="Variant.Outlined" Class="mb-4">
@foreach (var category in categories)
{
<MudSelectItem T="int?" Value="@((int?)category.Id)">@category.Name</MudSelectItem>
}
</MudSelect>
<div class="mb-4">
<label class="d-block mb-2" style="font-weight: 500;">본문 내용 (마크다운) *</label>
<textarea id="markdown-editor" @bind="model.Content" style="display: none;"></textarea>
<div id="editor-container" style="border: 1px solid #d0d0d0; border-radius: 4px; min-height: 400px;"></div>
</div>
<MudTextField @bind-Value="model.Tags" Label="태그 (쉼표로 구분)"
Variant="Variant.Outlined" Class="mb-4" />
<MudTextField @bind-Value="model.SeoTitle" Label="SEO 제목"
Variant="Variant.Outlined" Class="mb-4" />
<MudTextField @bind-Value="model.SeoDescription" Label="SEO 설명"
Variant="Variant.Outlined" Lines="3" Class="mb-4" />
<MudCheckBox @bind-Checked="model.IsPublished" Label="즉시 발행" Class="mb-4" />
<div class="d-flex gap-2">
<MudButton Variant="Variant.Filled" Color="Color.Primary"
@onclick="SavePost">저장</MudButton>
</div>
</MudForm>
<BlogForm Model="model" Categories="categories" SubmitText="저장" OnSubmit="SavePost" OnCancel="GoBack" />
</MudPaper>
@code {
private MudForm? form;
private List<Domain.Entities.Category> categories = [];
private CreatePostModel model = new();
private EasyMDE.Editor? editor;
[Inject]
private IJSRuntime JS { get; set; } = null!;
private IReadOnlyList<Domain.Entities.Category> categories = [];
private BlogForm.BlogFormModel model = new();
protected override async Task OnInitializedAsync()
{
categories = (await CategoryRepository.GetAllAsync()).ToList();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JS.InvokeVoidAsync("window.initMarkdownEditor", "markdown-editor", model.Content ?? "");
}
categories = await CategoryClient.GetAllAsync();
}
private void GoBack()
@@ -86,25 +38,9 @@
private async Task SavePost()
{
if (form == null)
return;
// 에디터에서 최신 내용 가져오기
model.Content = await JS.InvokeAsync<string>("window.getMarkdownContent");
if (string.IsNullOrWhiteSpace(model.Content))
{
Snackbar.Add("본문 내용을 입력하세요.", Severity.Error);
return;
}
await form.Validate();
if (!form.IsValid)
return;
try
{
await BlogService.CreateAsync(new CreateBlogPostDto
var result = await BlogClient.CreateAsync(new CreateBlogPostDto
{
Title = model.Title,
Content = model.Content,
@@ -115,6 +51,12 @@
IsPublished = model.IsPublished
});
if (result == null)
{
Snackbar.Add("포스트 저장에 실패했습니다.", Severity.Error);
return;
}
Snackbar.Add("포스트가 저장되었습니다.", Severity.Success);
Navigation.NavigateTo("/taxbaik/admin/blog");
}
@@ -123,45 +65,4 @@
Snackbar.Add(ex.Message, Severity.Error);
}
}
private class CreatePostModel
{
public string Title { get; set; } = "";
public string Content { get; set; } = "";
public int? CategoryId { get; set; }
public string? Tags { get; set; }
public string? SeoTitle { get; set; }
public string? SeoDescription { get; set; }
public bool IsPublished { get; set; }
}
}
<!-- EasyMDE 초기화 스크립트 -->
<script>
window.initMarkdownEditor = function(editorId, initialContent) {
if (!window.easyMDEInstance) {
window.easyMDEInstance = new EasyMDE({
element: document.getElementById(editorId),
spellChecker: false,
autoDownloadFontAwesome: false,
initialValue: initialContent || "",
toolbar: [
"bold", "italic", "strikethrough", "|",
"heading", "code", "|",
"unordered-list", "ordered-list", "|",
"link", "image", "table", "|",
"quote", "horizontal-rule", "|",
"preview", "side-by-side", "fullscreen", "|",
"guide"
],
previewRender: function(plainText) {
return marked.parse(plainText);
}
});
}
};
window.getMarkdownContent = function() {
return window.easyMDEInstance ? window.easyMDEInstance.value() : "";
};
</script>
@@ -1,11 +1,9 @@
@page "/admin/blog/{id:int}/edit"
@attribute [Authorize]
@rendermode @(new InteractiveServerRenderMode(prerender: false))
@using TaxBaik.Application.DTOs
@using TaxBaik.Application.Services
@using TaxBaik.Domain.Interfaces
@inject BlogService BlogService
@inject ICategoryRepository CategoryRepository
@using TaxBaik.Web.Components.Admin.Pages.Blog
@inject IBlogBrowserClient BlogClient
@inject ICategoryBrowserClient CategoryClient
@inject NavigationManager Navigation
@inject ISnackbar Snackbar
@inject IDialogService DialogService
@@ -32,42 +30,10 @@ else if (post == null)
else
{
<MudPaper Class="pa-4 mt-4" Elevation="1">
<MudForm @ref="form">
<MudTextField @bind-Value="model.Title" Label="제목 *"
Variant="Variant.Outlined" Class="mb-4" Required="true" RequiredError="제목을 입력하세요." Counter="100" MaxLength="100" />
<MudSelect T="int?" @bind-Value="model.CategoryId" Label="카테고리"
Variant="Variant.Outlined" Class="mb-4">
@foreach (var category in categories)
{
<MudSelectItem T="int?" Value="@((int?)category.Id)">@category.Name</MudSelectItem>
}
</MudSelect>
<div class="mb-4">
<label class="d-block mb-2" style="font-weight: 500;">본문 내용 (마크다운) *</label>
<textarea id="markdown-editor" @bind="model.Content" style="display: none;"></textarea>
<div id="editor-container" style="border: 1px solid #d0d0d0; border-radius: 4px; min-height: 400px;"></div>
</div>
<MudTextField @bind-Value="model.Tags" Label="태그 (쉼표로 구분)"
Variant="Variant.Outlined" Class="mb-4" />
<MudTextField @bind-Value="model.SeoTitle" Label="SEO 제목"
Variant="Variant.Outlined" Class="mb-4" />
<MudTextField @bind-Value="model.SeoDescription" Label="SEO 설명"
Variant="Variant.Outlined" Lines="3" Class="mb-4" />
<MudCheckBox @bind-Checked="model.IsPublished" Label="발행" Class="mb-4" />
<div class="d-flex gap-2">
<MudButton Variant="Variant.Filled" Color="Color.Primary"
@onclick="SavePost">저장</MudButton>
<MudButton Variant="Variant.Outlined" Color="Color.Error"
@onclick="DeletePost">삭제</MudButton>
</div>
</MudForm>
<BlogForm Model="model" Categories="categories" SubmitText="저장" OnSubmit="SavePost" />
<div class="mt-4">
<MudButton Variant="Variant.Outlined" Color="Color.Error" @onclick="DeletePost">삭제</MudButton>
</div>
</MudPaper>
}
@@ -75,23 +41,19 @@ else
[Parameter]
public int Id { get; set; }
[Inject]
private IJSRuntime JS { get; set; } = null!;
private MudForm? form;
private Domain.Entities.BlogPost? post;
private List<Domain.Entities.Category> categories = [];
private EditPostModel model = new();
private TaxBaik.Application.DTOs.BlogPostResponseDto? post;
private IReadOnlyList<Domain.Entities.Category> categories = [];
private BlogForm.BlogFormModel model = new();
private bool isLoading = true;
protected override async Task OnInitializedAsync()
{
try
{
post = await BlogService.GetByIdAsync(Id);
post = await BlogClient.GetByIdAsync(Id);
if (post != null)
{
categories = (await CategoryRepository.GetAllAsync()).ToList();
categories = await CategoryClient.GetAllAsync();
MapPostToModel(post);
}
}
@@ -105,15 +67,7 @@ else
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && post != null)
{
await JS.InvokeVoidAsync("window.initMarkdownEditor", "markdown-editor", model.Content ?? "");
}
}
private void MapPostToModel(Domain.Entities.BlogPost post)
private void MapPostToModel(TaxBaik.Application.DTOs.BlogPostResponseDto post)
{
model.Title = post.Title;
model.Content = post.Content;
@@ -131,25 +85,12 @@ else
private async Task SavePost()
{
if (form == null || post == null)
return;
// 에디터에서 최신 내용 가져오기
model.Content = await JS.InvokeAsync<string>("window.getMarkdownContent");
if (string.IsNullOrWhiteSpace(model.Content))
{
Snackbar.Add("본문 내용을 입력하세요.", Severity.Error);
return;
}
await form.Validate();
if (!form.IsValid)
if (post == null)
return;
try
{
await BlogService.UpdateAsync(post.Id, new CreateBlogPostDto
var result = await BlogClient.UpdateAsync(post.Id, new CreateBlogPostDto
{
Title = model.Title,
Content = model.Content,
@@ -160,6 +101,12 @@ else
IsPublished = model.IsPublished
});
if (result == null)
{
Snackbar.Add("저장 실패: 포스트를 저장하지 못했습니다.", Severity.Error);
return;
}
Snackbar.Add("포스트가 저장되었습니다.", Severity.Success);
Navigation.NavigateTo("/taxbaik/admin/blog");
}
@@ -188,7 +135,12 @@ else
try
{
await BlogService.DeleteAsync(post.Id);
var deleted = await BlogClient.DeleteAsync(post.Id);
if (!deleted)
{
Snackbar.Add("삭제 실패: 포스트를 삭제하지 못했습니다.", Severity.Error);
return;
}
Snackbar.Add("포스트가 삭제되었습니다.", Severity.Success);
Navigation.NavigateTo("/taxbaik/admin/blog");
}
@@ -197,45 +149,4 @@ else
Snackbar.Add($"삭제 실패: {ex.Message}", Severity.Error);
}
}
private class EditPostModel
{
public string Title { get; set; } = "";
public string Content { get; set; } = "";
public int? CategoryId { get; set; }
public string? Tags { get; set; }
public string? SeoTitle { get; set; }
public string? SeoDescription { get; set; }
public bool IsPublished { get; set; }
}
}
<!-- EasyMDE 초기화 스크립트 -->
<script>
window.initMarkdownEditor = function(editorId, initialContent) {
if (!window.easyMDEInstance) {
window.easyMDEInstance = new EasyMDE({
element: document.getElementById(editorId),
spellChecker: false,
autoDownloadFontAwesome: false,
initialValue: initialContent || "",
toolbar: [
"bold", "italic", "strikethrough", "|",
"heading", "code", "|",
"unordered-list", "ordered-list", "|",
"link", "image", "table", "|",
"quote", "horizontal-rule", "|",
"preview", "side-by-side", "fullscreen", "|",
"guide"
],
previewRender: function(plainText) {
return marked.parse(plainText);
}
});
}
};
window.getMarkdownContent = function() {
return window.easyMDEInstance ? window.easyMDEInstance.value() : "";
};
</script>
@@ -0,0 +1,80 @@
@using TaxBaik.Application.DTOs
@using TaxBaik.Domain.Entities
<MudForm @ref="form">
<MudTextField @bind-Value="Model.Title" Label="제목 *"
Variant="Variant.Outlined" Class="mb-4" Required="true" RequiredError="제목을 입력하세요." Counter="100" MaxLength="100" />
<MudSelect T="int?" @bind-Value="Model.CategoryId" Label="카테고리"
Variant="Variant.Outlined" Class="mb-4">
@foreach (var category in Categories)
{
<MudSelectItem T="int?" Value="@((int?)category.Id)">@category.Name</MudSelectItem>
}
</MudSelect>
<MudTextField @bind-Value="Model.Content" Label="본문 내용 *"
Variant="Variant.Outlined" Lines="16" Required="true" RequiredError="본문 내용을 입력하세요."
Class="mb-4" />
<MudTextField @bind-Value="Model.Tags" Label="태그 (쉼표로 구분)"
Variant="Variant.Outlined" Class="mb-4" />
<MudTextField @bind-Value="Model.SeoTitle" Label="SEO 제목"
Variant="Variant.Outlined" Class="mb-4" />
<MudTextField @bind-Value="Model.SeoDescription" Label="SEO 설명"
Variant="Variant.Outlined" Lines="3" Class="mb-4" />
<MudCheckBox @bind-Checked="Model.IsPublished" Label="즉시 발행" Class="mb-4" />
<div class="d-flex gap-2">
<MudButton Variant="Variant.Filled" Color="Color.Primary" @onclick="HandleSubmit">@SubmitText</MudButton>
@if (OnCancel.HasDelegate)
{
<MudButton Variant="Variant.Outlined" @onclick="OnCancel">취소</MudButton>
}
</div>
</MudForm>
@code {
[Parameter, EditorRequired]
public BlogFormModel Model { get; set; } = new();
[Parameter]
public IReadOnlyList<Category> Categories { get; set; } = [];
[Parameter]
public string SubmitText { get; set; } = "저장";
[Parameter]
public EventCallback OnSubmit { get; set; }
[Parameter]
public EventCallback OnCancel { get; set; }
private MudForm? form;
private async Task HandleSubmit()
{
if (form == null)
return;
await form.Validate();
if (!form.IsValid)
return;
await OnSubmit.InvokeAsync();
}
public class BlogFormModel
{
public string Title { get; set; } = "";
public string Content { get; set; } = "";
public int? CategoryId { get; set; }
public string? Tags { get; set; }
public string? SeoTitle { get; set; }
public string? SeoDescription { get; set; }
public bool IsPublished { get; set; }
}
}
@@ -1,8 +1,9 @@
@page "/admin/clients/{ClientId:int}"
@attribute [Authorize]
@using TaxBaik.Application.Services
@inject ClientService ClientService
@inject ConsultationService ConsultationService
@using TaxBaik.Web.Services
@using TaxBaik.Web.Services.AdminClients
@inject IClientBrowserClient ClientClient
@inject IConsultingActivityBrowserClient ConsultingClient
@inject NavigationManager Navigation
@inject ISnackbar Snackbar
@@ -102,8 +103,8 @@
<MudDatePicker @bind-Date="newDate" Label="상담일" DateFormat="yyyy-MM-dd" />
</MudItem>
<MudItem xs="12" sm="6">
<MudSelect T="string" @bind-Value="newServiceType" Label="서비스 분야">
@foreach (var t in ClientService.ServiceTypes)
<MudSelect T="string" @bind-Value="newServiceType" Label="서비스 분야">
@foreach (var t in serviceTypes)
{
<MudSelectItem Value="@t">@t</MudSelectItem>
}
@@ -116,7 +117,7 @@
<MudItem xs="12" sm="6">
<MudSelect T="string" @bind-Value="newResult" Label="결과">
<MudSelectItem Value="@("")">-</MudSelectItem>
@foreach (var r in ConsultationService.Results)
@foreach (var r in results)
{
<MudSelectItem Value="@r">@r</MudSelectItem>
}
@@ -182,6 +183,8 @@
private Domain.Entities.Client? client;
private List<Domain.Entities.Consultation> consultations = [];
private static readonly string[] serviceTypes = ["기장대리", "세무조정", "양도세", "증여세", "상속세", "부가세", "종소세", "기타"];
private static readonly string[] results = ["", "상담완료", "추가자료 요청", "견적발송", "계약전환", "보류"];
private bool showAddForm;
private DateTime? newDate = DateTime.Today;
@@ -197,8 +200,19 @@
private async Task LoadAll()
{
client = await ClientService.GetByIdAsync(ClientId);
consultations = (await ConsultationService.GetByClientIdAsync(ClientId)).ToList();
client = await ClientClient.GetByIdAsync(ClientId);
consultations = (await ConsultingClient.GetByClientIdAsync(ClientId))
.Select(c => new Domain.Entities.Consultation
{
Id = c.Id,
ClientId = c.ClientId,
ConsultationDate = c.ActivityDate,
ServiceType = c.ActivityType,
Summary = c.Description,
Result = null,
Fee = null
})
.ToList();
}
private void OpenAddConsultation()
@@ -215,30 +229,35 @@
{
try
{
var c = new Domain.Entities.Consultation
{
ClientId = ClientId,
ConsultationDate = newDate?.ToUniversalTime() ?? DateTime.UtcNow,
ServiceType = string.IsNullOrWhiteSpace(newServiceType) ? null : newServiceType,
Summary = newSummary,
Result = string.IsNullOrWhiteSpace(newResult) ? null : newResult,
Fee = newFee
};
await ConsultationService.CreateAsync(c);
var newId = await ConsultingClient.CreateAsync(
ClientId,
string.IsNullOrWhiteSpace(newServiceType) ? "기타" : newServiceType,
newDate?.ToUniversalTime() ?? DateTime.UtcNow,
newSummary,
null,
null);
if (newId <= 0)
throw new Exception("상담 생성 실패");
showAddForm = false;
consultations = (await ConsultationService.GetByClientIdAsync(ClientId)).ToList();
await LoadAll();
Snackbar.Add("상담이 추가되었습니다.", Severity.Success);
}
catch (ValidationException ex)
{
Snackbar.Add(ex.Message, Severity.Error);
}
catch (Exception ex)
{
Snackbar.Add(ex.Message, Severity.Error);
}
}
private async Task DeleteConsultation(int id)
{
await ConsultationService.DeleteAsync(id);
consultations = (await ConsultationService.GetByClientIdAsync(ClientId)).ToList();
await ConsultingClient.DeleteAsync(id);
await LoadAll();
Snackbar.Add("삭제되었습니다.", Severity.Info);
}
}
@@ -4,6 +4,7 @@
@using TaxBaik.Application.DTOs
@using TaxBaik.Web.Services
@using TaxBaik.Domain.Entities
@using TaxBaik.Web.Components.Admin.Shared
@inject IClientBrowserClient ClientClient
@inject NavigationManager Navigation
@inject ISnackbar Snackbar
@@ -54,20 +55,10 @@
<MudDivider />
</MudItem>
<MudItem xs="12" md="6">
<MudSelect @bind-Value="dto.ServiceType" Label="서비스 유형" T="string" Clearable="true">
@foreach (var t in ClientService.ServiceTypes)
{
<MudSelectItem Value="@t">@t</MudSelectItem>
}
</MudSelect>
<CommonCodeSelect @bind-Value="dto.ServiceType" Group="CLIENT_SERVICE_TYPE" Label="서비스 유형" Clearable="true" />
</MudItem>
<MudItem xs="12" md="6">
<MudSelect @bind-Value="dto.TaxType" Label="세금 유형" T="string" Clearable="true">
@foreach (var t in ClientService.TaxTypes)
{
<MudSelectItem Value="@t">@t</MudSelectItem>
}
</MudSelect>
<CommonCodeSelect @bind-Value="dto.TaxType" Group="CLIENT_TAX_TYPE" Label="세금 유형" Clearable="true" />
</MudItem>
@* 관리 정보 *@
@@ -76,18 +67,10 @@
<MudDivider />
</MudItem>
<MudItem xs="12" md="6">
<MudSelect @bind-Value="dto.Status" Label="상태 *" T="string" Required="true">
<MudSelectItem Value="@("active")">활성</MudSelectItem>
<MudSelectItem Value="@("inactive")">비활성</MudSelectItem>
</MudSelect>
<CommonCodeSelect @bind-Value="dto.Status" Group="CLIENT_STATUS" Label="상태 *" Required="true" />
</MudItem>
<MudItem xs="12" md="6">
<MudSelect @bind-Value="dto.Source" Label="유입 경로" T="string" Clearable="true">
@foreach (var s in ClientService.Sources)
{
<MudSelectItem Value="@s">@s</MudSelectItem>
}
</MudSelect>
<CommonCodeSelect @bind-Value="dto.Source" Group="CLIENT_SOURCE" Label="유입 경로" Clearable="true" />
</MudItem>
<MudItem xs="12">
<MudTextField @bind-Value="dto.Memo" Label="메모"
@@ -119,7 +102,6 @@
private bool isValid;
private bool isLoading = true;
private bool isSaving;
protected override async Task OnInitializedAsync()
{
if (Id.HasValue)
@@ -9,18 +9,15 @@
<PageTitle>고객 관리</PageTitle>
<section class="admin-page-hero">
<div>
<MudText Typo="Typo.caption" Class="admin-eyebrow">CRM</MudText>
<MudText Typo="Typo.h4" Class="admin-page-title">고객 관리</MudText>
<MudText Typo="Typo.body2" Class="admin-page-subtitle">고객 카드를 등록하고 상담 이력을 관리합니다.</MudText>
</div>
<MudButton Variant="Variant.Filled" Color="Color.Primary"
StartIcon="@Icons.Material.Filled.PersonAdd"
Href="/taxbaik/admin/clients/create">
고객 등록
</MudButton>
</section>
<AdminPageHeader Title="고객 관리" Eyebrow="CRM" Subtitle="고객 카드를 등록하고 상담 이력을 관리합니다.">
<ChildContent>
<MudButton Variant="Variant.Filled" Color="Color.Primary"
StartIcon="@Icons.Material.Filled.PersonAdd"
Href="/taxbaik/admin/clients/create">
고객 등록
</MudButton>
</ChildContent>
</AdminPageHeader>
@* 검색/필터 바 *@
<MudPaper Class="admin-surface mb-3 pa-3" Elevation="0">
@@ -53,10 +50,7 @@
}
else if (!clients.Any())
{
<div class="pa-6 text-center">
<MudIcon Icon="@Icons.Material.Filled.PeopleAlt" Style="font-size:3rem; opacity:.3;" />
<MudText Class="mt-2 text-muted">등록된 고객이 없습니다.</MudText>
</div>
<AdminEmptyState Icon="@Icons.Material.Filled.PeopleAlt" Message="등록된 고객이 없습니다." />
}
else
{
@@ -1,9 +1,8 @@
@page "/admin/inquiries/create"
@attribute [Authorize]
@using TaxBaik.Application.DTOs
@using TaxBaik.Application.Services
@using TaxBaik.Web.Components.Admin.Forms
@inject InquiryService InquiryService
@inject IInquiryBrowserClient InquiryClient
@inject NavigationManager Navigation
@inject ISnackbar Snackbar
@@ -32,13 +31,21 @@
{
try
{
await InquiryService.SubmitAsync(
model.Name,
model.Phone,
model.ServiceType,
model.Message,
model.Email,
ipAddress: "admin-registered");
var result = await InquiryClient.CreateAsync(new SubmitInquiryDto
{
Name = model.Name,
Phone = model.Phone,
Email = model.Email,
ServiceType = model.ServiceType,
Message = model.Message,
SuppressNotification = true
});
if (result == null)
{
Snackbar.Add("문의가 등록되지 않았습니다.", Severity.Error);
return;
}
Snackbar.Add("문의가 등록되었습니다.", Severity.Success);
Navigation.NavigateTo("/taxbaik/admin/inquiries");
@@ -1,9 +1,8 @@
@page "/admin/inquiries/{id:int}/edit"
@attribute [Authorize]
@using TaxBaik.Application.DTOs
@using TaxBaik.Application.Services
@using TaxBaik.Web.Components.Admin.Forms
@inject InquiryService InquiryService
@inject IInquiryBrowserClient InquiryClient
@inject NavigationManager Navigation
@inject ISnackbar Snackbar
@inject IDialogService DialogService
@@ -52,7 +51,7 @@ else
{
try
{
inquiry = await InquiryService.GetByIdAsync(Id);
inquiry = await InquiryClient.GetByIdAsync(Id);
if (inquiry != null)
{
formModel = new InquiryForm.InquiryFormModel
@@ -89,19 +88,34 @@ else
try
{
inquiry.Name = model.Name;
inquiry.Phone = model.Phone;
inquiry.Email = model.Email;
inquiry.ServiceType = model.ServiceType;
inquiry.Message = model.Message;
inquiry.AdminMemo = model.AdminMemo;
if (inquiry.Status != model.Status)
var updated = await InquiryClient.UpdateAsync(inquiry.Id, new UpdateInquiryDto
{
await InquiryService.UpdateStatusAsync(inquiry.Id, model.Status);
Name = model.Name,
Phone = model.Phone,
Email = model.Email,
ServiceType = model.ServiceType,
Message = model.Message,
Status = model.Status,
AdminMemo = model.AdminMemo
});
if (updated == null)
{
Snackbar.Add("문의 수정에 실패했습니다.", Severity.Error);
return;
}
await InquiryService.UpdateAdminMemoAsync(inquiry.Id, model.AdminMemo);
inquiry = updated;
formModel = new InquiryForm.InquiryFormModel
{
Name = inquiry.Name,
Phone = inquiry.Phone,
Email = inquiry.Email,
ServiceType = inquiry.ServiceType,
Message = inquiry.Message,
Status = inquiry.Status,
AdminMemo = inquiry.AdminMemo
};
Snackbar.Add("문의가 수정되었습니다.", Severity.Success);
Navigation.NavigateTo("/taxbaik/admin/inquiries");
@@ -131,7 +145,12 @@ else
try
{
await InquiryService.DeleteAsync(inquiry.Id);
var deleted = await InquiryClient.DeleteAsync(inquiry.Id);
if (!deleted)
{
Snackbar.Add("문의 삭제에 실패했습니다.", Severity.Error);
return;
}
Snackbar.Add("문의가 삭제되었습니다.", Severity.Success);
Navigation.NavigateTo("/taxbaik/admin/inquiries");
}
@@ -1,5 +1,6 @@
@page "/admin/revenue-trackings"
@using TaxBaik.Web.Services.AdminClients
@using TaxBaik.Web.Components.Admin.Shared
@inject IRevenueTrackingBrowserClient RevenueClient
@inject IClientBrowserClient ClientClient
@inject ISnackbar Snackbar
@@ -102,13 +103,7 @@
<MudTextField T="string" @bind-Value="revenueForm.InvoiceNumber" Label="청구번호" Variant="Variant.Outlined" FullWidth="true" Class="mb-4" Required="true" />
<MudDatePicker @bind-Date="revenueForm.InvoiceDate" Label="청구일" Variant="Variant.Outlined" FullWidth="true" Class="mb-4" Required="true" />
<MudNumericField T="decimal" @bind-Value="revenueForm.Amount" Label="청구액" Variant="Variant.Outlined" FullWidth="true" Class="mb-4" Required="true" />
<MudSelect T="string" @bind-Value="revenueForm.ServiceType" Label="서비스 유형" Variant="Variant.Outlined" FullWidth="true" Class="mb-4">
<MudSelectItem Value="@("기장 수수료")">기장 수수료</MudSelectItem>
<MudSelectItem Value="@("세무조정료")">세무조정료</MudSelectItem>
<MudSelectItem Value="@("세무상담료")">세무상담료</MudSelectItem>
<MudSelectItem Value="@("신고 대행료")">신고 대행료</MudSelectItem>
<MudSelectItem Value="@("자문 수수료")">자문 수수료</MudSelectItem>
</MudSelect>
<CommonCodeSelect @bind-Value="revenueForm.ServiceType" Group="REVENUE_SERVICE_TYPE" Label="서비스 유형" Class="mb-4" />
<MudDatePicker @bind-Date="revenueForm.DueDate" Label="납부예정일" Variant="Variant.Outlined" FullWidth="true" Class="mb-4" />
</MudForm>
</DialogContent>
@@ -35,7 +35,7 @@
<MudTextField @bind-Value="email" Label="이메일"
Variant="Variant.Outlined" Class="mb-4" />
<MudTextField @bind-Value="kakaoUrl" Label="카카오 채널 URL"
<MudTextField @bind-Value="kakaoUrl" Label="카카오채널 URL"
Variant="Variant.Outlined" Class="mb-4" />
<MudTextField @bind-Value="instagramUrl" Label="인스타그램"
@@ -1,5 +1,6 @@
@using TaxBaik.Web.Services
@using TaxBaik.Domain.Entities
@using TaxBaik.Web.Components.Admin.Shared
@inject ITaxFilingBrowserClient FilingClient
@inject ISnackbar Snackbar
@@ -21,10 +22,10 @@ else
<RowTemplate>
<MudTd>@context.ClientName</MudTd>
<MudTd>@context.FilingType</MudTd>
<MudTd>@context.DueDate.ToString("yyyy-MM-dd")</MudTd>
<MudTd>@BusinessDayCalculator.GetEffectiveDueDate(DateOnly.FromDateTime(context.DueDate)).ToDateTime(TimeOnly.MinValue).ToString("yyyy-MM-dd")</MudTd>
<MudTd>
@{
var dday = (context.DueDate.Date - DateTime.Today).Days;
var dday = BusinessDayCalculator.GetDday(DateOnly.FromDateTime(context.DueDate));
}
@if (dday < 0)
{
@@ -2,6 +2,7 @@
@attribute [Authorize]
@using TaxBaik.Web.Services
@using TaxBaik.Domain.Entities
@using TaxBaik.Web.Components.Admin.Shared
@inject ITaxFilingBrowserClient FilingClient
@inject IClientBrowserClient ClientClient
@inject ISnackbar Snackbar
@@ -34,12 +35,7 @@
Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12" sm="6" md="4">
<MudSelect T="string" @bind-Value="newFilingType" Label="신고 유형 *" Variant="Variant.Outlined">
@foreach (var t in TaxFilingService.FilingTypes)
{
<MudSelectItem Value="@t">@t</MudSelectItem>
}
</MudSelect>
<CommonCodeSelect @bind-Value="newFilingType" Group="FILING_TYPE" Label="신고 유형 *" />
</MudItem>
<MudItem xs="12" sm="6" md="4">
<MudDatePicker @bind-Date="newDueDate" Label="신고 기한 *" DateFormat="yyyy-MM-dd" />
@@ -82,6 +78,10 @@
protected override async Task OnInitializedAsync() => await Reload();
protected override async Task OnParametersSetAsync()
{
}
private async Task Reload()
{
try