221 lines
9.4 KiB
Plaintext
221 lines
9.4 KiB
Plaintext
@page "/admin/tax-profiles"
|
|
@using TaxBaik.Web.Services.AdminClients
|
|
@inject ITaxProfileBrowserClient TaxProfileClient
|
|
@inject IClientBrowserClient ClientClient
|
|
@inject IJSRuntime JS
|
|
@attribute [Authorize]
|
|
|
|
<PageTitle>세무 프로필</PageTitle>
|
|
<section class="admin-page-hero">
|
|
<div>
|
|
<div class="admin-eyebrow">CRM & 세무관리</div>
|
|
<h1 class="admin-page-title">세무 프로필</h1>
|
|
<p class="admin-page-subtitle">고객별 세무 프로필, 신고 일정, 위험도 추적</p>
|
|
</div>
|
|
<button type="button" class="site-button primary" @onclick="OpenCreateDialog">새 프로필 추가</button>
|
|
</section>
|
|
|
|
<div class="admin-surface">
|
|
@if (profiles is null)
|
|
{
|
|
<Skeleton Count="5" CssClass="taxbaik-skeleton-grid" />
|
|
}
|
|
else if (profiles.Count == 0)
|
|
{
|
|
<div class="muted">세무 프로필이 없습니다.</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="admin-table-wrap">
|
|
<table class="admin-table">
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>고객</th>
|
|
<th>사업 유형</th>
|
|
<th>위험도</th>
|
|
<th>다음 신고</th>
|
|
<th>작업</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach (var item in profiles)
|
|
{
|
|
<tr>
|
|
<td>@item.Id</td>
|
|
<td>@(clientMap.GetValueOrDefault(item.ClientId, $"Client #{item.ClientId}"))</td>
|
|
<td>@item.BusinessType</td>
|
|
<td><span class="status-pill @(item.TaxRiskLevel == "high" ? "danger" : item.TaxRiskLevel == "normal" ? "warning" : "success")">@item.TaxRiskLevel</span></td>
|
|
<td>@(item.NextFilingDueDate?.ToString("yyyy-MM-dd") ?? "—")</td>
|
|
<td>
|
|
<div class="admin-row-actions">
|
|
<button type="button" class="admin-icon-button" @onclick="@(async () => await OpenEditDialog(item))">✎</button>
|
|
<button type="button" class="admin-icon-button danger" @onclick="@(async () => await DeleteProfile(item.Id))">✕</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<dialog class="admin-dialog" open="@isDialogOpen">
|
|
<form class="admin-dialog-card" @onsubmit="SaveProfile" @onsubmit:preventDefault="true">
|
|
<h3>@(isEditMode ? "세무 프로필 수정" : "새 세무 프로필 추가")</h3>
|
|
<label>고객
|
|
<select class="admin-input" @bind="ClientIdText">
|
|
<option value="">선택하세요</option>
|
|
@foreach (var client in clients)
|
|
{
|
|
<option value="@client.Id.ToString()">@GetClientDisplayName(client)</option>
|
|
}
|
|
</select>
|
|
</label>
|
|
<label>사업 유형
|
|
<select class="admin-input" @bind="profileForm.BusinessType">
|
|
<option value="">선택하세요</option>
|
|
<option value="일반제조업">일반제조업</option>
|
|
<option value="도소매업">도소매업</option>
|
|
<option value="서비스업">서비스업</option>
|
|
<option value="정보통신업">정보통신업</option>
|
|
<option value="부동산업">부동산업</option>
|
|
<option value="건설업">건설업</option>
|
|
<option value="음식점업">음식점업</option>
|
|
<option value="프리랜서">프리랜서</option>
|
|
<option value="기타">기타</option>
|
|
</select>
|
|
</label>
|
|
<label>위험도
|
|
<select class="admin-input" @bind="profileForm.TaxRiskLevel">
|
|
<option value="low">낮음</option>
|
|
<option value="normal">보통</option>
|
|
<option value="high">높음</option>
|
|
</select>
|
|
</label>
|
|
<label>다음 신고 예정일 <input class="admin-input" type="text" placeholder="2026-07-01" @bind="NextFilingText" /></label>
|
|
<label>특수 사항 <textarea class="admin-input" rows="3" @bind="profileForm.SpecialNotes"></textarea></label>
|
|
<div class="admin-dialog-actions">
|
|
<button type="button" class="site-button secondary" @onclick="CloseDialog">취소</button>
|
|
<button type="submit" class="site-button primary">저장</button>
|
|
</div>
|
|
</form>
|
|
</dialog>
|
|
|
|
@code {
|
|
[CascadingParameter] private Task<AuthenticationState>? AuthStateTask { get; set; }
|
|
private List<TaxProfile>? profiles;
|
|
private List<Client> clients = [];
|
|
private Dictionary<int, string> clientMap = new();
|
|
private bool isDialogOpen;
|
|
private bool isEditMode;
|
|
private TaxProfile? editingProfile;
|
|
private TaxProfileForm profileForm = new();
|
|
private string ClientIdText { get => profileForm.ClientId > 0 ? profileForm.ClientId.ToString() : ""; set => profileForm.ClientId = int.TryParse(value, out var id) ? id : 0; }
|
|
private string NextFilingText { get => profileForm.NextFilingDueDate?.ToString("yyyy-MM-dd") ?? ""; set => profileForm.NextFilingDueDate = DateTime.TryParse(value, out var dt) ? dt : null; }
|
|
|
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
{
|
|
if (firstRender && AuthStateTask != null)
|
|
{
|
|
var authState = await AuthStateTask;
|
|
if (authState.User.Identity?.IsAuthenticated == true)
|
|
{
|
|
await LoadData();
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
private async Task LoadData()
|
|
{
|
|
try
|
|
{
|
|
profiles = await TaxProfileClient.GetAllAsync();
|
|
var (clientItems, _) = await ClientClient.GetPagedAsync(pageSize: 1000);
|
|
clients = clientItems.ToList();
|
|
clientMap = clients.ToDictionary(c => c.Id, GetClientDisplayName);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await JS.InvokeVoidAsync("alert", $"데이터 로드 실패: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private void OpenCreateDialog()
|
|
{
|
|
isEditMode = false;
|
|
editingProfile = null;
|
|
profileForm = new TaxProfileForm { ClientId = clients.FirstOrDefault()?.Id ?? 0, TaxRiskLevel = "normal", NextFilingDueDate = DateTime.Today.AddMonths(1) };
|
|
isDialogOpen = true;
|
|
}
|
|
|
|
private async Task OpenEditDialog(TaxProfile profile)
|
|
{
|
|
isEditMode = true;
|
|
editingProfile = profile;
|
|
profileForm = new TaxProfileForm
|
|
{
|
|
ClientId = profile.ClientId,
|
|
BusinessType = profile.BusinessType ?? "",
|
|
TaxRiskLevel = profile.TaxRiskLevel,
|
|
NextFilingDueDate = profile.NextFilingDueDate,
|
|
SpecialNotes = profile.SpecialNotes
|
|
};
|
|
isDialogOpen = true;
|
|
await Task.CompletedTask;
|
|
}
|
|
|
|
private async Task SaveProfile()
|
|
{
|
|
if (profileForm.ClientId <= 0 || string.IsNullOrWhiteSpace(profileForm.BusinessType))
|
|
{
|
|
await JS.InvokeVoidAsync("alert", "고객과 사업 유형을 입력하세요.");
|
|
return;
|
|
}
|
|
try
|
|
{
|
|
if (isEditMode && editingProfile != null)
|
|
{
|
|
await TaxProfileClient.UpdateAsync(editingProfile.Id, profileForm.BusinessType, null, profileForm.NextFilingDueDate, profileForm.TaxRiskLevel);
|
|
await JS.InvokeVoidAsync("alert", "세무 프로필이 수정되었습니다.");
|
|
}
|
|
else
|
|
{
|
|
var newId = await TaxProfileClient.CreateAsync(profileForm.ClientId, profileForm.BusinessType);
|
|
if (newId > 0)
|
|
{
|
|
await TaxProfileClient.UpdateAsync(newId, profileForm.BusinessType, null, profileForm.NextFilingDueDate, profileForm.TaxRiskLevel);
|
|
await JS.InvokeVoidAsync("alert", "세무 프로필이 추가되었습니다.");
|
|
}
|
|
}
|
|
CloseDialog();
|
|
await LoadData();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await JS.InvokeVoidAsync("alert", $"저장 실패: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private async Task DeleteProfile(int id)
|
|
{
|
|
if (!await JS.InvokeAsync<bool>("confirm", "이 세무 프로필을 삭제하시겠습니까?")) return;
|
|
try
|
|
{
|
|
await TaxProfileClient.DeleteAsync(id);
|
|
await JS.InvokeVoidAsync("alert", "세무 프로필이 삭제되었습니다.");
|
|
await LoadData();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await JS.InvokeVoidAsync("alert", $"삭제 실패: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private void CloseDialog() { isDialogOpen = false; isEditMode = false; editingProfile = null; profileForm = new(); }
|
|
private static string GetClientDisplayName(Client client) => !string.IsNullOrWhiteSpace(client.CompanyName) ? client.CompanyName : !string.IsNullOrWhiteSpace(client.Name) ? client.Name : $"Client #{client.Id}";
|
|
private sealed class TaxProfileForm { public int ClientId { get; set; } public string BusinessType { get; set; } = ""; public string TaxRiskLevel { get; set; } = "normal"; public DateTime? NextFilingDueDate { get; set; } public string? SpecialNotes { get; set; } }
|
|
}
|