feat: implement ERP-style split pane master-detail layout for tax profiles, schedules, and contracts backoffice pages
TaxBaik CI/CD / build-and-deploy (push) Successful in 1m31s
TaxBaik CI/CD / build-and-deploy (push) Successful in 1m31s
This commit is contained in:
@@ -21,116 +21,133 @@
|
|||||||
</MudText>
|
</MudText>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="OpenCreateDialog" StartIcon="@Icons.Material.Filled.Add">
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="PrepareCreate" StartIcon="@Icons.Material.Filled.Add" id="btn-add-contract">
|
||||||
새 계약 추가
|
새 계약 추가
|
||||||
</MudButton>
|
</MudButton>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<MudPaper Class="admin-surface" Elevation="0">
|
@if (contracts is null)
|
||||||
@if (contracts is null)
|
{
|
||||||
{
|
<MudProgressLinear Indeterminate="true" />
|
||||||
<MudProgressLinear Indeterminate="true" />
|
}
|
||||||
}
|
else
|
||||||
else if (contracts.Count == 0)
|
{
|
||||||
{
|
<MudGrid Spacing="2" Class="mt-2">
|
||||||
<MudAlert Severity="Severity.Info" Class="mt-4">
|
<!-- Left: Dense Grid List -->
|
||||||
<MudIcon Icon="@Icons.Material.Filled.Description" Class="me-2" />
|
<MudItem XS="12" MD="8">
|
||||||
계약이 없습니다.
|
@if (contracts.Count == 0)
|
||||||
</MudAlert>
|
{
|
||||||
}
|
<MudAlert Severity="Severity.Info">
|
||||||
else
|
<MudIcon Icon="@Icons.Material.Filled.Description" Class="me-2" />
|
||||||
{
|
계약이 없습니다.
|
||||||
<MudDataGrid T="Contract"
|
</MudAlert>
|
||||||
Items="@contracts"
|
}
|
||||||
Dense="true"
|
else
|
||||||
Hover="true"
|
{
|
||||||
Striped="true"
|
<MudDataGrid T="Contract"
|
||||||
Virtualize="true"
|
Items="@contracts"
|
||||||
RowsPerPage="30"
|
Dense="true"
|
||||||
Class="admin-grid">
|
Hover="true"
|
||||||
<Columns>
|
Striped="true"
|
||||||
<PropertyColumn Property="x => x.Id" Title="ID" Sortable="true" />
|
Virtualize="true"
|
||||||
<TemplateColumn Title="고객">
|
RowsPerPage="30"
|
||||||
<CellTemplate>
|
SelectedItem="@selectedContract"
|
||||||
@if (clientMap.TryGetValue(context.Item.ClientId, out var clientName))
|
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.ContractNumber" Title="계약번호" />
|
||||||
|
<PropertyColumn Property="x => x.ServiceType" Title="서비스 유형" />
|
||||||
|
<PropertyColumn Property="x => x.MonthlyFee" Title="월 수수료" Format="C" />
|
||||||
|
<TemplateColumn Title="계약기간">
|
||||||
|
<CellTemplate>
|
||||||
|
@context.Item.StartDate.ToString("yyyy-MM-dd")
|
||||||
|
@if (context.Item.EndDate.HasValue)
|
||||||
|
{
|
||||||
|
<span>~@context.Item.EndDate.Value.ToString("yyyy-MM-dd")</span>
|
||||||
|
}
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
<TemplateColumn Title="상태">
|
||||||
|
<CellTemplate>
|
||||||
|
@{
|
||||||
|
var isActive = !context.Item.EndDate.HasValue || context.Item.EndDate.Value >= DateTime.Today;
|
||||||
|
}
|
||||||
|
@if (isActive)
|
||||||
|
{
|
||||||
|
<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>
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Delete" Color="Color.Error" Size="Size.Small"
|
||||||
|
OnClick="@(async () => await DeleteContract(context.Item.Id))" />
|
||||||
|
</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="contractForm.ClientId" Label="고객" Required="true" Variant="Variant.Outlined" FullWidth="true" Class="mb-3" RequiredError="고객을 선택하세요." Disabled="@isEditMode">
|
||||||
|
@foreach (var client in clients)
|
||||||
{
|
{
|
||||||
<MudLink Href="@($"/taxbaik/admin/clients/{context.Item.ClientId}")" Color="Color.Primary">
|
<MudSelectItem Value="@((int?)client.Id)">@GetClientDisplayName(client)</MudSelectItem>
|
||||||
@clientName
|
|
||||||
</MudLink>
|
|
||||||
}
|
}
|
||||||
</CellTemplate>
|
</MudSelect>
|
||||||
</TemplateColumn>
|
<MudTextField T="string" @bind-Value="contractForm.ContractNumber" Label="계약번호" Variant="Variant.Outlined" FullWidth="true" Class="mb-3" Required="true" />
|
||||||
<PropertyColumn Property="x => x.ContractNumber" Title="계약번호" />
|
<MudSelect T="string" @bind-Value="contractForm.ServiceType" Label="서비스 유형" Variant="Variant.Outlined" FullWidth="true" Class="mb-3" Required="true">
|
||||||
<PropertyColumn Property="x => x.ServiceType" Title="서비스 유형" />
|
<MudSelectItem Value="@("개인 기장대리")">개인 기장대리</MudSelectItem>
|
||||||
<PropertyColumn Property="x => x.MonthlyFee" Title="월 수수료" Format="C" />
|
<MudSelectItem Value="@("법인 기장대리")">법인 기장대리</MudSelectItem>
|
||||||
<TemplateColumn Title="계약기간">
|
<MudSelectItem Value="@("세무조정 대행")">세무조정 대행</MudSelectItem>
|
||||||
<CellTemplate>
|
<MudSelectItem Value="@("양도세 신고대리")">양도세 신고대리</MudSelectItem>
|
||||||
@context.Item.StartDate.ToString("yyyy-MM-dd")
|
<MudSelectItem Value="@("상속·증여 자문")">상속·증여 자문</MudSelectItem>
|
||||||
@if (context.Item.EndDate.HasValue)
|
<MudSelectItem Value="@("세무조사 대응")">세무조사 대응</MudSelectItem>
|
||||||
|
</MudSelect>
|
||||||
|
<MudDatePicker @bind-Date="contractForm.StartDate" Label="계약 시작일" Variant="Variant.Outlined" FullWidth="true" Class="mb-3" Required="true" />
|
||||||
|
<MudNumericField T="decimal?" @bind-Value="contractForm.MonthlyFee" Label="월 수수료" Variant="Variant.Outlined" FullWidth="true" Class="mb-4" />
|
||||||
|
|
||||||
|
<div class="d-flex justify-end gap-2">
|
||||||
|
@if (isEditMode)
|
||||||
{
|
{
|
||||||
<span>~@context.Item.EndDate.Value.ToString("yyyy-MM-dd")</span>
|
<MudButton Variant="Variant.Outlined" Color="Color.Error" OnClick="@(async () => await DeleteContract(selectedContract?.Id ?? 0))">삭제</MudButton>
|
||||||
}
|
|
||||||
</CellTemplate>
|
|
||||||
</TemplateColumn>
|
|
||||||
<TemplateColumn Title="상태">
|
|
||||||
<CellTemplate>
|
|
||||||
@{
|
|
||||||
var isActive = !context.Item.EndDate.HasValue || context.Item.EndDate.Value >= DateTime.Today;
|
|
||||||
}
|
|
||||||
@if (isActive)
|
|
||||||
{
|
|
||||||
<MudChip Size="Size.Small" Color="Color.Success" Variant="Variant.Filled">활성</MudChip>
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<MudChip Size="Size.Small" Color="Color.Default" Variant="Variant.Outlined">만료</MudChip>
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="SaveContract" id="btn-save-contract">저장</MudButton>
|
||||||
}
|
}
|
||||||
</CellTemplate>
|
</div>
|
||||||
</TemplateColumn>
|
</MudForm>
|
||||||
<TemplateColumn Title="작업" Sortable="false">
|
</MudPaper>
|
||||||
<CellTemplate>
|
</MudItem>
|
||||||
<MudButtonGroup Size="Size.Small" Variant="Variant.Outlined">
|
</MudGrid>
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.Delete" Color="Color.Error"
|
}
|
||||||
OnClick="@(async () => await DeleteContract(context.Item.Id))" />
|
|
||||||
</MudButtonGroup>
|
|
||||||
</CellTemplate>
|
|
||||||
</TemplateColumn>
|
|
||||||
</Columns>
|
|
||||||
</MudDataGrid>
|
|
||||||
}
|
|
||||||
</MudPaper>
|
|
||||||
|
|
||||||
<!-- Create Dialog -->
|
|
||||||
<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="contractForm.ClientId" Label="고객" Required="true" Variant="Variant.Outlined" FullWidth="true" Class="mb-4" RequiredError="고객을 선택하세요.">
|
|
||||||
@foreach (var client in clients)
|
|
||||||
{
|
|
||||||
<MudSelectItem Value="@((int?)client.Id)">@GetClientDisplayName(client)</MudSelectItem>
|
|
||||||
}
|
|
||||||
</MudSelect>
|
|
||||||
<MudTextField T="string" @bind-Value="contractForm.ContractNumber" Label="계약번호" Variant="Variant.Outlined" FullWidth="true" Class="mb-4" Required="true" />
|
|
||||||
<MudSelect T="string" @bind-Value="contractForm.ServiceType" 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>
|
|
||||||
</MudSelect>
|
|
||||||
<MudDatePicker @bind-Date="contractForm.StartDate" Label="계약 시작일" Variant="Variant.Outlined" FullWidth="true" Class="mb-4" Required="true" />
|
|
||||||
<MudNumericField T="decimal?" @bind-Value="contractForm.MonthlyFee" Label="월 수수료" Variant="Variant.Outlined" FullWidth="true" Class="mb-4" />
|
|
||||||
</MudForm>
|
|
||||||
</DialogContent>
|
|
||||||
<DialogActions>
|
|
||||||
<MudButton OnClick="CloseDialog">취소</MudButton>
|
|
||||||
<MudButton Color="Color.Primary" OnClick="SaveContract">저장</MudButton>
|
|
||||||
</DialogActions>
|
|
||||||
</MudDialog>
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
@@ -141,7 +158,8 @@
|
|||||||
private Dictionary<int, string> clientMap = new();
|
private Dictionary<int, string> clientMap = new();
|
||||||
private decimal mrr = 0;
|
private decimal mrr = 0;
|
||||||
private MudForm? form;
|
private MudForm? form;
|
||||||
private bool isDialogOpen;
|
private bool isEditMode;
|
||||||
|
private Contract? selectedContract;
|
||||||
private ContractForm contractForm = new();
|
private ContractForm contractForm = new();
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
@@ -154,6 +172,7 @@
|
|||||||
if (authState.User.Identity?.IsAuthenticated == true)
|
if (authState.User.Identity?.IsAuthenticated == true)
|
||||||
{
|
{
|
||||||
await LoadData();
|
await LoadData();
|
||||||
|
PrepareCreate();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -176,14 +195,30 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OpenCreateDialog()
|
private void PrepareCreate()
|
||||||
{
|
{
|
||||||
|
selectedContract = null;
|
||||||
|
isEditMode = false;
|
||||||
contractForm = new ContractForm
|
contractForm = new ContractForm
|
||||||
{
|
{
|
||||||
ClientId = clients.FirstOrDefault()?.Id,
|
ClientId = clients.FirstOrDefault()?.Id,
|
||||||
StartDate = DateTime.Today
|
StartDate = DateTime.Today
|
||||||
};
|
};
|
||||||
isDialogOpen = true;
|
}
|
||||||
|
|
||||||
|
private void OnRowSelected(Contract contract)
|
||||||
|
{
|
||||||
|
if (contract == null) return;
|
||||||
|
selectedContract = contract;
|
||||||
|
isEditMode = true;
|
||||||
|
contractForm = new ContractForm
|
||||||
|
{
|
||||||
|
ClientId = contract.ClientId,
|
||||||
|
ContractNumber = contract.ContractNumber,
|
||||||
|
ServiceType = contract.ServiceType,
|
||||||
|
StartDate = contract.StartDate,
|
||||||
|
MonthlyFee = contract.MonthlyFee
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SaveContract()
|
private async Task SaveContract()
|
||||||
@@ -211,7 +246,7 @@
|
|||||||
if (newId > 0)
|
if (newId > 0)
|
||||||
{
|
{
|
||||||
Snackbar.Add("계약이 추가되었습니다.", Severity.Success);
|
Snackbar.Add("계약이 추가되었습니다.", Severity.Success);
|
||||||
CloseDialog();
|
PrepareCreate();
|
||||||
await LoadData();
|
await LoadData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -239,6 +274,10 @@
|
|||||||
{
|
{
|
||||||
await ContractClient.DeleteAsync(id);
|
await ContractClient.DeleteAsync(id);
|
||||||
Snackbar.Add("계약이 삭제되었습니다.", Severity.Success);
|
Snackbar.Add("계약이 삭제되었습니다.", Severity.Success);
|
||||||
|
if (selectedContract?.Id == id)
|
||||||
|
{
|
||||||
|
PrepareCreate();
|
||||||
|
}
|
||||||
await LoadData();
|
await LoadData();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -247,18 +286,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CloseDialog()
|
|
||||||
{
|
|
||||||
isDialogOpen = false;
|
|
||||||
contractForm = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetClientDisplayName(Client client)
|
private static string GetClientDisplayName(Client client)
|
||||||
=> !string.IsNullOrWhiteSpace(client.CompanyName)
|
=> !string.IsNullOrWhiteSpace(client.CompanyName)
|
||||||
? client.CompanyName
|
? client.CompanyName
|
||||||
: !string.IsNullOrWhiteSpace(client.Name)
|
: !string.IsNullOrWhiteSpace(client.Name)
|
||||||
? client.Name
|
? client.Name
|
||||||
: $"Client #{client.Id}";
|
: $"Client #{client.Id}";
|
||||||
|
|
||||||
private class ContractForm
|
private class ContractForm
|
||||||
{
|
{
|
||||||
public int? ClientId { get; set; }
|
public int? ClientId { get; set; }
|
||||||
|
|||||||
@@ -14,141 +14,163 @@
|
|||||||
<MudText Typo="Typo.h4" Class="admin-page-title">신고 일정</MudText>
|
<MudText Typo="Typo.h4" Class="admin-page-title">신고 일정</MudText>
|
||||||
<MudText Typo="Typo.body2" Class="admin-page-subtitle">고객별 마감일과 처리 상태를 한 화면에서 관리합니다.</MudText>
|
<MudText Typo="Typo.body2" Class="admin-page-subtitle">고객별 마감일과 처리 상태를 한 화면에서 관리합니다.</MudText>
|
||||||
</div>
|
</div>
|
||||||
<MudButton Variant="Variant.Filled"
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="PrepareCreate" StartIcon="@Icons.Material.Filled.Add" id="btn-add-schedule">
|
||||||
Color="Color.Primary"
|
|
||||||
OnClick="OpenCreateDialog"
|
|
||||||
StartIcon="@Icons.Material.Filled.Add">
|
|
||||||
새 일정 추가
|
새 일정 추가
|
||||||
</MudButton>
|
</MudButton>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<MudPaper Class="admin-surface" Elevation="0">
|
@if (schedules is null)
|
||||||
@if (schedules is null)
|
{
|
||||||
{
|
<MudProgressLinear Indeterminate="true" />
|
||||||
<MudProgressLinear Indeterminate="true" />
|
}
|
||||||
}
|
else
|
||||||
else if (schedules.Count == 0)
|
{
|
||||||
{
|
<MudGrid Spacing="2" Class="mt-2">
|
||||||
<MudAlert Severity="Severity.Info" Class="mt-4">
|
<!-- Left: Dense Grid List -->
|
||||||
<MudIcon Icon="@Icons.Material.Filled.EventBusy" Class="me-2" />
|
<MudItem XS="12" MD="8">
|
||||||
신고 일정이 없습니다.
|
@if (schedules.Count == 0)
|
||||||
</MudAlert>
|
{
|
||||||
}
|
<MudAlert Severity="Severity.Info">
|
||||||
else
|
<MudIcon Icon="@Icons.Material.Filled.EventBusy" Class="me-2" />
|
||||||
{
|
신고 일정이 없습니다.
|
||||||
<MudDataGrid T="TaxFilingSchedule"
|
</MudAlert>
|
||||||
Items="@schedules"
|
}
|
||||||
Dense="true"
|
else
|
||||||
Hover="true"
|
{
|
||||||
Striped="true"
|
<MudDataGrid T="TaxFilingSchedule"
|
||||||
Virtualize="true"
|
Items="@schedules"
|
||||||
RowsPerPage="30"
|
Dense="true"
|
||||||
Class="admin-grid">
|
Hover="true"
|
||||||
<Columns>
|
Striped="true"
|
||||||
<PropertyColumn Property="x => x.Id" Title="ID" Sortable="true" />
|
Virtualize="true"
|
||||||
<TemplateColumn Title="고객">
|
RowsPerPage="30"
|
||||||
<CellTemplate>
|
SelectedItem="@selectedSchedule"
|
||||||
@if (clientMap.TryGetValue(context.Item.ClientId, out var clientName))
|
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)
|
||||||
{
|
{
|
||||||
<MudLink Href="@($"/taxbaik/admin/clients/{context.Item.ClientId}")" Color="Color.Primary">
|
<MudSelectItem Value="@((int?)client.Id)">@GetClientDisplayName(client)</MudSelectItem>
|
||||||
@clientName
|
|
||||||
</MudLink>
|
|
||||||
}
|
}
|
||||||
</CellTemplate>
|
</MudSelect>
|
||||||
</TemplateColumn>
|
<MudSelect T="string" @bind-Value="scheduleForm.FilingType" Label="신고 유형" Variant="Variant.Outlined" FullWidth="true" Class="mb-3" Required="true">
|
||||||
<PropertyColumn Property="x => x.FilingType" Title="신고 유형" />
|
<MudSelectItem Value="@("종합소득세")">종합소득세</MudSelectItem>
|
||||||
<TemplateColumn Title="마감일">
|
<MudSelectItem Value="@("부가가치세")">부가가치세</MudSelectItem>
|
||||||
<CellTemplate>
|
<MudSelectItem Value="@("법인세")">법인세</MudSelectItem>
|
||||||
@{
|
<MudSelectItem Value="@("원천세")">원천세</MudSelectItem>
|
||||||
var daysLeft = (context.Item.DueDate.Date - DateTime.Today).Days;
|
<MudSelectItem Value="@("종합부동산세")">종합부동산세</MudSelectItem>
|
||||||
var statusColor = daysLeft < 0 ? Color.Error : daysLeft <= 7 ? Color.Warning : Color.Success;
|
<MudSelectItem Value="@("양도소득세")">양도소득세</MudSelectItem>
|
||||||
}
|
<MudSelectItem Value="@("상속·증여세")">상속·증여세</MudSelectItem>
|
||||||
<MudChip Size="Size.Small" Color="@statusColor" Variant="Variant.Filled">
|
<MudSelectItem Value="@("세무조정")">세무조정</MudSelectItem>
|
||||||
@context.Item.DueDate.ToString("yyyy-MM-dd")
|
</MudSelect>
|
||||||
@if (daysLeft >= 0)
|
<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" />
|
||||||
<span class="ms-1">(D-@daysLeft)</span>
|
|
||||||
}
|
<div class="d-flex justify-end gap-2">
|
||||||
else
|
@if (isEditMode && selectedSchedule?.Status != "completed")
|
||||||
{
|
|
||||||
<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>
|
<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
|
else
|
||||||
{
|
{
|
||||||
<MudChip Size="Size.Small" Color="Color.Default" Variant="Variant.Outlined">대기</MudChip>
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="SaveSchedule" id="btn-save-schedule">저장</MudButton>
|
||||||
}
|
}
|
||||||
</CellTemplate>
|
</div>
|
||||||
</TemplateColumn>
|
</MudForm>
|
||||||
<TemplateColumn Title="작업" Sortable="false">
|
</MudPaper>
|
||||||
<CellTemplate>
|
</MudItem>
|
||||||
<MudButtonGroup Size="Size.Small" Variant="Variant.Outlined">
|
</MudGrid>
|
||||||
@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>
|
|
||||||
}
|
|
||||||
</MudPaper>
|
|
||||||
|
|
||||||
<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="고객을 선택하세요.">
|
|
||||||
@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-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>
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
@@ -158,7 +180,8 @@
|
|||||||
private List<Client> clients = [];
|
private List<Client> clients = [];
|
||||||
private Dictionary<int, string> clientMap = new();
|
private Dictionary<int, string> clientMap = new();
|
||||||
private MudForm? form;
|
private MudForm? form;
|
||||||
private bool isDialogOpen;
|
private bool isEditMode;
|
||||||
|
private TaxFilingSchedule? selectedSchedule;
|
||||||
private TaxFilingScheduleForm scheduleForm = new();
|
private TaxFilingScheduleForm scheduleForm = new();
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
@@ -171,6 +194,7 @@
|
|||||||
if (authState.User.Identity?.IsAuthenticated == true)
|
if (authState.User.Identity?.IsAuthenticated == true)
|
||||||
{
|
{
|
||||||
await LoadData();
|
await LoadData();
|
||||||
|
PrepareCreate();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -192,15 +216,30 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OpenCreateDialog()
|
private void PrepareCreate()
|
||||||
{
|
{
|
||||||
|
selectedSchedule = null;
|
||||||
|
isEditMode = false;
|
||||||
scheduleForm = new TaxFilingScheduleForm
|
scheduleForm = new TaxFilingScheduleForm
|
||||||
{
|
{
|
||||||
FilingYear = DateTime.Now.Year,
|
FilingYear = DateTime.Now.Year,
|
||||||
DueDate = DateTime.Today,
|
DueDate = DateTime.Today,
|
||||||
ClientId = clients.FirstOrDefault()?.Id
|
ClientId = clients.FirstOrDefault()?.Id
|
||||||
};
|
};
|
||||||
isDialogOpen = true;
|
}
|
||||||
|
|
||||||
|
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()
|
private async Task SaveSchedule()
|
||||||
@@ -227,7 +266,7 @@
|
|||||||
if (newId > 0)
|
if (newId > 0)
|
||||||
{
|
{
|
||||||
Snackbar.Add("신고 일정이 추가되었습니다.", Severity.Success);
|
Snackbar.Add("신고 일정이 추가되었습니다.", Severity.Success);
|
||||||
CloseDialog();
|
PrepareCreate();
|
||||||
await LoadData();
|
await LoadData();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -247,6 +286,10 @@
|
|||||||
{
|
{
|
||||||
await TaxFilingClient.MarkCompletedAsync(id);
|
await TaxFilingClient.MarkCompletedAsync(id);
|
||||||
Snackbar.Add("신고 일정이 완료 처리되었습니다.", Severity.Success);
|
Snackbar.Add("신고 일정이 완료 처리되었습니다.", Severity.Success);
|
||||||
|
if (selectedSchedule?.Id == id)
|
||||||
|
{
|
||||||
|
PrepareCreate();
|
||||||
|
}
|
||||||
await LoadData();
|
await LoadData();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -272,6 +315,10 @@
|
|||||||
{
|
{
|
||||||
await TaxFilingClient.DeleteAsync(id);
|
await TaxFilingClient.DeleteAsync(id);
|
||||||
Snackbar.Add("신고 일정이 삭제되었습니다.", Severity.Success);
|
Snackbar.Add("신고 일정이 삭제되었습니다.", Severity.Success);
|
||||||
|
if (selectedSchedule?.Id == id)
|
||||||
|
{
|
||||||
|
PrepareCreate();
|
||||||
|
}
|
||||||
await LoadData();
|
await LoadData();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -280,18 +327,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CloseDialog()
|
|
||||||
{
|
|
||||||
isDialogOpen = false;
|
|
||||||
scheduleForm = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetClientDisplayName(Client client)
|
private static string GetClientDisplayName(Client client)
|
||||||
=> !string.IsNullOrWhiteSpace(client.CompanyName)
|
=> !string.IsNullOrWhiteSpace(client.CompanyName)
|
||||||
? client.CompanyName
|
? client.CompanyName
|
||||||
: !string.IsNullOrWhiteSpace(client.Name)
|
: !string.IsNullOrWhiteSpace(client.Name)
|
||||||
? client.Name
|
? client.Name
|
||||||
: $"Client #{client.Id}";
|
: $"Client #{client.Id}";
|
||||||
|
|
||||||
private class TaxFilingScheduleForm
|
private class TaxFilingScheduleForm
|
||||||
{
|
{
|
||||||
public int? ClientId { get; set; }
|
public int? ClientId { get; set; }
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<MudText Typo="Typo.h4" Class="admin-page-title">세무 프로필</MudText>
|
<MudText Typo="Typo.h4" Class="admin-page-title">세무 프로필</MudText>
|
||||||
<MudText Typo="Typo.body2" Class="admin-page-subtitle">고객별 세무 프로필, 신고 일정, 위험도 추적</MudText>
|
<MudText Typo="Typo.body2" Class="admin-page-subtitle">고객별 세무 프로필, 신고 일정, 위험도 추적</MudText>
|
||||||
</div>
|
</div>
|
||||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="OpenCreateDialog" StartIcon="@Icons.Material.Filled.Add">
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="PrepareCreate" StartIcon="@Icons.Material.Filled.Add" id="btn-add-profile">
|
||||||
새 프로필 추가
|
새 프로필 추가
|
||||||
</MudButton>
|
</MudButton>
|
||||||
</section>
|
</section>
|
||||||
@@ -24,94 +24,109 @@
|
|||||||
{
|
{
|
||||||
<MudProgressCircular Indeterminate="true" Class="mt-4" />
|
<MudProgressCircular Indeterminate="true" Class="mt-4" />
|
||||||
}
|
}
|
||||||
else if (profiles.Count == 0)
|
|
||||||
{
|
|
||||||
<MudAlert Severity="Severity.Info" Class="mt-4">세무 프로필이 없습니다.</MudAlert>
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<MudDataGrid T="TaxProfile"
|
<MudGrid Spacing="2" Class="mt-2">
|
||||||
Items="@profiles"
|
<!-- Left: Dense Grid List -->
|
||||||
Dense="true"
|
<MudItem XS="12" MD="8">
|
||||||
Hover="true"
|
@if (profiles.Count == 0)
|
||||||
Striped="true"
|
{
|
||||||
Virtualize="true"
|
<MudAlert Severity="Severity.Info">세무 프로필이 없습니다.</MudAlert>
|
||||||
RowsPerPage="30"
|
}
|
||||||
Class="admin-grid mt-4">
|
else
|
||||||
<Columns>
|
{
|
||||||
<PropertyColumn Property="x => x.Id" Title="ID" Sortable="true" />
|
<MudDataGrid T="TaxProfile"
|
||||||
<TemplateColumn Title="고객">
|
Items="@profiles"
|
||||||
<CellTemplate>
|
Dense="true"
|
||||||
@if (clientMap.TryGetValue(context.Item.ClientId, out var clientName))
|
Hover="true"
|
||||||
{
|
Striped="true"
|
||||||
<MudLink Href="@($"/taxbaik/admin/clients/{context.Item.ClientId}")" Color="Color.Primary">
|
Virtualize="true"
|
||||||
@clientName
|
RowsPerPage="30"
|
||||||
</MudLink>
|
SelectedItem="@selectedProfile"
|
||||||
}
|
SelectedItemChanged="OnRowSelected"
|
||||||
</CellTemplate>
|
Class="admin-grid">
|
||||||
</TemplateColumn>
|
<Columns>
|
||||||
<PropertyColumn Property="x => x.BusinessType" Title="사업 유형" />
|
<PropertyColumn Property="x => x.Id" Title="ID" Sortable="true" />
|
||||||
<TemplateColumn Title="위험도">
|
<TemplateColumn Title="고객">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
<MudChip Size="Size.Small" Color="@GetRiskColor(context.Item.TaxRiskLevel)" Variant="Variant.Filled">
|
@if (clientMap.TryGetValue(context.Item.ClientId, out var clientName))
|
||||||
@context.Item.TaxRiskLevel
|
{
|
||||||
</MudChip>
|
@clientName
|
||||||
</CellTemplate>
|
}
|
||||||
</TemplateColumn>
|
</CellTemplate>
|
||||||
<TemplateColumn Title="다음 신고">
|
</TemplateColumn>
|
||||||
<CellTemplate>
|
<PropertyColumn Property="x => x.BusinessType" Title="사업 유형" />
|
||||||
@if (context.Item.NextFilingDueDate.HasValue)
|
<TemplateColumn Title="위험도">
|
||||||
{
|
<CellTemplate>
|
||||||
@context.Item.NextFilingDueDate.Value.ToString("yyyy-MM-dd")
|
<MudChip Size="Size.Small" Color="@GetRiskColor(context.Item.TaxRiskLevel)" Variant="Variant.Filled">
|
||||||
}
|
@context.Item.TaxRiskLevel
|
||||||
</CellTemplate>
|
</MudChip>
|
||||||
</TemplateColumn>
|
</CellTemplate>
|
||||||
<TemplateColumn Title="작업" Sortable="false">
|
</TemplateColumn>
|
||||||
<CellTemplate>
|
<TemplateColumn Title="다음 신고">
|
||||||
<MudButtonGroup Size="Size.Small" Variant="Variant.Outlined">
|
<CellTemplate>
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.Edit" OnClick="@(async () => await OpenEditDialog(context.Item))" />
|
@if (context.Item.NextFilingDueDate.HasValue)
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.Delete" Color="Color.Error" OnClick="@(async () => await DeleteProfile(context.Item.Id))" />
|
{
|
||||||
</MudButtonGroup>
|
@context.Item.NextFilingDueDate.Value.ToString("yyyy-MM-dd")
|
||||||
</CellTemplate>
|
}
|
||||||
</TemplateColumn>
|
</CellTemplate>
|
||||||
</Columns>
|
</TemplateColumn>
|
||||||
</MudDataGrid>
|
<TemplateColumn Title="작업" Sortable="false">
|
||||||
}
|
<CellTemplate>
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Delete" Color="Color.Error" Size="Size.Small" OnClick="@(async () => await DeleteProfile(context.Item.Id))" />
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
</Columns>
|
||||||
|
</MudDataGrid>
|
||||||
|
}
|
||||||
|
</MudItem>
|
||||||
|
|
||||||
<!-- Create/Edit Dialog -->
|
<!-- Right: Detail Form Panel (Inline Editor) -->
|
||||||
<MudDialog @bind-IsVisible="isDialogOpen" Options="new DialogOptions { MaxWidth = MaxWidth.Small, FullWidth = true }">
|
<MudItem XS="12" MD="4">
|
||||||
<TitleContent>
|
<MudPaper Class="pa-4 admin-surface admin-editor-panel" Elevation="0" Style="border: 1px solid var(--border-color); min-height: 400px;">
|
||||||
<MudText Typo="Typo.h6">@(isEditMode ? "세무 프로필 수정" : "새 세무 프로필 추가")</MudText>
|
<div class="d-flex align-center justify-space-between mb-4">
|
||||||
</TitleContent>
|
<MudText Typo="Typo.h6" Class="font-weight-bold">@(isEditMode ? "세무 프로필 수정" : "새 세무 프로필 추가")</MudText>
|
||||||
<DialogContent>
|
@if (isEditMode)
|
||||||
<MudForm @ref="form">
|
{
|
||||||
<MudSelect T="int?" @bind-Value="profileForm.ClientId" Label="고객" Required="true" Variant="Variant.Outlined" FullWidth="true" Class="mb-4" RequiredError="고객을 선택하세요.">
|
<MudButton Size="Size.Small" Variant="Variant.Outlined" Color="Color.Secondary" OnClick="PrepareCreate">
|
||||||
@foreach (var client in clients)
|
새로 작성
|
||||||
{
|
</MudButton>
|
||||||
<MudSelectItem Value="@((int?)client.Id)">@GetClientDisplayName(client)</MudSelectItem>
|
}
|
||||||
}
|
</div>
|
||||||
</MudSelect>
|
<MudForm @ref="form">
|
||||||
<MudSelect T="string" @bind-Value="profileForm.BusinessType" Label="사업 유형" Variant="Variant.Outlined" FullWidth="true" Class="mb-4" Required="true">
|
<MudSelect T="int?" @bind-Value="profileForm.ClientId" Label="고객" Required="true" Variant="Variant.Outlined" FullWidth="true" Class="mb-3" RequiredError="고객을 선택하세요." Disabled="@isEditMode">
|
||||||
@foreach (var type in businessTypes)
|
@foreach (var client in clients)
|
||||||
{
|
{
|
||||||
<MudSelectItem Value="@type.CodeValue">@type.CodeName</MudSelectItem>
|
<MudSelectItem Value="@((int?)client.Id)">@GetClientDisplayName(client)</MudSelectItem>
|
||||||
}
|
}
|
||||||
</MudSelect>
|
</MudSelect>
|
||||||
<MudSelect T="string" @bind-Value="profileForm.TaxRiskLevel" Label="위험도" Variant="Variant.Outlined" FullWidth="true" Class="mb-4">
|
<MudSelect T="string" @bind-Value="profileForm.BusinessType" Label="사업 유형" Variant="Variant.Outlined" FullWidth="true" Class="mb-3" Required="true">
|
||||||
@foreach (var level in riskLevels)
|
@foreach (var type in businessTypes)
|
||||||
{
|
{
|
||||||
<MudSelectItem Value="@level.CodeValue">@level.CodeName</MudSelectItem>
|
<MudSelectItem Value="@type.CodeValue">@type.CodeName</MudSelectItem>
|
||||||
}
|
}
|
||||||
</MudSelect>
|
</MudSelect>
|
||||||
<MudDatePicker @bind-Date="profileForm.NextFilingDueDate" Label="다음 신고 예정일" Variant="Variant.Outlined" FullWidth="true" Class="mb-4" />
|
<MudSelect T="string" @bind-Value="profileForm.TaxRiskLevel" Label="위험도" Variant="Variant.Outlined" FullWidth="true" Class="mb-3">
|
||||||
<MudTextField T="string" @bind-Value="profileForm.SpecialNotes" Label="특수 사항" Variant="Variant.Outlined" FullWidth="true" Lines="2" />
|
@foreach (var level in riskLevels)
|
||||||
</MudForm>
|
{
|
||||||
</DialogContent>
|
<MudSelectItem Value="@level.CodeValue">@level.CodeName</MudSelectItem>
|
||||||
<DialogActions>
|
}
|
||||||
<MudButton OnClick="CloseDialog">취소</MudButton>
|
</MudSelect>
|
||||||
<MudButton Color="Color.Primary" OnClick="SaveProfile">저장</MudButton>
|
<MudDatePicker @bind-Date="profileForm.NextFilingDueDate" Label="다음 신고 예정일" Variant="Variant.Outlined" FullWidth="true" Class="mb-3" />
|
||||||
</DialogActions>
|
<MudTextField T="string" @bind-Value="profileForm.SpecialNotes" Label="특수 사항" Variant="Variant.Outlined" FullWidth="true" Lines="3" Class="mb-4" />
|
||||||
</MudDialog>
|
|
||||||
|
<div class="d-flex justify-end gap-2">
|
||||||
|
@if (isEditMode)
|
||||||
|
{
|
||||||
|
<MudButton Variant="Variant.Outlined" Color="Color.Error" OnClick="@(async () => await DeleteProfile(selectedProfile?.Id ?? 0))">삭제</MudButton>
|
||||||
|
}
|
||||||
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="SaveProfile" id="btn-save-profile">저장</MudButton>
|
||||||
|
</div>
|
||||||
|
</MudForm>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
</MudGrid>
|
||||||
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
@@ -123,9 +138,8 @@ else
|
|||||||
private List<CommonCode> businessTypes = [];
|
private List<CommonCode> businessTypes = [];
|
||||||
private List<CommonCode> riskLevels = [];
|
private List<CommonCode> riskLevels = [];
|
||||||
private MudForm? form;
|
private MudForm? form;
|
||||||
private bool isDialogOpen;
|
|
||||||
private bool isEditMode;
|
private bool isEditMode;
|
||||||
private TaxProfile? editingProfile;
|
private TaxProfile? selectedProfile;
|
||||||
private TaxProfileForm profileForm = new();
|
private TaxProfileForm profileForm = new();
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
@@ -138,6 +152,7 @@ else
|
|||||||
if (authState.User.Identity?.IsAuthenticated == true)
|
if (authState.User.Identity?.IsAuthenticated == true)
|
||||||
{
|
{
|
||||||
await LoadData();
|
await LoadData();
|
||||||
|
PrepareCreate();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -185,23 +200,23 @@ else
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OpenCreateDialog()
|
private void PrepareCreate()
|
||||||
{
|
{
|
||||||
|
selectedProfile = null;
|
||||||
isEditMode = false;
|
isEditMode = false;
|
||||||
editingProfile = null;
|
|
||||||
profileForm = new TaxProfileForm
|
profileForm = new TaxProfileForm
|
||||||
{
|
{
|
||||||
ClientId = clients.FirstOrDefault()?.Id,
|
ClientId = clients.FirstOrDefault()?.Id,
|
||||||
TaxRiskLevel = "normal",
|
TaxRiskLevel = "normal",
|
||||||
NextFilingDueDate = DateTime.Today.AddMonths(1)
|
NextFilingDueDate = DateTime.Today.AddMonths(1)
|
||||||
};
|
};
|
||||||
isDialogOpen = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task OpenEditDialog(TaxProfile profile)
|
private void OnRowSelected(TaxProfile profile)
|
||||||
{
|
{
|
||||||
|
if (profile == null) return;
|
||||||
|
selectedProfile = profile;
|
||||||
isEditMode = true;
|
isEditMode = true;
|
||||||
editingProfile = profile;
|
|
||||||
profileForm = new TaxProfileForm
|
profileForm = new TaxProfileForm
|
||||||
{
|
{
|
||||||
ClientId = profile.ClientId,
|
ClientId = profile.ClientId,
|
||||||
@@ -210,7 +225,6 @@ else
|
|||||||
NextFilingDueDate = profile.NextFilingDueDate,
|
NextFilingDueDate = profile.NextFilingDueDate,
|
||||||
SpecialNotes = profile.SpecialNotes
|
SpecialNotes = profile.SpecialNotes
|
||||||
};
|
};
|
||||||
isDialogOpen = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SaveProfile()
|
private async Task SaveProfile()
|
||||||
@@ -220,16 +234,16 @@ else
|
|||||||
await form.Validate();
|
await form.Validate();
|
||||||
if (!form.IsValid)
|
if (!form.IsValid)
|
||||||
{
|
{
|
||||||
Snackbar.Add("고객을 선택하세요.", Severity.Warning);
|
Snackbar.Add("고객을 선택하세요.", Severity.Warning);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (isEditMode && editingProfile != null)
|
if (isEditMode && selectedProfile != null)
|
||||||
{
|
{
|
||||||
await TaxProfileClient.UpdateAsync(editingProfile.Id, profileForm.BusinessType,
|
await TaxProfileClient.UpdateAsync(selectedProfile.Id, profileForm.BusinessType,
|
||||||
null, profileForm.NextFilingDueDate, profileForm.TaxRiskLevel);
|
null, profileForm.NextFilingDueDate, profileForm.TaxRiskLevel);
|
||||||
Snackbar.Add("세무 프로필이 수정되었습니다.", Severity.Success);
|
Snackbar.Add("세무 프로필이 수정되었습니다.", Severity.Success);
|
||||||
}
|
}
|
||||||
@@ -245,7 +259,6 @@ else
|
|||||||
profileForm.BusinessType);
|
profileForm.BusinessType);
|
||||||
if (newId > 0)
|
if (newId > 0)
|
||||||
{
|
{
|
||||||
// 생성 후 상태 업데이트 처리
|
|
||||||
await TaxProfileClient.UpdateAsync(
|
await TaxProfileClient.UpdateAsync(
|
||||||
newId,
|
newId,
|
||||||
profileForm.BusinessType,
|
profileForm.BusinessType,
|
||||||
@@ -255,7 +268,7 @@ else
|
|||||||
Snackbar.Add("세무 프로필이 추가되었습니다.", Severity.Success);
|
Snackbar.Add("세무 프로필이 추가되었습니다.", Severity.Success);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CloseDialog();
|
PrepareCreate();
|
||||||
await LoadData();
|
await LoadData();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -280,6 +293,10 @@ else
|
|||||||
{
|
{
|
||||||
await TaxProfileClient.DeleteAsync(id);
|
await TaxProfileClient.DeleteAsync(id);
|
||||||
Snackbar.Add("세무 프로필이 삭제되었습니다.", Severity.Success);
|
Snackbar.Add("세무 프로필이 삭제되었습니다.", Severity.Success);
|
||||||
|
if (selectedProfile?.Id == id)
|
||||||
|
{
|
||||||
|
PrepareCreate();
|
||||||
|
}
|
||||||
await LoadData();
|
await LoadData();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -288,14 +305,6 @@ else
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CloseDialog()
|
|
||||||
{
|
|
||||||
isDialogOpen = false;
|
|
||||||
isEditMode = false;
|
|
||||||
editingProfile = null;
|
|
||||||
profileForm = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Color GetRiskColor(string riskLevel) => riskLevel switch
|
private Color GetRiskColor(string riskLevel) => riskLevel switch
|
||||||
{
|
{
|
||||||
"high" => Color.Error,
|
"high" => Color.Error,
|
||||||
@@ -310,6 +319,7 @@ else
|
|||||||
: !string.IsNullOrWhiteSpace(client.Name)
|
: !string.IsNullOrWhiteSpace(client.Name)
|
||||||
? client.Name
|
? client.Name
|
||||||
: $"Client #{client.Id}";
|
: $"Client #{client.Id}";
|
||||||
|
|
||||||
private class TaxProfileForm
|
private class TaxProfileForm
|
||||||
{
|
{
|
||||||
public int? ClientId { get; set; }
|
public int? ClientId { get; set; }
|
||||||
|
|||||||
@@ -90,14 +90,13 @@ test.describe('admin CRM pages', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('TaxProfiles modal dialog opens on add button click', async ({ page }) => {
|
test('TaxProfiles editor panel is visible on add button click', async ({ page }) => {
|
||||||
await navigateInBlazor(page, `${baseUrl}/admin/tax-profiles`);
|
await navigateInBlazor(page, `${baseUrl}/admin/tax-profiles`);
|
||||||
|
|
||||||
const addButton = page.getByRole('button', { name: /새 프로필 추가/ });
|
const addButton = page.getByRole('button', { name: /새 프로필 추가/ });
|
||||||
await expect(addButton).toBeVisible();
|
await expect(addButton).toBeVisible();
|
||||||
await addButton.click();
|
await addButton.click();
|
||||||
await expect(page).toHaveURL(/\/taxbaik\/admin\/tax-profiles$/);
|
await expect(page.locator('.admin-editor-panel')).toBeVisible({ timeout: 5000 });
|
||||||
await expect(addButton).toBeVisible();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('No console errors on CRM page navigation', async ({ page }) => {
|
test('No console errors on CRM page navigation', async ({ page }) => {
|
||||||
@@ -131,11 +130,11 @@ test.describe('admin CRM pages', () => {
|
|||||||
const addButton = page.getByRole('button', { name: /새 프로필 추가/ });
|
const addButton = page.getByRole('button', { name: /새 프로필 추가/ });
|
||||||
await addButton.click();
|
await addButton.click();
|
||||||
|
|
||||||
// 대화상자(MudDialog) 자체의 노출 대기
|
// 분할 편집기(admin-editor-panel) 노출 대기
|
||||||
await expect(page.locator('.mud-dialog')).toBeVisible({ timeout: 5000 });
|
await expect(page.locator('.admin-editor-panel')).toBeVisible({ timeout: 5000 });
|
||||||
|
|
||||||
// mud-select 내의 input 클릭 (이벤트 핸들러 격발 유도)
|
// mud-select 내의 input 클릭 (이벤트 핸들러 격발 유도)
|
||||||
const select = page.locator('.mud-dialog .mud-select').filter({ hasText: '사업 유형' }).first();
|
const select = page.locator('.admin-editor-panel .mud-select').filter({ hasText: '사업 유형' }).first();
|
||||||
await page.waitForTimeout(500);
|
await page.waitForTimeout(500);
|
||||||
await select.locator('input').click();
|
await select.locator('input').click();
|
||||||
|
|
||||||
@@ -153,9 +152,9 @@ test.describe('admin CRM pages', () => {
|
|||||||
const addButton = page.getByRole('button', { name: /새 일정 추가/ });
|
const addButton = page.getByRole('button', { name: /새 일정 추가/ });
|
||||||
await addButton.click();
|
await addButton.click();
|
||||||
|
|
||||||
await expect(page.locator('.mud-dialog')).toBeVisible({ timeout: 5000 });
|
await expect(page.locator('.admin-editor-panel')).toBeVisible({ timeout: 5000 });
|
||||||
|
|
||||||
const select = page.locator('.mud-dialog .mud-select').filter({ hasText: '신고 유형' }).first();
|
const select = page.locator('.admin-editor-panel .mud-select').filter({ hasText: '신고 유형' }).first();
|
||||||
await page.waitForTimeout(500);
|
await page.waitForTimeout(500);
|
||||||
await select.locator('input').click();
|
await select.locator('input').click();
|
||||||
|
|
||||||
@@ -171,9 +170,9 @@ test.describe('admin CRM pages', () => {
|
|||||||
const addButton = page.getByRole('button', { name: /새 계약 추가/ });
|
const addButton = page.getByRole('button', { name: /새 계약 추가/ });
|
||||||
await addButton.click();
|
await addButton.click();
|
||||||
|
|
||||||
await expect(page.locator('.mud-dialog')).toBeVisible({ timeout: 5000 });
|
await expect(page.locator('.admin-editor-panel')).toBeVisible({ timeout: 5000 });
|
||||||
|
|
||||||
const select = page.locator('.mud-dialog .mud-select').filter({ hasText: '서비스 유형' }).first();
|
const select = page.locator('.admin-editor-panel .mud-select').filter({ hasText: '서비스 유형' }).first();
|
||||||
await page.waitForTimeout(500);
|
await page.waitForTimeout(500);
|
||||||
await select.locator('input').click();
|
await select.locator('input').click();
|
||||||
|
|
||||||
@@ -181,4 +180,5 @@ test.describe('admin CRM pages', () => {
|
|||||||
await expect(popover.getByText('개인 기장대리')).toBeVisible({ timeout: 5000 });
|
await expect(popover.getByText('개인 기장대리')).toBeVisible({ timeout: 5000 });
|
||||||
await expect(popover.getByText('법인 기장대리')).toBeVisible({ timeout: 5000 });
|
await expect(popover.getByText('법인 기장대리')).toBeVisible({ timeout: 5000 });
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user