Files
taxbaik/TaxBaik.Web/Components/Admin/Pages/TaxFilingSchedules.razor
T
kjh2064 1b173376ee
TaxBaik CI/CD / build-and-deploy (push) Failing after 1m53s
refactor: admin ui를 fluent v5와 html 기반으로 전환
2026-06-29 22:37:40 +09:00

205 lines
8.7 KiB
Plaintext

@page "/admin/tax-filing-schedules"
@using TaxBaik.Web.Services.AdminClients
@inject ITaxFilingScheduleBrowserClient TaxFilingClient
@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 (schedules is null)
{
<Skeleton Count="6" CssClass="taxbaik-skeleton-grid" />
}
else if (schedules.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>
<th>작업</th>
</tr>
</thead>
<tbody>
@foreach (var item in schedules)
{
var daysLeft = (item.DueDate.Date - DateTime.Today).Days;
<tr>
<td>@item.Id</td>
<td>@clientMap.GetValueOrDefault(item.ClientId, $"Client #{item.ClientId}")</td>
<td>@item.FilingType</td>
<td>@item.DueDate.ToString("yyyy-MM-dd") @(daysLeft >= 0 ? $"(D-{daysLeft})" : $"(마감 {Math.Abs(daysLeft)}일 경과)")</td>
<td>@item.FilingYear</td>
<td>@(item.Status == "completed" ? "완료" : "대기")</td>
<td>
<div class="admin-row-actions">
@if (item.Status != "completed")
{
<button type="button" class="site-button secondary" @onclick="@(async () => await CompleteSchedule(item.Id))">완료</button>
}
<button type="button" class="admin-icon-button danger" @onclick="@(async () => await DeleteSchedule(item.Id))">✕</button>
</div>
</td>
</tr>
}
</tbody>
</table>
</div>
}
</div>
<dialog class="admin-dialog" open="@isDialogOpen">
<form class="admin-dialog-card" @onsubmit="SaveSchedule" @onsubmit:preventDefault="true">
<h3>새 신고 일정 추가</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="scheduleForm.FilingType">
<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>마감일 <input class="admin-input" type="text" placeholder="2026-07-01" @bind="DueDateText" /></label>
<label>신고연도 <input class="admin-input" type="text" placeholder="2026" @bind="FilingYearText" /></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<TaxFilingSchedule>? schedules;
private List<Client> clients = [];
private Dictionary<int, string> clientMap = new();
private bool isDialogOpen;
private TaxFilingScheduleForm scheduleForm = new();
private string ClientIdText { get => scheduleForm.ClientId > 0 ? scheduleForm.ClientId.ToString() : ""; set => scheduleForm.ClientId = int.TryParse(value, out var id) ? id : 0; }
private string DueDateText { get => scheduleForm.DueDate?.ToString("yyyy-MM-dd") ?? ""; set => scheduleForm.DueDate = DateTime.TryParse(value, out var dt) ? dt : null; }
private string FilingYearText { get => scheduleForm.FilingYear.ToString(); set => scheduleForm.FilingYear = int.TryParse(value, out var year) ? year : DateTime.Now.Year; }
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
{
schedules = await TaxFilingClient.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()
{
scheduleForm = new TaxFilingScheduleForm { FilingYear = DateTime.Now.Year, DueDate = DateTime.Today, ClientId = clients.FirstOrDefault()?.Id ?? 0 };
isDialogOpen = true;
}
private async Task SaveSchedule()
{
if (scheduleForm.ClientId <= 0 || string.IsNullOrWhiteSpace(scheduleForm.FilingType))
{
await JS.InvokeVoidAsync("alert", "필수 항목을 입력해주세요.");
return;
}
try
{
var newId = await TaxFilingClient.CreateAsync(scheduleForm.ClientId, scheduleForm.FilingType, scheduleForm.DueDate ?? DateTime.Today, scheduleForm.FilingYear);
if (newId > 0)
{
await JS.InvokeVoidAsync("alert", "신고 일정이 추가되었습니다.");
CloseDialog();
await LoadData();
}
}
catch (Exception ex)
{
await JS.InvokeVoidAsync("alert", $"저장 실패: {ex.Message}");
}
}
private async Task CompleteSchedule(int id)
{
try
{
await TaxFilingClient.MarkCompletedAsync(id);
await JS.InvokeVoidAsync("alert", "신고 일정이 완료 처리되었습니다.");
await LoadData();
}
catch (Exception ex)
{
await JS.InvokeVoidAsync("alert", $"처리 실패: {ex.Message}");
}
}
private async Task DeleteSchedule(int id)
{
if (!await JS.InvokeAsync<bool>("confirm", "이 신고 일정을 삭제하시겠습니까?")) return;
try
{
await TaxFilingClient.DeleteAsync(id);
await JS.InvokeVoidAsync("alert", "신고 일정이 삭제되었습니다.");
await LoadData();
}
catch (Exception ex)
{
await JS.InvokeVoidAsync("alert", $"삭제 실패: {ex.Message}");
}
}
private void CloseDialog() { isDialogOpen = false; scheduleForm = 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 TaxFilingScheduleForm { public int ClientId { get; set; } public string FilingType { get; set; } = ""; public DateTime? DueDate { get; set; } public int FilingYear { get; set; } = DateTime.Now.Year; }
}