diff --git a/TaxBaik.Web/Components/Admin/Pages/ConsultingActivities.razor b/TaxBaik.Web/Components/Admin/Pages/ConsultingActivities.razor new file mode 100644 index 0000000..786e962 --- /dev/null +++ b/TaxBaik.Web/Components/Admin/Pages/ConsultingActivities.razor @@ -0,0 +1,258 @@ +@page "/admin/consulting-activities" +@using TaxBaik.Web.Services.AdminClients +@inject IConsultingActivityBrowserClient ActivityClient +@inject IClientBrowserClient ClientClient +@inject ISnackbar Snackbar +@inject IDialogService DialogService +@attribute [Authorize] + +상담 활동 관리 + +
+
+ 상담 활동 관리 + + 새 활동 기록 + +
+ + @if (activities == null) + { + + } + else if (activities.Count == 0) + { + 상담 활동이 없습니다. + } + else + { + + + + + + @if (clientMap.TryGetValue(context.Item.ClientId, out var clientName)) + { + + @clientName + + } + + + + + + + @{ + var desc = context.Item.Description ?? ""; + if (desc.Length > 30) desc = desc.Substring(0, 30) + "..."; + } + @desc + + + + + @if (context.Item.NextFollowupDate.HasValue) + { + var daysLeft = (context.Item.NextFollowupDate.Value.Date - DateTime.Today).Days; + + @context.Item.NextFollowupDate.Value.ToString("yyyy-MM-dd") + + } + + + + + + + + + + + + + } +
+ + + + + @(editingActivity == null ? "새 활동 기록" : "활동 기록 수정") + + + + + @foreach (var client in clients) + { + @client.CompanyName + } + + + + + + + + + 취소 + 저장 + + + +@code { + private List? activities; + private List clients = []; + private Dictionary clientMap = new(); + private MudForm? form; + private bool isDialogOpen; + private ConsultingActivity? editingActivity; + private ConsultingActivityForm activityForm = new(); + + protected override async Task OnInitializedAsync() + { + await LoadData(); + } + + private async Task LoadData() + { + try + { + activities = await ActivityClient.GetAllAsync(); + var (clientItems, _) = await ClientClient.GetPagedAsync(); + clients = clientItems.ToList(); + clientMap = clients.ToDictionary(c => c.Id, c => c.CompanyName ?? ""); + } + catch (Exception ex) + { + Snackbar.Add($"데이터 로드 실패: {ex.Message}", Severity.Error); + } + } + + private void OpenCreateDialog() + { + editingActivity = null; + activityForm = new ConsultingActivityForm { ActivityDate = DateTime.Now }; + isDialogOpen = true; + } + + private async Task OpenEditDialog(ConsultingActivity activity) + { + editingActivity = activity; + activityForm = new ConsultingActivityForm + { + ClientId = activity.ClientId, + ActivityType = activity.ActivityType, + ActivityDate = activity.ActivityDate, + Description = activity.Description, + NextFollowupDate = activity.NextFollowupDate + }; + isDialogOpen = true; + } + + private async Task SaveActivity() + { + try + { + if (editingActivity == null) + { + var actDate = activityForm.ActivityDate ?? DateTime.Now; + var newId = await ActivityClient.CreateAsync( + activityForm.ClientId, + activityForm.ActivityType, + actDate, + activityForm.Description, + null, + activityForm.NextFollowupDate); + + if (newId > 0) + { + Snackbar.Add("활동이 기록되었습니다.", Severity.Success); + CloseDialog(); + await LoadData(); + } + } + else + { + await ActivityClient.UpdateAsync( + editingActivity.Id, + null, + activityForm.NextFollowupDate); + + Snackbar.Add("활동이 업데이트되었습니다.", Severity.Success); + CloseDialog(); + await LoadData(); + } + } + catch (Exception ex) + { + Snackbar.Add($"저장 실패: {ex.Message}", Severity.Error); + } + } + + private async Task DeleteActivity(int id) + { + var parameters = new DialogParameters(); + parameters.Add("Title", "삭제 확인"); + parameters.Add("Message", "이 활동을 삭제하시겠습니까?"); + + var dialog = await DialogService.ShowAsync("", parameters); + var result = await dialog.Result; + + if (result?.Canceled ?? true) + return; + + try + { + await ActivityClient.DeleteAsync(id); + Snackbar.Add("활동이 삭제되었습니다.", Severity.Success); + await LoadData(); + } + catch (Exception ex) + { + Snackbar.Add($"삭제 실패: {ex.Message}", Severity.Error); + } + } + + private void CloseDialog() + { + isDialogOpen = false; + editingActivity = null; + activityForm = new(); + } + + private class ConsultingActivityForm + { + public int ClientId { get; set; } + public string ActivityType { get; set; } = ""; + public DateTime? ActivityDate { get; set; } = DateTime.Now; + public string Description { get; set; } = ""; + public DateTime? NextFollowupDate { get; set; } + } +} + + diff --git a/TaxBaik.Web/Components/Admin/Pages/Contracts.razor b/TaxBaik.Web/Components/Admin/Pages/Contracts.razor new file mode 100644 index 0000000..7e605a0 --- /dev/null +++ b/TaxBaik.Web/Components/Admin/Pages/Contracts.razor @@ -0,0 +1,237 @@ +@page "/admin/contracts" +@using TaxBaik.Web.Services.AdminClients +@inject IContractBrowserClient ContractClient +@inject IClientBrowserClient ClientClient +@inject ISnackbar Snackbar +@inject IDialogService DialogService +@attribute [Authorize] + +계약 관리 + +
+
+
+ 계약 관리 + @if (mrr > 0) + { + + 월 정기수익: ₩@mrr.ToString("N0") + + } +
+ + 새 계약 추가 + +
+ + @if (contracts == 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 + { + 만료 + } + + + + + + + + + + + + } +
+ + + + + 새 계약 추가 + + + + + @foreach (var client in clients) + { + @client.CompanyName + } + + + + + + + + + 취소 + 저장 + + + +@code { + private List? contracts; + private List clients = []; + private Dictionary clientMap = new(); + private decimal mrr = 0; + private MudForm? form; + private bool isDialogOpen; + private ContractForm contractForm = new(); + + protected override async Task OnInitializedAsync() + { + await LoadData(); + } + + private async Task LoadData() + { + try + { + contracts = await ContractClient.GetAllAsync(); + var (clientItems, _) = await ClientClient.GetPagedAsync(); + clients = clientItems.ToList(); + clientMap = clients.ToDictionary(c => c.Id, c => c.CompanyName ?? ""); + mrr = await ContractClient.GetMonthlyRecurringRevenueAsync(); + } + catch (Exception ex) + { + Snackbar.Add($"데이터 로드 실패: {ex.Message}", Severity.Error); + } + } + + private void OpenCreateDialog() + { + contractForm = new(); + isDialogOpen = true; + } + + private async Task SaveContract() + { + try + { + var newId = await ContractClient.CreateAsync( + contractForm.ClientId, + contractForm.ContractNumber, + contractForm.ServiceType, + contractForm.StartDate ?? DateTime.Now, + contractForm.MonthlyFee); + + if (newId > 0) + { + Snackbar.Add("계약이 추가되었습니다.", Severity.Success); + CloseDialog(); + await LoadData(); + } + } + catch (Exception ex) + { + Snackbar.Add($"저장 실패: {ex.Message}", Severity.Error); + } + } + + private async Task DeleteContract(int id) + { + var parameters = new DialogParameters(); + parameters.Add("Title", "삭제 확인"); + parameters.Add("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); + await LoadData(); + } + catch (Exception ex) + { + Snackbar.Add($"삭제 실패: {ex.Message}", Severity.Error); + } + } + + private void CloseDialog() + { + isDialogOpen = false; + contractForm = new(); + } + + 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; } + } +} + + diff --git a/TaxBaik.Web/Components/Admin/Pages/RevenueTrackings.razor b/TaxBaik.Web/Components/Admin/Pages/RevenueTrackings.razor new file mode 100644 index 0000000..c1410cf --- /dev/null +++ b/TaxBaik.Web/Components/Admin/Pages/RevenueTrackings.razor @@ -0,0 +1,237 @@ +@page "/admin/revenue-trackings" +@using TaxBaik.Web.Services.AdminClients +@inject IRevenueTrackingBrowserClient RevenueClient +@inject IClientBrowserClient ClientClient +@inject ISnackbar Snackbar +@inject IDialogService DialogService +@attribute [Authorize] + +수익 추적 관리 + +
+
+ 수익 추적 관리 + + 새 청구 추가 + +
+ + @if (revenues == null) + { + + } + else if (revenues.Count == 0) + { + 청구 기록이 없습니다. + } + else + { + + + + + + @if (clientMap.TryGetValue(context.Item.ClientId, out var clientName)) + { + + @clientName + + } + + + + + + + + @if (context.Item.PaymentStatus == "paid") + { + 납부 + } + else + { + 미납 + } + + + + + + @if (context.Item.PaymentStatus != "paid") + { + + } + + + + + + + } +
+ + + + + 새 청구 추가 + + + + + @foreach (var client in clients) + { + @client.CompanyName + } + + + + + + + + + + 취소 + 저장 + + + +@code { + private List? revenues; + private List clients = []; + private Dictionary clientMap = new(); + private MudForm? form; + private bool isDialogOpen; + private RevenueForm revenueForm = new(); + + protected override async Task OnInitializedAsync() + { + await LoadData(); + } + + private async Task LoadData() + { + try + { + revenues = await RevenueClient.GetAllAsync(); + var (clientItems, _) = await ClientClient.GetPagedAsync(); + clients = clientItems.ToList(); + clientMap = clients.ToDictionary(c => c.Id, c => c.CompanyName ?? ""); + } + catch (Exception ex) + { + Snackbar.Add($"데이터 로드 실패: {ex.Message}", Severity.Error); + } + } + + private void OpenCreateDialog() + { + revenueForm = new(); + isDialogOpen = true; + } + + private async Task SaveRevenue() + { + try + { + var newId = await RevenueClient.CreateAsync( + revenueForm.ClientId, + revenueForm.InvoiceNumber, + revenueForm.InvoiceDate ?? DateTime.Now, + revenueForm.Amount, + revenueForm.ServiceType, + revenueForm.DueDate); + + if (newId > 0) + { + Snackbar.Add("청구가 추가되었습니다.", Severity.Success); + CloseDialog(); + await LoadData(); + } + } + catch (Exception ex) + { + Snackbar.Add($"저장 실패: {ex.Message}", Severity.Error); + } + } + + private async Task MarkPaid(int id) + { + try + { + await RevenueClient.MarkPaidAsync(id, DateTime.Now); + Snackbar.Add("납부가 처리되었습니다.", Severity.Success); + await LoadData(); + } + catch (Exception ex) + { + Snackbar.Add($"처리 실패: {ex.Message}", Severity.Error); + } + } + + private async Task DeleteRevenue(int id) + { + var parameters = new DialogParameters(); + parameters.Add("Title", "삭제 확인"); + parameters.Add("Message", "이 청구를 삭제하시겠습니까?"); + + var dialog = await DialogService.ShowAsync("", parameters); + var result = await dialog.Result; + + if (result?.Canceled ?? true) + return; + + try + { + await RevenueClient.DeleteAsync(id); + Snackbar.Add("청구가 삭제되었습니다.", Severity.Success); + await LoadData(); + } + catch (Exception ex) + { + Snackbar.Add($"삭제 실패: {ex.Message}", Severity.Error); + } + } + + private void CloseDialog() + { + isDialogOpen = false; + revenueForm = new(); + } + + private class RevenueForm + { + public int ClientId { get; set; } + public string InvoiceNumber { get; set; } = ""; + public DateTime? InvoiceDate { get; set; } + public decimal Amount { get; set; } + public string? ServiceType { get; set; } + public DateTime? DueDate { get; set; } + } +} + + diff --git a/TaxBaik.Web/Components/Admin/Pages/TaxFilingSchedules.razor b/TaxBaik.Web/Components/Admin/Pages/TaxFilingSchedules.razor new file mode 100644 index 0000000..886686d --- /dev/null +++ b/TaxBaik.Web/Components/Admin/Pages/TaxFilingSchedules.razor @@ -0,0 +1,249 @@ +@page "/admin/tax-filing-schedules" +@using TaxBaik.Web.Services.AdminClients +@inject ITaxFilingScheduleBrowserClient TaxFilingClient +@inject IClientBrowserClient ClientClient +@inject ISnackbar Snackbar +@inject IDialogService DialogService +@attribute [Authorize] + +신고 일정 관리 + +
+
+ 신고 일정 관리 + + 새 일정 추가 + +
+ + @if (schedules == null) + { + + } + else if (schedules.Count == 0) + { + 신고 일정이 없습니다. + } + else + { + + + + + + @if (clientMap.TryGetValue(context.Item.ClientId, out var clientName)) + { + + @clientName + + } + + + + + + @{ + var daysLeft = (context.Item.DueDate.Date - DateTime.Today).Days; + var statusColor = daysLeft < 0 ? Color.Error : daysLeft <= 7 ? Color.Warning : Color.Success; + } + + @context.Item.DueDate.ToString("yyyy-MM-dd") + @if (daysLeft >= 0) { (D-@daysLeft) } + else { (마감@(Math.Abs(daysLeft))일경과) } + + + + + + + @if (context.Item.Status == "completed") + { + 완료 + } + else + { + 대기 + } + + + + + + @if (context.Item.Status != "completed") + { + + } + + + + + + + } +
+ + + + + @(editingSchedule == null ? "새 신고 일정 추가" : "신고 일정 수정") + + + + + @foreach (var client in clients) + { + @client.CompanyName + } + + + + + + + + 취소 + 저장 + + + +@code { + private List? schedules; + private List clients = []; + private Dictionary clientMap = new(); + private MudForm? form; + private bool isDialogOpen; + private TaxFilingSchedule? editingSchedule; + private TaxFilingScheduleForm scheduleForm = new(); + + protected override async Task OnInitializedAsync() + { + await LoadData(); + } + + private async Task LoadData() + { + try + { + schedules = await TaxFilingClient.GetAllAsync(); + var (clientItems, _) = await ClientClient.GetPagedAsync(); + clients = clientItems.ToList(); + clientMap = clients.ToDictionary(c => c.Id, c => c.CompanyName ?? ""); + } + catch (Exception ex) + { + Snackbar.Add($"데이터 로드 실패: {ex.Message}", Severity.Error); + } + } + + private void OpenCreateDialog() + { + editingSchedule = null; + scheduleForm = new(); + isDialogOpen = true; + } + + private async Task SaveSchedule() + { + try + { + if (editingSchedule == null) + { + var newId = await TaxFilingClient.CreateAsync( + scheduleForm.ClientId, + scheduleForm.FilingType, + scheduleForm.DueDate ?? DateTime.Now, + scheduleForm.FilingYear); + + if (newId > 0) + { + Snackbar.Add("신고 일정이 추가되었습니다.", Severity.Success); + CloseDialog(); + await LoadData(); + } + } + } + catch (Exception ex) + { + Snackbar.Add($"저장 실패: {ex.Message}", Severity.Error); + } + } + + private async Task CompleteSchedule(int id) + { + try + { + await TaxFilingClient.MarkCompletedAsync(id); + Snackbar.Add("신고 일정이 완료 처리되었습니다.", Severity.Success); + await LoadData(); + } + catch (Exception ex) + { + Snackbar.Add($"처리 실패: {ex.Message}", Severity.Error); + } + } + + private async Task DeleteSchedule(int id) + { + var parameters = new DialogParameters(); + parameters.Add("Title", "삭제 확인"); + parameters.Add("Message", "이 신고 일정을 삭제하시겠습니까?"); + + var dialog = await DialogService.ShowAsync("", parameters); + var result = await dialog.Result; + + if (result?.Canceled ?? true) + return; + + try + { + await TaxFilingClient.DeleteAsync(id); + Snackbar.Add("신고 일정이 삭제되었습니다.", Severity.Success); + await LoadData(); + } + catch (Exception ex) + { + Snackbar.Add($"삭제 실패: {ex.Message}", Severity.Error); + } + } + + private void CloseDialog() + { + isDialogOpen = false; + editingSchedule = null; + scheduleForm = new(); + } + + 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; + } +} + +