refactor: admin ui를 fluent v5와 html 기반으로 전환
TaxBaik CI/CD / build-and-deploy (push) Failing after 1m53s
TaxBaik CI/CD / build-and-deploy (push) Failing after 1m53s
This commit is contained in:
@@ -2,177 +2,125 @@
|
||||
@using TaxBaik.Web.Services.AdminClients
|
||||
@inject ITaxFilingScheduleBrowserClient TaxFilingClient
|
||||
@inject IClientBrowserClient ClientClient
|
||||
@inject ISnackbar Snackbar
|
||||
@inject IDialogService DialogService
|
||||
@inject IJSRuntime JS
|
||||
@attribute [Authorize]
|
||||
|
||||
<PageTitle>신고 일정</PageTitle>
|
||||
|
||||
<section class="admin-page-hero">
|
||||
<div>
|
||||
<MudText Typo="Typo.caption" Class="admin-eyebrow">CRM & 세무관리</MudText>
|
||||
<MudText Typo="Typo.h4" Class="admin-page-title">신고 일정</MudText>
|
||||
<MudText Typo="Typo.body2" Class="admin-page-subtitle">고객별 마감일과 처리 상태를 한 화면에서 관리합니다.</MudText>
|
||||
<div class="admin-eyebrow">CRM & 세무관리</div>
|
||||
<h1 class="admin-page-title">신고 일정</h1>
|
||||
<p class="admin-page-subtitle">고객별 마감일과 처리 상태를 한 화면에서 관리합니다.</p>
|
||||
</div>
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
OnClick="OpenCreateDialog"
|
||||
StartIcon="@Icons.Material.Filled.Add">
|
||||
새 일정 추가
|
||||
</MudButton>
|
||||
<button type="button" class="site-button primary" @onclick="OpenCreateDialog">새 일정 추가</button>
|
||||
</section>
|
||||
|
||||
<MudPaper Class="admin-surface" Elevation="0">
|
||||
<div class="admin-surface">
|
||||
@if (schedules is null)
|
||||
{
|
||||
<MudProgressLinear Indeterminate="true" />
|
||||
<Skeleton Count="6" CssClass="taxbaik-skeleton-grid" />
|
||||
}
|
||||
else if (schedules.Count == 0)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info" Class="mt-4">
|
||||
<MudIcon Icon="@Icons.Material.Filled.EventBusy" Class="me-2" />
|
||||
신고 일정이 없습니다.
|
||||
</MudAlert>
|
||||
<div class="muted">신고 일정이 없습니다.</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudDataGrid T="TaxFilingSchedule"
|
||||
Items="@schedules"
|
||||
Dense="true"
|
||||
Hover="true"
|
||||
Striped="true"
|
||||
Virtualize="true"
|
||||
RowsPerPage="30"
|
||||
Class="admin-grid">
|
||||
<Columns>
|
||||
<PropertyColumn Property="x => x.Id" Title="ID" Sortable="true" />
|
||||
<TemplateColumn Title="고객">
|
||||
<CellTemplate>
|
||||
@if (clientMap.TryGetValue(context.Item.ClientId, out var clientName))
|
||||
{
|
||||
<MudLink Href="@($"/taxbaik/admin/clients/{context.Item.ClientId}")" Color="Color.Primary">
|
||||
@clientName
|
||||
</MudLink>
|
||||
}
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<PropertyColumn Property="x => x.FilingType" Title="신고 유형" />
|
||||
<TemplateColumn Title="마감일">
|
||||
<CellTemplate>
|
||||
@{
|
||||
var daysLeft = (context.Item.DueDate.Date - DateTime.Today).Days;
|
||||
var statusColor = daysLeft < 0 ? Color.Error : daysLeft <= 7 ? Color.Warning : Color.Success;
|
||||
}
|
||||
<MudChip Size="Size.Small" Color="@statusColor" Variant="Variant.Filled">
|
||||
@context.Item.DueDate.ToString("yyyy-MM-dd")
|
||||
@if (daysLeft >= 0)
|
||||
{
|
||||
<span class="ms-1">(D-@daysLeft)</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="ms-1">(마감 @Math.Abs(daysLeft)일 경과)</span>
|
||||
}
|
||||
</MudChip>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<PropertyColumn Property="x => x.FilingYear" Title="신고연도" />
|
||||
<TemplateColumn Title="상태">
|
||||
<CellTemplate>
|
||||
@if (context.Item.Status == "completed")
|
||||
{
|
||||
<MudChip Size="Size.Small" Color="Color.Success" Variant="Variant.Filled">완료</MudChip>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudChip Size="Size.Small" Color="Color.Default" Variant="Variant.Outlined">대기</MudChip>
|
||||
}
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="작업" Sortable="false">
|
||||
<CellTemplate>
|
||||
<MudButtonGroup Size="Size.Small" Variant="Variant.Outlined">
|
||||
@if (context.Item.Status != "completed")
|
||||
{
|
||||
<MudIconButton Icon="@Icons.Material.Filled.CheckCircle"
|
||||
Color="Color.Success"
|
||||
OnClick="@(async () => await CompleteSchedule(context.Item.Id))"
|
||||
Title="완료" />
|
||||
}
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
||||
Color="Color.Error"
|
||||
OnClick="@(async () => await DeleteSchedule(context.Item.Id))"
|
||||
Title="삭제" />
|
||||
</MudButtonGroup>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
</Columns>
|
||||
</MudDataGrid>
|
||||
<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>
|
||||
}
|
||||
</MudPaper>
|
||||
</div>
|
||||
|
||||
<MudDialog @bind-IsVisible="isDialogOpen" Options="new DialogOptions { MaxWidth = MaxWidth.Small, FullWidth = true }">
|
||||
<TitleContent>
|
||||
<MudText Typo="Typo.h6">새 신고 일정 추가</MudText>
|
||||
</TitleContent>
|
||||
<DialogContent>
|
||||
<MudForm @ref="form">
|
||||
<MudSelect T="int?"
|
||||
@bind-Value="scheduleForm.ClientId"
|
||||
Label="고객"
|
||||
Required="true"
|
||||
Variant="Variant.Outlined"
|
||||
FullWidth="true"
|
||||
Class="mb-4"
|
||||
RequiredError="고객을 선택하세요.">
|
||||
<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)
|
||||
{
|
||||
<MudSelectItem Value="@((int?)client.Id)">@GetClientDisplayName(client)</MudSelectItem>
|
||||
<option value="@client.Id.ToString()">@GetClientDisplayName(client)</option>
|
||||
}
|
||||
</MudSelect>
|
||||
<MudSelect T="string" @bind-Value="scheduleForm.FilingType" Label="신고 유형" Variant="Variant.Outlined" FullWidth="true" Class="mb-4" Required="true">
|
||||
<MudSelectItem Value="@("종합소득세")">종합소득세</MudSelectItem>
|
||||
<MudSelectItem Value="@("부가가치세")">부가가치세</MudSelectItem>
|
||||
<MudSelectItem Value="@("법인세")">법인세</MudSelectItem>
|
||||
<MudSelectItem Value="@("원천세")">원천세</MudSelectItem>
|
||||
<MudSelectItem Value="@("종합부동산세")">종합부동산세</MudSelectItem>
|
||||
<MudSelectItem Value="@("양도소득세")">양도소득세</MudSelectItem>
|
||||
<MudSelectItem Value="@("상속·증여세")">상속·증여세</MudSelectItem>
|
||||
<MudSelectItem Value="@("세무조정")">세무조정</MudSelectItem>
|
||||
</MudSelect>
|
||||
<MudDatePicker @bind-Date="scheduleForm.DueDate" Label="마감일" Variant="Variant.Outlined" FullWidth="true" Class="mb-4" Required="true" />
|
||||
<MudNumericField T="int" @bind-Value="scheduleForm.FilingYear" Label="신고연도" Variant="Variant.Outlined" FullWidth="true" Class="mb-4" Required="true" />
|
||||
</MudForm>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="CloseDialog">취소</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="SaveSchedule">저장</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
</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; }
|
||||
|
||||
[CascadingParameter] private Task<AuthenticationState>? AuthStateTask { get; set; }
|
||||
private List<TaxFilingSchedule>? schedules;
|
||||
private List<Client> clients = [];
|
||||
private Dictionary<int, string> clientMap = new();
|
||||
private MudForm? form;
|
||||
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)
|
||||
if (firstRender && AuthStateTask != null)
|
||||
{
|
||||
if (AuthStateTask != null)
|
||||
var authState = await AuthStateTask;
|
||||
if (authState.User.Identity?.IsAuthenticated == true)
|
||||
{
|
||||
var authState = await AuthStateTask;
|
||||
if (authState.User.Identity?.IsAuthenticated == true)
|
||||
{
|
||||
await LoadData();
|
||||
StateHasChanged();
|
||||
}
|
||||
await LoadData();
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -188,56 +136,36 @@
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"데이터 로드 실패: {ex.Message}", Severity.Error);
|
||||
await JS.InvokeVoidAsync("alert", $"데이터 로드 실패: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenCreateDialog()
|
||||
{
|
||||
scheduleForm = new TaxFilingScheduleForm
|
||||
{
|
||||
FilingYear = DateTime.Now.Year,
|
||||
DueDate = DateTime.Today,
|
||||
ClientId = clients.FirstOrDefault()?.Id
|
||||
};
|
||||
scheduleForm = new TaxFilingScheduleForm { FilingYear = DateTime.Now.Year, DueDate = DateTime.Today, ClientId = clients.FirstOrDefault()?.Id ?? 0 };
|
||||
isDialogOpen = true;
|
||||
}
|
||||
|
||||
private async Task SaveSchedule()
|
||||
{
|
||||
if (form != null)
|
||||
if (scheduleForm.ClientId <= 0 || string.IsNullOrWhiteSpace(scheduleForm.FilingType))
|
||||
{
|
||||
await form.Validate();
|
||||
if (!form.IsValid)
|
||||
{
|
||||
Snackbar.Add("필수 항목을 입력해주세요.", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
await JS.InvokeVoidAsync("alert", "필수 항목을 입력해주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (scheduleForm.ClientId == null) return;
|
||||
var newId = await TaxFilingClient.CreateAsync(
|
||||
scheduleForm.ClientId.Value,
|
||||
scheduleForm.FilingType,
|
||||
scheduleForm.DueDate ?? DateTime.Today,
|
||||
scheduleForm.FilingYear);
|
||||
|
||||
var newId = await TaxFilingClient.CreateAsync(scheduleForm.ClientId, scheduleForm.FilingType, scheduleForm.DueDate ?? DateTime.Today, scheduleForm.FilingYear);
|
||||
if (newId > 0)
|
||||
{
|
||||
Snackbar.Add("신고 일정이 추가되었습니다.", Severity.Success);
|
||||
await JS.InvokeVoidAsync("alert", "신고 일정이 추가되었습니다.");
|
||||
CloseDialog();
|
||||
await LoadData();
|
||||
}
|
||||
else
|
||||
{
|
||||
Snackbar.Add("등록에 실패했습니다.", Severity.Error);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"저장 실패: {ex.Message}", Severity.Error);
|
||||
await JS.InvokeVoidAsync("alert", $"저장 실패: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,57 +174,31 @@
|
||||
try
|
||||
{
|
||||
await TaxFilingClient.MarkCompletedAsync(id);
|
||||
Snackbar.Add("신고 일정이 완료 처리되었습니다.", Severity.Success);
|
||||
await JS.InvokeVoidAsync("alert", "신고 일정이 완료 처리되었습니다.");
|
||||
await LoadData();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"처리 실패: {ex.Message}", Severity.Error);
|
||||
await JS.InvokeVoidAsync("alert", $"처리 실패: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteSchedule(int id)
|
||||
{
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
{ "Title", "삭제 확인" },
|
||||
{ "Message", "이 신고 일정을 삭제하시겠습니까?" }
|
||||
};
|
||||
|
||||
var dialog = await DialogService.ShowAsync<ConfirmDialog>("", parameters);
|
||||
var result = await dialog.Result;
|
||||
if (result?.Canceled ?? true)
|
||||
return;
|
||||
|
||||
if (!await JS.InvokeAsync<bool>("confirm", "이 신고 일정을 삭제하시겠습니까?")) return;
|
||||
try
|
||||
{
|
||||
await TaxFilingClient.DeleteAsync(id);
|
||||
Snackbar.Add("신고 일정이 삭제되었습니다.", Severity.Success);
|
||||
await JS.InvokeVoidAsync("alert", "신고 일정이 삭제되었습니다.");
|
||||
await LoadData();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"삭제 실패: {ex.Message}", Severity.Error);
|
||||
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 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;
|
||||
}
|
||||
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; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user