Files
taxbaik/TaxBaik.Web/Components/Admin/Pages/Clients/ClientEdit.razor
T
kjh2064 fbdbbc7a1f
TaxBaik CI/CD / build-and-deploy (push) Successful in 54s
refactor: Phase 7-3 - Clients + TaxFilings API-First (WIP)
**Clients Migration Complete:**
- ClientController: GET /api/client (paged), POST/PUT/DELETE
- ClientBrowserClient: IClientBrowserClient interface + implementation
- ClientList.razor: Service → API client
- ClientEdit.razor: Service → API client (Create/Update)

**TaxFilings API Framework Ready:**
- TaxFilingController: GET upcoming, GET by client, POST/PUT/DELETE
- TaxFilingBrowserClient: ITaxFilingBrowserClient interface + impl
- Registered in Program.cs with TokenRefreshHandler

**SOLID Applied:**
✓ Separation of concerns (Controller → Service → Repository)
✓ Dependency inversion (Blazor → Browser clients, not services)
✓ Interface segregation (Specialized clients per domain)

**Status:**
- Clients Blazor:  ClientList + ClientEdit refactored
- TaxFilings Blazor:  Pending refactor (pages exist)
- Faqs:  API + Blazor pending
- Announcements:  API + Blazor pending
- Phase 6 SignalR:  Deferred

Next: Refactor TaxFilings Blazor pages, then Faqs & Announcements
Build:  Success (0 errors, 2 warnings in Dashboard)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 11:08:43 +09:00

195 lines
7.5 KiB
Plaintext

@page "/admin/clients/create"
@page "/admin/clients/{Id:int}/edit"
@attribute [Authorize]
@using TaxBaik.Application.DTOs
@using TaxBaik.Web.Services
@using TaxBaik.Domain.Entities
@inject IClientBrowserClient ClientClient
@inject NavigationManager Navigation
@inject ISnackbar Snackbar
<PageTitle>@(Id.HasValue ? "고객 수정" : "고객 등록")</PageTitle>
<section class="admin-page-hero">
<div>
<MudText Typo="Typo.caption" Class="admin-eyebrow">CRM</MudText>
<MudText Typo="Typo.h4" Class="admin-page-title">@(Id.HasValue ? "고객 수정" : "고객 등록")</MudText>
</div>
<MudButton Variant="Variant.Outlined" Href="/taxbaik/admin/clients"
StartIcon="@Icons.Material.Filled.ArrowBack">목록으로</MudButton>
</section>
<MudPaper Class="admin-surface" Elevation="0" Style="max-width:720px;">
@if (isLoading)
{
<MudProgressLinear Indeterminate="true" />
}
else
{
<MudForm @ref="form" @bind-IsValid="isValid">
<MudGrid Spacing="3">
@* 기본 정보 *@
<MudItem xs="12">
<MudText Typo="Typo.subtitle1" Class="fw-bold mb-1">기본 정보</MudText>
<MudDivider />
</MudItem>
<MudItem xs="12" md="6">
<MudTextField @bind-Value="dto.Name" Label="고객명 *" Required="true"
RequiredError="고객명을 입력하세요." />
</MudItem>
<MudItem xs="12" md="6">
<MudTextField @bind-Value="dto.CompanyName" Label="회사명 (선택)" />
</MudItem>
<MudItem xs="12" md="6">
<MudTextField @bind-Value="dto.Phone" Label="연락처"
Placeholder="010-0000-0000" />
</MudItem>
<MudItem xs="12" md="6">
<MudTextField @bind-Value="dto.Email" Label="이메일" InputType="InputType.Email" />
</MudItem>
@* 세무 정보 *@
<MudItem xs="12" Class="mt-2">
<MudText Typo="Typo.subtitle1" Class="fw-bold mb-1">세무 정보</MudText>
<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>
</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>
</MudItem>
@* 관리 정보 *@
<MudItem xs="12" Class="mt-2">
<MudText Typo="Typo.subtitle1" Class="fw-bold mb-1">관리 정보</MudText>
<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>
</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>
</MudItem>
<MudItem xs="12">
<MudTextField @bind-Value="dto.Memo" Label="메모"
Lines="4" AutoGrow="true"
Placeholder="상담 배경, 특이사항, 중요 날짜 등 자유롭게 기록하세요" />
</MudItem>
@* 저장 버튼 *@
<MudItem xs="12" Class="d-flex gap-2 mt-2">
<MudButton Variant="Variant.Filled" Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Save"
OnClick="@SaveAsync" Disabled="@isSaving">
@(isSaving ? "저장 중..." : "저장")
</MudButton>
<MudButton Variant="Variant.Outlined" Href="/taxbaik/admin/clients">
취소
</MudButton>
</MudItem>
</MudGrid>
</MudForm>
}
</MudPaper>
@code {
[Parameter] public int? Id { get; set; }
private MudForm form = null!;
private CreateClientDto dto = new() { Status = "active" };
private bool isValid;
private bool isLoading = true;
private bool isSaving;
protected override async Task OnInitializedAsync()
{
if (Id.HasValue)
{
try
{
var client = await ClientClient.GetByIdAsync(Id.Value);
if (client is null)
{
Snackbar.Add("고객을 찾을 수 없습니다.", Severity.Error);
Navigation.NavigateTo("/taxbaik/admin/clients");
return;
}
dto = new CreateClientDto
{
Name = client.Name,
CompanyName = client.CompanyName,
Phone = client.Phone,
Email = client.Email,
ServiceType = client.ServiceType,
TaxType = client.TaxType,
Status = client.Status,
Source = client.Source,
Memo = client.Memo
};
}
catch (Exception ex)
{
Snackbar.Add($"오류: {ex.Message}", Severity.Error);
Navigation.NavigateTo("/taxbaik/admin/clients");
return;
}
}
isLoading = false;
}
private async Task SaveAsync()
{
await form.Validate();
if (!isValid) return;
isSaving = true;
try
{
if (Id.HasValue)
{
var result = await ClientClient.UpdateAsync(Id.Value, dto);
if (result != null)
Snackbar.Add("고객 정보가 수정되었습니다.", Severity.Success);
else
Snackbar.Add("수정에 실패했습니다.", Severity.Error);
}
else
{
var result = await ClientClient.CreateAsync(dto);
if (result != null)
Snackbar.Add("고객이 등록되었습니다.", Severity.Success);
else
Snackbar.Add("등록에 실패했습니다.", Severity.Error);
}
Navigation.NavigateTo("/taxbaik/admin/clients");
}
catch (Exception ex)
{
Snackbar.Add($"저장 실패: {ex.Message}", Severity.Error);
}
finally
{
isSaving = false;
}
}
}