@page "/admin/contracts" @using TaxBaik.Web.Services.AdminClients @inject IContractBrowserClient ContractClient @inject IClientBrowserClient ClientClient @inject ISnackbar Snackbar @inject IDialogService DialogService @attribute [Authorize] 계약 관리 CRM & 세무관리 계약 관리 고객 계약과 월 정기수익을 함께 관리합니다. @if (mrr > 0) { 월 정기수익: ₩@mrr.ToString("N0") } 새 계약 추가 @if (contracts is null) { } else { @if (contracts.Count == 0) { 계약이 없습니다. } else { @if (clientMap.TryGetValue(context.Item.ClientId, out var clientName)) { @clientName } @context.Item.StartDate.ToString("yyyy-MM-dd") @if (context.Item.EndDate.HasValue) { ~@context.Item.EndDate.Value.ToString("yyyy-MM-dd") } @{ var isActive = !context.Item.EndDate.HasValue || context.Item.EndDate.Value >= DateTime.Today; } @if (isActive) { 활성 } else { 만료 } } @(isEditMode ? "계약 상세 정보" : "새 계약 추가") @if (isEditMode) { 새로 작성 } @foreach (var client in clients) { @GetClientDisplayName(client) } 개인 기장대리 법인 기장대리 세무조정 대행 양도세 신고대리 상속·증여 자문 세무조사 대응 @if (isEditMode) { 삭제 } else { 저장 } } @code { [CascadingParameter] private Task? AuthStateTask { get; set; } private List? contracts; private List clients = []; private Dictionary clientMap = new(); private decimal mrr = 0; private MudForm? form; private bool isEditMode; private Contract? selectedContract; private ContractForm contractForm = 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 { contracts = await ContractClient.GetAllAsync(); var (clientItems, _) = await ClientClient.GetPagedAsync(pageSize: 1000); clients = clientItems.ToList(); clientMap = clients.ToDictionary(c => c.Id, GetClientDisplayName); mrr = await ContractClient.GetMonthlyRecurringRevenueAsync(); } catch (Exception ex) { Snackbar.Add($"데이터 로드 실패: {ex.Message}", Severity.Error); } } private void PrepareCreate() { selectedContract = null; isEditMode = false; contractForm = new ContractForm { ClientId = clients.FirstOrDefault()?.Id, StartDate = DateTime.Today }; } 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() { if (form != null) { await form.Validate(); if (!form.IsValid) { Snackbar.Add("필수 항목을 입력해주세요.", Severity.Warning); return; } } try { if (contractForm.ClientId == null) return; var newId = await ContractClient.CreateAsync( contractForm.ClientId.Value, contractForm.ContractNumber, contractForm.ServiceType, contractForm.StartDate ?? DateTime.Now, contractForm.MonthlyFee); if (newId > 0) { Snackbar.Add("계약이 추가되었습니다.", Severity.Success); PrepareCreate(); await LoadData(); } } catch (Exception ex) { Snackbar.Add($"저장 실패: {ex.Message}", Severity.Error); } } private async Task DeleteContract(int id) { var parameters = new DialogParameters { { "Title", "삭제 확인" }, { "Message", "이 계약을 삭제하시겠습니까?" } }; var dialog = await DialogService.ShowAsync("", parameters); var result = await dialog.Result; if (result?.Canceled ?? true) return; try { await ContractClient.DeleteAsync(id); Snackbar.Add("계약이 삭제되었습니다.", Severity.Success); if (selectedContract?.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 ContractForm { public int? ClientId { get; set; } public string ContractNumber { get; set; } = ""; public string ServiceType { get; set; } = ""; public DateTime? StartDate { get; set; } public decimal? MonthlyFee { get; set; } } }