0e98e68532
DB: - V006__CreateClients.sql: clients 테이블 (name, company_name, phone, email, service_type, tax_type, status, source, memo) Domain: - Client 엔티티 - IClientRepository (GetPagedAsync 이름/연락처/회사명 검색 + 상태 필터) Infrastructure: - ClientRepository: ILIKE 검색, 페이징, CRUD Application: - ClientService: ServiceTypes/TaxTypes/Sources 상수 정의 - CreateClientDto Admin UI: - ClientList.razor: 검색바 + 상태 필터 + 페이징 테이블 - ClientEdit.razor: 기본정보/세무정보/관리정보 섹션 폼 - MainLayout: 고객 관리 NavGroup 추가, 홈페이지 메뉴 그룹화 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
180 lines
6.9 KiB
Plaintext
180 lines
6.9 KiB
Plaintext
@page "/admin/clients/create"
|
|
@page "/admin/clients/{Id:int}/edit"
|
|
@attribute [Authorize]
|
|
@using TaxBaik.Application.DTOs
|
|
@using TaxBaik.Application.Services
|
|
@using TaxBaik.Domain.Entities
|
|
@inject ClientService ClientService
|
|
@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)
|
|
{
|
|
var client = await ClientService.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
|
|
};
|
|
}
|
|
isLoading = false;
|
|
}
|
|
|
|
private async Task SaveAsync()
|
|
{
|
|
await form.Validate();
|
|
if (!isValid) return;
|
|
|
|
isSaving = true;
|
|
try
|
|
{
|
|
if (Id.HasValue)
|
|
{
|
|
await ClientService.UpdateAsync(Id.Value, dto);
|
|
Snackbar.Add("고객 정보가 수정되었습니다.", Severity.Success);
|
|
}
|
|
else
|
|
{
|
|
var newId = await ClientService.CreateAsync(dto);
|
|
Snackbar.Add("고객이 등록되었습니다.", Severity.Success);
|
|
}
|
|
Navigation.NavigateTo("/taxbaik/admin/clients");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Snackbar.Add($"저장 실패: {ex.Message}", Severity.Error);
|
|
}
|
|
finally
|
|
{
|
|
isSaving = false;
|
|
}
|
|
}
|
|
}
|