8202c3278b
TaxBaik CI/CD / build-and-deploy (push) Failing after 2m17s
Phase 8: Complete WebAssembly 렌더 모드 전환 (정공법) Migration Summary: - ALL Admin components → TaxBaik.Web.Client - Routes.razor, Pages/*, Layout/*, Shared/*, Forms/* - App.razor → TaxBaik.WasmClient (호스트 컴포넌트) - Shared utilities → TaxBaik.Application.Utils Architecture: ✅ App.razor: TaxBaik.WasmClient (WebAssembly, 호스트) ✅ Routes + Pages: TaxBaik.WasmClient (WebAssembly) ✅ Layout + Shared + Forms: TaxBaik.WasmClient (WebAssembly) ✅ Services: TaxBaik.Web (API-First) Key Changes: - Namespaces: TaxBaik.Web.Components.Admin → TaxBaik.WasmClient.Components.Admin - Shared utilities: TaxBaik.Application.Utils (single source of truth) - Program.cs: MapRazorComponents<TaxBaik.WasmClient.Components.Admin.App>() - _Imports.razor: Components/Admin 폴더에 재구성 Build Status: ✅ 0 errors, 0 warnings Benefits: - Stateless server (no Circuit memory) - Client-side rendering (WebAssembly) - Unlimited concurrent users (horizontal scaling) - ERP-ready architecture Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
259 lines
11 KiB
Plaintext
259 lines
11 KiB
Plaintext
@page "/admin/clients/{ClientId:int}"
|
|
@attribute [Authorize]
|
|
@using TaxBaik.Web.Services
|
|
@using TaxBaik.Web.Services.AdminClients
|
|
@using TaxBaik.WasmClient.Components.Admin.Shared
|
|
@inject IClientBrowserClient ClientClient
|
|
@inject IConsultingActivityBrowserClient ConsultingClient
|
|
@inject NavigationManager Navigation
|
|
@inject ISnackbar Snackbar
|
|
|
|
<PageTitle>고객 상세</PageTitle>
|
|
|
|
<section class="admin-page-hero">
|
|
<div>
|
|
<MudText Typo="Typo.caption" Class="admin-eyebrow">Client Details</MudText>
|
|
<MudText Typo="Typo.h4" Class="admin-page-title">고객 상세</MudText>
|
|
<MudText Typo="Typo.body2" Class="admin-page-subtitle">고객 정보와 상담 이력을 관리합니다.</MudText>
|
|
</div>
|
|
</section>
|
|
|
|
@if (client == null)
|
|
{
|
|
<MudText>고객을 찾을 수 없습니다.</MudText>
|
|
return;
|
|
}
|
|
|
|
<MudStack Row="true" AlignItems="AlignItems.Center" Class="mb-4" Spacing="2">
|
|
<MudButton Variant="Variant.Outlined" Color="Color.Primary"
|
|
StartIcon="@Icons.Material.Filled.ArrowBack"
|
|
@onclick="@(() => Navigation.NavigateTo("/taxbaik/admin/clients"))">
|
|
목록으로
|
|
</MudButton>
|
|
<MudButton Variant="Variant.Outlined" Color="Color.Warning"
|
|
StartIcon="@Icons.Material.Filled.Edit"
|
|
Href="@($"/taxbaik/admin/clients/{ClientId}/edit")">
|
|
수정
|
|
</MudButton>
|
|
</MudStack>
|
|
|
|
<MudGrid>
|
|
<MudItem xs="12" md="5">
|
|
<MudPaper Class="pa-4" Elevation="1">
|
|
<MudText Typo="Typo.h6" Class="mb-3">고객 정보</MudText>
|
|
<MudGrid Spacing="2">
|
|
<MudItem xs="6">
|
|
<MudText Typo="Typo.subtitle2" Color="Color.Secondary">이름</MudText>
|
|
<MudText>@client.Name</MudText>
|
|
</MudItem>
|
|
<MudItem xs="6">
|
|
<MudText Typo="Typo.subtitle2" Color="Color.Secondary">상호</MudText>
|
|
<MudText>@(client.CompanyName ?? "-")</MudText>
|
|
</MudItem>
|
|
<MudItem xs="6">
|
|
<MudText Typo="Typo.subtitle2" Color="Color.Secondary">연락처</MudText>
|
|
<MudText>@(client.Phone ?? "-")</MudText>
|
|
</MudItem>
|
|
<MudItem xs="6">
|
|
<MudText Typo="Typo.subtitle2" Color="Color.Secondary">이메일</MudText>
|
|
<MudText>@(client.Email ?? "-")</MudText>
|
|
</MudItem>
|
|
<MudItem xs="6">
|
|
<MudText Typo="Typo.subtitle2" Color="Color.Secondary">서비스</MudText>
|
|
<MudText>@(client.ServiceType ?? "-")</MudText>
|
|
</MudItem>
|
|
<MudItem xs="6">
|
|
<MudText Typo="Typo.subtitle2" Color="Color.Secondary">사업자 유형</MudText>
|
|
<MudText>@(client.TaxType ?? "-")</MudText>
|
|
</MudItem>
|
|
<MudItem xs="6">
|
|
<MudText Typo="Typo.subtitle2" Color="Color.Secondary">유입 경로</MudText>
|
|
<MudText>@(client.Source ?? "-")</MudText>
|
|
</MudItem>
|
|
<MudItem xs="6">
|
|
<MudText Typo="Typo.subtitle2" Color="Color.Secondary">등록일</MudText>
|
|
<MudText>@client.CreatedAt.ToLocalTime().ToString("yyyy-MM-dd")</MudText>
|
|
</MudItem>
|
|
@if (!string.IsNullOrWhiteSpace(client.Memo))
|
|
{
|
|
<MudItem xs="12">
|
|
<MudText Typo="Typo.subtitle2" Color="Color.Secondary">메모</MudText>
|
|
<MudText Style="white-space: pre-wrap;">@client.Memo</MudText>
|
|
</MudItem>
|
|
}
|
|
</MudGrid>
|
|
</MudPaper>
|
|
</MudItem>
|
|
|
|
<MudItem xs="12" md="7">
|
|
<MudPaper Class="pa-4" Elevation="1">
|
|
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Class="mb-3">
|
|
<MudText Typo="Typo.h6">상담 이력</MudText>
|
|
<MudButton Variant="Variant.Filled" Color="Color.Primary"
|
|
Size="Size.Small"
|
|
OnClick="OpenAddConsultation">
|
|
+ 상담 추가
|
|
</MudButton>
|
|
</MudStack>
|
|
|
|
@if (showAddForm)
|
|
{
|
|
<MudPaper Class="pa-3 mb-3" Outlined="true">
|
|
<MudGrid Spacing="2">
|
|
<MudItem xs="12" sm="6">
|
|
<MudDatePicker @bind-Date="newDate" Label="상담일" DateFormat="yyyy-MM-dd" />
|
|
</MudItem>
|
|
<MudItem xs="12" sm="6">
|
|
<CommonCodeSelect @bind-Value="newServiceType" Group="CONSULTING_ACTIVITY_TYPE" Label="서비스 분야" Placeholder="선택" Clearable="true" />
|
|
</MudItem>
|
|
<MudItem xs="12">
|
|
<MudTextField T="string" @bind-Value="newSummary" Label="상담 내용 *"
|
|
Lines="3" Variant="Variant.Outlined" Required="true" />
|
|
</MudItem>
|
|
<MudItem xs="12" sm="6">
|
|
<MudSelect T="string" @bind-Value="newResult" Label="결과">
|
|
<MudSelectItem Value="@("")">-</MudSelectItem>
|
|
@foreach (var r in results)
|
|
{
|
|
<MudSelectItem Value="@r">@r</MudSelectItem>
|
|
}
|
|
</MudSelect>
|
|
</MudItem>
|
|
<MudItem xs="12" sm="6">
|
|
<MudNumericField T="decimal?" @bind-Value="newFee" Label="수임료 (원)"
|
|
Format="N0" />
|
|
</MudItem>
|
|
</MudGrid>
|
|
<MudStack Row="true" Class="mt-2" Spacing="2">
|
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="AddConsultation">저장</MudButton>
|
|
<MudButton Variant="Variant.Outlined" OnClick="@(() => showAddForm = false)">취소</MudButton>
|
|
</MudStack>
|
|
</MudPaper>
|
|
}
|
|
|
|
@if (consultations.Count == 0)
|
|
{
|
|
<MudText Color="Color.Secondary">상담 이력이 없습니다.</MudText>
|
|
}
|
|
else
|
|
{
|
|
<MudList T="string" Dense="true">
|
|
@foreach (var c in consultations)
|
|
{
|
|
<MudListItem>
|
|
<MudPaper Class="pa-3" Outlined="true" Style="width:100%">
|
|
<MudStack Row="true" AlignItems="AlignItems.Start" Justify="Justify.SpaceBetween">
|
|
<div>
|
|
<MudText Typo="Typo.caption" Color="Color.Secondary">
|
|
@c.ConsultationDate.ToString("yyyy-MM-dd")
|
|
@if (!string.IsNullOrEmpty(c.ServiceType)) { <text> · @c.ServiceType</text> }
|
|
</MudText>
|
|
<MudText Style="white-space: pre-wrap;" Class="mt-1">@c.Summary</MudText>
|
|
@if (!string.IsNullOrEmpty(c.Result))
|
|
{
|
|
<MudChip T="string" Size="Size.Small" Color="Color.Info" Class="mt-1">@c.Result</MudChip>
|
|
}
|
|
@if (c.Fee.HasValue)
|
|
{
|
|
<MudText Typo="Typo.caption" Color="Color.Secondary" Class="mt-1">
|
|
수임료: @c.Fee.Value.ToString("N0")원
|
|
</MudText>
|
|
}
|
|
</div>
|
|
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
|
Size="Size.Small" Color="Color.Error"
|
|
OnClick="@(() => DeleteConsultation(c.Id))" />
|
|
</MudStack>
|
|
</MudPaper>
|
|
</MudListItem>
|
|
}
|
|
</MudList>
|
|
}
|
|
</MudPaper>
|
|
</MudItem>
|
|
</MudGrid>
|
|
|
|
@code {
|
|
[Parameter]
|
|
public int ClientId { get; set; }
|
|
|
|
private Domain.Entities.Client? client;
|
|
private List<Domain.Entities.Consultation> consultations = [];
|
|
private static readonly string[] results = ["", "상담완료", "추가자료 요청", "견적발송", "계약전환", "보류"];
|
|
|
|
private bool showAddForm;
|
|
private DateTime? newDate = DateTime.Today;
|
|
private string newServiceType = "";
|
|
private string newSummary = "";
|
|
private string newResult = "";
|
|
private decimal? newFee;
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
await LoadAll();
|
|
}
|
|
|
|
private async Task LoadAll()
|
|
{
|
|
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()
|
|
{
|
|
showAddForm = true;
|
|
newDate = DateTime.Today;
|
|
newServiceType = "";
|
|
newSummary = "";
|
|
newResult = "";
|
|
newFee = null;
|
|
}
|
|
|
|
private async Task AddConsultation()
|
|
{
|
|
try
|
|
{
|
|
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;
|
|
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 ConsultingClient.DeleteAsync(id);
|
|
await LoadAll();
|
|
Snackbar.Add("삭제되었습니다.", Severity.Info);
|
|
}
|
|
}
|