345 lines
14 KiB
Plaintext
345 lines
14 KiB
Plaintext
@page "/admin/tax-filing-schedules"
|
|
@using TaxBaik.Web.Services.AdminClients
|
|
@inject ITaxFilingScheduleBrowserClient TaxFilingClient
|
|
@inject IClientBrowserClient ClientClient
|
|
@inject ISnackbar Snackbar
|
|
@inject IDialogService DialogService
|
|
@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>
|
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="PrepareCreate" StartIcon="@Icons.Material.Filled.Add" id="btn-add-schedule">
|
|
새 일정 추가
|
|
</MudButton>
|
|
</section>
|
|
|
|
@if (schedules is null)
|
|
{
|
|
<MudProgressLinear Indeterminate="true" />
|
|
}
|
|
else
|
|
{
|
|
<MudGrid Spacing="2" Class="mt-2">
|
|
<!-- Left: Dense Grid List -->
|
|
<MudItem XS="12" MD="8">
|
|
@if (schedules.Count == 0)
|
|
{
|
|
<MudAlert Severity="Severity.Info">
|
|
<MudIcon Icon="@Icons.Material.Filled.EventBusy" Class="me-2" />
|
|
신고 일정이 없습니다.
|
|
</MudAlert>
|
|
}
|
|
else
|
|
{
|
|
<MudDataGrid T="TaxFilingSchedule"
|
|
Items="@schedules"
|
|
Dense="true"
|
|
Hover="true"
|
|
Striped="true"
|
|
Virtualize="true"
|
|
RowsPerPage="30"
|
|
SelectedItem="@selectedSchedule"
|
|
SelectedItemChanged="OnRowSelected"
|
|
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))
|
|
{
|
|
@clientName
|
|
}
|
|
</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>
|
|
}
|
|
</MudItem>
|
|
|
|
<!-- Right: Detail Form Panel (Inline Editor) -->
|
|
<MudItem XS="12" MD="4">
|
|
<MudPaper Class="pa-4 admin-surface admin-editor-panel" Elevation="0" Style="border: 1px solid var(--border-color); min-height: 400px;">
|
|
<div class="d-flex align-center justify-space-between mb-4">
|
|
<MudText Typo="Typo.h6" Class="font-weight-bold">@(isEditMode ? "신고 일정 상세" : "새 신고 일정 추가")</MudText>
|
|
@if (isEditMode)
|
|
{
|
|
<MudButton Size="Size.Small" Variant="Variant.Outlined" Color="Color.Secondary" OnClick="PrepareCreate">
|
|
새로 작성
|
|
</MudButton>
|
|
}
|
|
</div>
|
|
<MudForm @ref="form">
|
|
<MudSelect T="int?"
|
|
@bind-Value="scheduleForm.ClientId"
|
|
Label="고객"
|
|
Required="true"
|
|
Variant="Variant.Outlined"
|
|
FullWidth="@true"
|
|
Class="mb-3"
|
|
RequiredError="고객을 선택하세요."
|
|
Disabled="@isEditMode">
|
|
@foreach (var client in clients)
|
|
{
|
|
<MudSelectItem Value="@((int?)client.Id)">@GetClientDisplayName(client)</MudSelectItem>
|
|
}
|
|
</MudSelect>
|
|
<MudSelect T="string" @bind-Value="scheduleForm.FilingType" Label="신고 유형" Variant="Variant.Outlined" FullWidth="@true" Class="mb-3" 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-3" Required="true" />
|
|
<MudNumericField T="int" @bind-Value="scheduleForm.FilingYear" Label="신고연도" Variant="Variant.Outlined" FullWidth="@true" Class="mb-4" Required="true" />
|
|
|
|
<div class="d-flex justify-end gap-2">
|
|
@if (isEditMode && selectedSchedule?.Status != "completed")
|
|
{
|
|
<MudButton Variant="Variant.Outlined" Color="Color.Success" OnClick="@(async () => await CompleteSchedule(selectedSchedule?.Id ?? 0))">완료 처리</MudButton>
|
|
}
|
|
@if (isEditMode)
|
|
{
|
|
<MudButton Variant="Variant.Outlined" Color="Color.Error" OnClick="@(async () => await DeleteSchedule(selectedSchedule?.Id ?? 0))">삭제</MudButton>
|
|
}
|
|
else
|
|
{
|
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="SaveSchedule" id="btn-save-schedule">저장</MudButton>
|
|
}
|
|
</div>
|
|
</MudForm>
|
|
</MudPaper>
|
|
</MudItem>
|
|
</MudGrid>
|
|
}
|
|
|
|
@code {
|
|
[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 isEditMode;
|
|
private TaxFilingSchedule? selectedSchedule;
|
|
private TaxFilingScheduleForm scheduleForm = new();
|
|
|
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
{
|
|
if (firstRender)
|
|
{
|
|
if (AuthStateTask != null)
|
|
{
|
|
var authState = await AuthStateTask;
|
|
if (authState.User.Identity?.IsAuthenticated == true)
|
|
{
|
|
await LoadData();
|
|
PrepareCreate();
|
|
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)
|
|
{
|
|
Snackbar.Add($"데이터 로드 실패: {ex.Message}", Severity.Error);
|
|
}
|
|
}
|
|
|
|
private void PrepareCreate()
|
|
{
|
|
selectedSchedule = null;
|
|
isEditMode = false;
|
|
scheduleForm = new TaxFilingScheduleForm
|
|
{
|
|
FilingYear = DateTime.Now.Year,
|
|
DueDate = DateTime.Today,
|
|
ClientId = clients.FirstOrDefault()?.Id
|
|
};
|
|
}
|
|
|
|
private void OnRowSelected(TaxFilingSchedule schedule)
|
|
{
|
|
if (schedule == null) return;
|
|
selectedSchedule = schedule;
|
|
isEditMode = true;
|
|
scheduleForm = new TaxFilingScheduleForm
|
|
{
|
|
ClientId = schedule.ClientId,
|
|
FilingType = schedule.FilingType,
|
|
DueDate = schedule.DueDate,
|
|
FilingYear = schedule.FilingYear
|
|
};
|
|
}
|
|
|
|
private async Task SaveSchedule()
|
|
{
|
|
if (form != null)
|
|
{
|
|
await form.Validate();
|
|
if (!form.IsValid)
|
|
{
|
|
Snackbar.Add("필수 항목을 입력해주세요.", Severity.Warning);
|
|
return;
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
if (scheduleForm.ClientId == null) return;
|
|
var newId = await TaxFilingClient.CreateAsync(
|
|
scheduleForm.ClientId.Value,
|
|
scheduleForm.FilingType,
|
|
scheduleForm.DueDate ?? DateTime.Today,
|
|
scheduleForm.FilingYear);
|
|
|
|
if (newId > 0)
|
|
{
|
|
Snackbar.Add("신고 일정이 추가되었습니다.", Severity.Success);
|
|
PrepareCreate();
|
|
await LoadData();
|
|
}
|
|
else
|
|
{
|
|
Snackbar.Add("등록에 실패했습니다.", Severity.Error);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Snackbar.Add($"저장 실패: {ex.Message}", Severity.Error);
|
|
}
|
|
}
|
|
|
|
private async Task CompleteSchedule(int id)
|
|
{
|
|
try
|
|
{
|
|
await TaxFilingClient.MarkCompletedAsync(id);
|
|
Snackbar.Add("신고 일정이 완료 처리되었습니다.", Severity.Success);
|
|
if (selectedSchedule?.Id == id)
|
|
{
|
|
PrepareCreate();
|
|
}
|
|
await LoadData();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Snackbar.Add($"처리 실패: {ex.Message}", Severity.Error);
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
try
|
|
{
|
|
await TaxFilingClient.DeleteAsync(id);
|
|
Snackbar.Add("신고 일정이 삭제되었습니다.", Severity.Success);
|
|
if (selectedSchedule?.Id == id)
|
|
{
|
|
PrepareCreate();
|
|
}
|
|
await LoadData();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Snackbar.Add($"삭제 실패: {ex.Message}", Severity.Error);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|