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>
|
||||
}
|
||||
</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>
|
||||
</section>
|
||||
|
||||
<MudPaper Class="admin-surface" Elevation="0">
|
||||
@if (contracts is null)
|
||||
{
|
||||
<MudProgressLinear Indeterminate="true" />
|
||||
}
|
||||
else if (contracts.Count == 0)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info" Class="mt-4">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Description" Class="me-2" />
|
||||
계약이 없습니다.
|
||||
</MudAlert>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudDataGrid T="Contract"
|
||||
Items="@contracts"
|
||||
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))
|
||||
@if (contracts is null)
|
||||
{
|
||||
<MudProgressLinear Indeterminate="true" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudGrid Spacing="2" Class="mt-2">
|
||||
<!-- Left: Dense Grid List -->
|
||||
<MudItem XS="12" MD="8">
|
||||
@if (contracts.Count == 0)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Description" Class="me-2" />
|
||||
계약이 없습니다.
|
||||
</MudAlert>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudDataGrid T="Contract"
|
||||
Items="@contracts"
|
||||
Dense="true"
|
||||
Hover="true"
|
||||
Striped="true"
|
||||
Virtualize="true"
|
||||
RowsPerPage="30"
|
||||
SelectedItem="@selectedContract"
|
||||
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">
|
||||
@clientName
|
||||
</MudLink>
|
||||
<MudSelectItem Value="@((int?)client.Id)">@GetClientDisplayName(client)</MudSelectItem>
|
||||
}
|
||||
</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)
|
||||
</MudSelect>
|
||||
<MudTextField T="string" @bind-Value="contractForm.ContractNumber" Label="계약번호" Variant="Variant.Outlined" FullWidth="true" Class="mb-3" Required="true" />
|
||||
<MudSelect T="string" @bind-Value="contractForm.ServiceType" 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>
|
||||
</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>
|
||||
}
|
||||
</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>
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Error" OnClick="@(async () => await DeleteContract(selectedContract?.Id ?? 0))">삭제</MudButton>
|
||||
}
|
||||
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>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="작업" Sortable="false">
|
||||
<CellTemplate>
|
||||
<MudButtonGroup Size="Size.Small" Variant="Variant.Outlined">
|
||||
<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>
|
||||
</div>
|
||||
</MudForm>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
}
|
||||
|
||||
@code {
|
||||
[CascadingParameter]
|
||||
@@ -141,7 +158,8 @@
|
||||
private Dictionary<int, string> clientMap = new();
|
||||
private decimal mrr = 0;
|
||||
private MudForm? form;
|
||||
private bool isDialogOpen;
|
||||
private bool isEditMode;
|
||||
private Contract? selectedContract;
|
||||
private ContractForm contractForm = new();
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
@@ -154,6 +172,7 @@
|
||||
if (authState.User.Identity?.IsAuthenticated == true)
|
||||
{
|
||||
await LoadData();
|
||||
PrepareCreate();
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
@@ -176,14 +195,30 @@
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenCreateDialog()
|
||||
private void PrepareCreate()
|
||||
{
|
||||
selectedContract = null;
|
||||
isEditMode = false;
|
||||
contractForm = new ContractForm
|
||||
{
|
||||
ClientId = clients.FirstOrDefault()?.Id,
|
||||
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()
|
||||
@@ -211,7 +246,7 @@
|
||||
if (newId > 0)
|
||||
{
|
||||
Snackbar.Add("계약이 추가되었습니다.", Severity.Success);
|
||||
CloseDialog();
|
||||
PrepareCreate();
|
||||
await LoadData();
|
||||
}
|
||||
}
|
||||
@@ -239,6 +274,10 @@
|
||||
{
|
||||
await ContractClient.DeleteAsync(id);
|
||||
Snackbar.Add("계약이 삭제되었습니다.", Severity.Success);
|
||||
if (selectedContract?.Id == id)
|
||||
{
|
||||
PrepareCreate();
|
||||
}
|
||||
await LoadData();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -247,18 +286,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseDialog()
|
||||
{
|
||||
isDialogOpen = false;
|
||||
contractForm = new();
|
||||
}
|
||||
|
||||
private static string GetClientDisplayName(Client client)
|
||||
=> !string.IsNullOrWhiteSpace(client.CompanyName)
|
||||
? client.CompanyName
|
||||
: !string.IsNullOrWhiteSpace(client.Name)
|
||||
? client.Name
|
||||
: $"Client #{client.Id}";
|
||||
|
||||
private class ContractForm
|
||||
{
|
||||
public int? ClientId { get; set; }
|
||||
|
||||
Reference in New Issue
Block a user