This commit is contained in:
@@ -5,7 +5,6 @@
|
|||||||
@inject IConsultingActivityBrowserClient ActivityClient
|
@inject IConsultingActivityBrowserClient ActivityClient
|
||||||
@inject IClientBrowserClient ClientClient
|
@inject IClientBrowserClient ClientClient
|
||||||
@inject ISnackbar Snackbar
|
@inject ISnackbar Snackbar
|
||||||
@inject IDialogService DialogService
|
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
|
|
||||||
<PageTitle>상담 활동 관리</PageTitle>
|
<PageTitle>상담 활동 관리</PageTitle>
|
||||||
@@ -14,11 +13,8 @@
|
|||||||
<div>
|
<div>
|
||||||
<MudText Typo="Typo.caption" Class="admin-eyebrow">CRM & 세무관리</MudText>
|
<MudText Typo="Typo.caption" Class="admin-eyebrow">CRM & 세무관리</MudText>
|
||||||
<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>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<AdminDataPanel Loading="@(activities is null)" SkeletonContent="@ActivitySkeleton">
|
<AdminDataPanel Loading="@(activities is null)" SkeletonContent="@ActivitySkeleton">
|
||||||
@@ -27,102 +23,36 @@
|
|||||||
}
|
}
|
||||||
else if (activities.Count == 0)
|
else if (activities.Count == 0)
|
||||||
{
|
{
|
||||||
<MudAlert Severity="Severity.Info" Class="mt-4">
|
<MudAlert Severity="Severity.Info" Class="mt-4">상담 활동이 없습니다.</MudAlert>
|
||||||
<MudIcon Icon="@Icons.Material.Filled.Timeline" Class="me-2" />
|
|
||||||
상담 활동이 없습니다.
|
|
||||||
</MudAlert>
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<MudDataGrid T="ConsultingActivity"
|
<table class="admin-grid table">
|
||||||
Items="@activities"
|
<thead>
|
||||||
Dense="true"
|
<tr>
|
||||||
Hover="true"
|
<th>ID</th>
|
||||||
Striped="true"
|
<th>고객</th>
|
||||||
|
<th>활동 유형</th>
|
||||||
RowsPerPage="30"
|
<th>활동일시</th>
|
||||||
Class="admin-grid">
|
<th>설명</th>
|
||||||
<Columns>
|
</tr>
|
||||||
<PropertyColumn Property="x => x.Id" Title="ID" Sortable="true" />
|
</thead>
|
||||||
<TemplateColumn Title="고객">
|
<tbody>
|
||||||
<CellTemplate>
|
@foreach (var item in activities)
|
||||||
@if (clientMap.TryGetValue(context.Item.ClientId, out var clientName))
|
{
|
||||||
{
|
<tr>
|
||||||
<MudLink Href="@($"/taxbaik/admin/clients/{context.Item.ClientId}")" Color="Color.Primary">
|
<td>@item.Id</td>
|
||||||
@clientName
|
<td>@clientMap.GetValueOrDefault(item.ClientId)</td>
|
||||||
</MudLink>
|
<td>@item.ActivityType</td>
|
||||||
}
|
<td>@item.ActivityDate.ToString("g")</td>
|
||||||
</CellTemplate>
|
<td>@item.Description</td>
|
||||||
</TemplateColumn>
|
</tr>
|
||||||
<PropertyColumn Property="x => x.ActivityType" Title="활동 유형" />
|
}
|
||||||
<PropertyColumn Property="x => x.ActivityDate" Title="활동일시" Format="g" />
|
</tbody>
|
||||||
<TemplateColumn Title="설명">
|
</table>
|
||||||
<CellTemplate>
|
|
||||||
@{
|
|
||||||
var desc = context.Item.Description ?? "";
|
|
||||||
if (desc.Length > 30) desc = desc.Substring(0, 30) + "...";
|
|
||||||
}
|
|
||||||
<span>@desc</span>
|
|
||||||
</CellTemplate>
|
|
||||||
</TemplateColumn>
|
|
||||||
<TemplateColumn Title="다음 팔로업">
|
|
||||||
<CellTemplate>
|
|
||||||
@if (context.Item.NextFollowupDate.HasValue)
|
|
||||||
{
|
|
||||||
var daysLeft = (context.Item.NextFollowupDate.Value.Date - DateTime.Today).Days;
|
|
||||||
<MudChip Size="Size.Small"
|
|
||||||
Color="@(daysLeft < 0 ? Color.Error : daysLeft <= 3 ? Color.Warning : Color.Success)"
|
|
||||||
Variant="Variant.Filled">
|
|
||||||
@context.Item.NextFollowupDate.Value.ToString("yyyy-MM-dd")
|
|
||||||
</MudChip>
|
|
||||||
}
|
|
||||||
</CellTemplate>
|
|
||||||
</TemplateColumn>
|
|
||||||
<TemplateColumn Title="작업" Sortable="false">
|
|
||||||
<CellTemplate>
|
|
||||||
<MudButtonGroup Size="Size.Small" Variant="Variant.Outlined">
|
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.Edit" OnClick="@(async () => await OpenEditDialog(context.Item))" />
|
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.Delete" Color="Color.Error"
|
|
||||||
OnClick="@(async () => await DeleteActivity(context.Item.Id))" />
|
|
||||||
</MudButtonGroup>
|
|
||||||
</CellTemplate>
|
|
||||||
</TemplateColumn>
|
|
||||||
</Columns>
|
|
||||||
</MudDataGrid>
|
|
||||||
}
|
}
|
||||||
</AdminDataPanel>
|
</AdminDataPanel>
|
||||||
|
|
||||||
<MudDialog @bind-IsVisible="isDialogOpen" Options="new DialogOptions { MaxWidth = MaxWidth.Small, FullWidth = true }">
|
|
||||||
<TitleContent>
|
|
||||||
<MudText Typo="Typo.h6">@(editingActivity == null ? "새 활동 기록" : "활동 기록 수정")</MudText>
|
|
||||||
</TitleContent>
|
|
||||||
<DialogContent>
|
|
||||||
<MudForm @ref="form">
|
|
||||||
<div class="mb-4">
|
|
||||||
<label class="mud-input-label mud-input-label-animated mud-input-label-outlined mb-1">고객</label>
|
|
||||||
<select class="mud-input mud-input-outlined mud-input-root mud-input-root-adorned-start"
|
|
||||||
style="width: 100%; min-height: 56px; padding: 16px 14px;"
|
|
||||||
value="@activityForm.ClientId"
|
|
||||||
@onchange="e => activityForm.ClientId = int.TryParse(e.Value?.ToString(), out var clientId) ? clientId : 0">
|
|
||||||
<option value="0">고객을 선택하세요.</option>
|
|
||||||
@foreach (var client in clients)
|
|
||||||
{
|
|
||||||
<option value="@client.Id">@GetClientDisplayName(client)</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<CommonCodeSelect @bind-Value="activityForm.ActivityType" Group="CONSULTING_ACTIVITY_TYPE" Label="활동 유형" Class="mb-4" Required="true" />
|
|
||||||
<MudTextField T="string" Label="활동일" Variant="Variant.Outlined" FullWidth="true" Class="mb-4" Required="true" Value="@(activityForm.ActivityDate?.ToString("yyyy-MM-dd HH:mm") ?? string.Empty)" />
|
|
||||||
<MudTextField T="string" @bind-Value="activityForm.Description" Label="설명" Variant="Variant.Outlined" FullWidth="true" Lines="3" Class="mb-4" Required="true" />
|
|
||||||
<MudTextField T="string" Label="다음 팔로업일" Variant="Variant.Outlined" FullWidth="true" Class="mb-4" Value="@(activityForm.NextFollowupDate?.ToString("yyyy-MM-dd") ?? string.Empty)" />
|
|
||||||
</MudForm>
|
|
||||||
</DialogContent>
|
|
||||||
<DialogActions>
|
|
||||||
<MudButton OnClick="CloseDialog">취소</MudButton>
|
|
||||||
<MudButton Color="Color.Primary" OnClick="SaveActivity">저장</MudButton>
|
|
||||||
</DialogActions>
|
|
||||||
</MudDialog>
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
private Task<AuthenticationState>? AuthStateTask { get; set; }
|
private Task<AuthenticationState>? AuthStateTask { get; set; }
|
||||||
@@ -130,10 +60,6 @@
|
|||||||
private List<ConsultingActivity>? activities;
|
private List<ConsultingActivity>? activities;
|
||||||
private List<Client> clients = [];
|
private List<Client> clients = [];
|
||||||
private Dictionary<int, string> clientMap = new();
|
private Dictionary<int, string> clientMap = new();
|
||||||
private MudForm? form;
|
|
||||||
private bool isDialogOpen;
|
|
||||||
private ConsultingActivity? editingActivity;
|
|
||||||
private ConsultingActivityForm activityForm = new();
|
|
||||||
|
|
||||||
private RenderFragment ActivitySkeleton => builder =>
|
private RenderFragment ActivitySkeleton => builder =>
|
||||||
{
|
{
|
||||||
@@ -178,126 +104,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OpenCreateDialog()
|
|
||||||
{
|
|
||||||
editingActivity = null;
|
|
||||||
activityForm = new ConsultingActivityForm
|
|
||||||
{
|
|
||||||
ActivityDate = DateTime.Now,
|
|
||||||
ClientId = clients.FirstOrDefault()?.Id ?? 0
|
|
||||||
};
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
if (form != null)
|
|
||||||
{
|
|
||||||
await form.Validate();
|
|
||||||
if (!form.IsValid)
|
|
||||||
{
|
|
||||||
Snackbar.Add("필수 항목을 입력해주세요.", Severity.Warning);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
{
|
|
||||||
{ "Title", "삭제 확인" },
|
|
||||||
{ "Message", "이 활동을 삭제하시겠습니까?" }
|
|
||||||
};
|
|
||||||
|
|
||||||
var dialog = await DialogService.ShowAsync<ConfirmDialog>("", 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 static string GetClientDisplayName(Client client)
|
private static string GetClientDisplayName(Client client)
|
||||||
=> !string.IsNullOrWhiteSpace(client.CompanyName)
|
=> !string.IsNullOrWhiteSpace(client.CompanyName) ? $"{client.Name} ({client.CompanyName})" : client.Name;
|
||||||
? client.CompanyName
|
|
||||||
: !string.IsNullOrWhiteSpace(client.Name)
|
|
||||||
? client.Name
|
|
||||||
: $"Client #{client.Id}";
|
|
||||||
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; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
@inject IRevenueTrackingBrowserClient RevenueClient
|
@inject IRevenueTrackingBrowserClient RevenueClient
|
||||||
@inject IClientBrowserClient ClientClient
|
@inject IClientBrowserClient ClientClient
|
||||||
@inject ISnackbar Snackbar
|
@inject ISnackbar Snackbar
|
||||||
@inject IDialogService DialogService
|
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
|
|
||||||
<PageTitle>수익 추적 관리</PageTitle>
|
<PageTitle>수익 추적 관리</PageTitle>
|
||||||
@@ -14,11 +13,8 @@
|
|||||||
<div>
|
<div>
|
||||||
<MudText Typo="Typo.caption" Class="admin-eyebrow">CRM & 세무관리</MudText>
|
<MudText Typo="Typo.caption" Class="admin-eyebrow">CRM & 세무관리</MudText>
|
||||||
<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>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<AdminDataPanel Loading="@(revenues is null)" SkeletonContent="@RevenueSkeleton">
|
<AdminDataPanel Loading="@(revenues is null)" SkeletonContent="@RevenueSkeleton">
|
||||||
@@ -27,99 +23,36 @@
|
|||||||
}
|
}
|
||||||
else if (revenues.Count == 0)
|
else if (revenues.Count == 0)
|
||||||
{
|
{
|
||||||
<MudAlert Severity="Severity.Info" Class="mt-4">
|
<MudAlert Severity="Severity.Info" Class="mt-4">청구 기록이 없습니다.</MudAlert>
|
||||||
<MudIcon Icon="@Icons.Material.Filled.Payments" Class="me-2" />
|
|
||||||
청구 기록이 없습니다.
|
|
||||||
</MudAlert>
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<MudDataGrid T="RevenueTracking"
|
<table class="admin-grid table">
|
||||||
Items="@revenues"
|
<thead>
|
||||||
Dense="true"
|
<tr>
|
||||||
Hover="true"
|
<th>ID</th>
|
||||||
Striped="true"
|
<th>고객</th>
|
||||||
|
<th>청구번호</th>
|
||||||
RowsPerPage="30"
|
<th>청구일</th>
|
||||||
Class="admin-grid">
|
<th>청구액</th>
|
||||||
<Columns>
|
</tr>
|
||||||
<PropertyColumn Property="x => x.Id" Title="ID" Sortable="true" />
|
</thead>
|
||||||
<TemplateColumn Title="고객">
|
<tbody>
|
||||||
<CellTemplate>
|
@foreach (var item in revenues)
|
||||||
@if (clientMap.TryGetValue(context.Item.ClientId, out var clientName))
|
{
|
||||||
{
|
<tr>
|
||||||
<MudLink Href="@($"/taxbaik/admin/clients/{context.Item.ClientId}")" Color="Color.Primary">
|
<td>@item.Id</td>
|
||||||
@clientName
|
<td>@clientMap.GetValueOrDefault(item.ClientId)</td>
|
||||||
</MudLink>
|
<td>@item.InvoiceNumber</td>
|
||||||
}
|
<td>@item.InvoiceDate.ToString("yyyy-MM-dd")</td>
|
||||||
</CellTemplate>
|
<td>@item.Amount.ToString("C")</td>
|
||||||
</TemplateColumn>
|
</tr>
|
||||||
<PropertyColumn Property="x => x.InvoiceNumber" Title="청구번호" />
|
}
|
||||||
<PropertyColumn Property="x => x.InvoiceDate" Title="청구일" Format="yyyy-MM-dd" />
|
</tbody>
|
||||||
<PropertyColumn Property="x => x.Amount" Title="청구액" Format="C" />
|
</table>
|
||||||
<TemplateColumn Title="납부여부">
|
|
||||||
<CellTemplate>
|
|
||||||
@if (context.Item.PaymentStatus == "paid")
|
|
||||||
{
|
|
||||||
<MudChip Size="Size.Small" Color="Color.Success" Variant="Variant.Filled">납부</MudChip>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<MudChip Size="Size.Small" Color="Color.Warning" Variant="Variant.Filled">미납</MudChip>
|
|
||||||
}
|
|
||||||
</CellTemplate>
|
|
||||||
</TemplateColumn>
|
|
||||||
<TemplateColumn Title="작업" Sortable="false">
|
|
||||||
<CellTemplate>
|
|
||||||
<MudButtonGroup Size="Size.Small" Variant="Variant.Outlined">
|
|
||||||
@if (context.Item.PaymentStatus != "paid")
|
|
||||||
{
|
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.CheckCircle" Color="Color.Success"
|
|
||||||
OnClick="@(async () => await MarkPaid(context.Item.Id))" Title="납부 처리" />
|
|
||||||
}
|
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.Delete" Color="Color.Error"
|
|
||||||
OnClick="@(async () => await DeleteRevenue(context.Item.Id))" />
|
|
||||||
</MudButtonGroup>
|
|
||||||
</CellTemplate>
|
|
||||||
</TemplateColumn>
|
|
||||||
</Columns>
|
|
||||||
</MudDataGrid>
|
|
||||||
}
|
}
|
||||||
</AdminDataPanel>
|
</AdminDataPanel>
|
||||||
|
|
||||||
<!-- 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">
|
|
||||||
<div class="mb-4">
|
|
||||||
<label class="mud-input-label mud-input-label-animated mud-input-label-outlined mb-1">고객</label>
|
|
||||||
<select class="mud-input mud-input-outlined mud-input-root mud-input-root-adorned-start"
|
|
||||||
style="width: 100%; min-height: 56px; padding: 16px 14px;"
|
|
||||||
value="@revenueForm.ClientId"
|
|
||||||
@onchange="e => revenueForm.ClientId = int.TryParse(e.Value?.ToString(), out var clientId) ? clientId : 0">
|
|
||||||
<option value="0">고객을 선택하세요.</option>
|
|
||||||
@foreach (var client in clients)
|
|
||||||
{
|
|
||||||
<option value="@client.Id">@GetClientDisplayName(client)</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<MudTextField T="string" @bind-Value="revenueForm.InvoiceNumber" Label="청구번호" Variant="Variant.Outlined" FullWidth="true" Class="mb-4" Required="true" />
|
|
||||||
<MudTextField T="string" Label="청구일" Variant="Variant.Outlined" FullWidth="true" Class="mb-4" Required="true" Value="@(revenueForm.InvoiceDate?.ToString("yyyy-MM-dd") ?? string.Empty)" />
|
|
||||||
<MudNumericField T="decimal" @bind-Value="revenueForm.Amount" Label="청구액" Variant="Variant.Outlined" FullWidth="true" Class="mb-4" Required="true" />
|
|
||||||
<CommonCodeSelect @bind-Value="revenueForm.ServiceType" Group="REVENUE_SERVICE_TYPE" Label="서비스 유형" Class="mb-4" />
|
|
||||||
<MudTextField T="string" Label="납부예정일" Variant="Variant.Outlined" FullWidth="true" Class="mb-4" Value="@(revenueForm.DueDate?.ToString("yyyy-MM-dd") ?? string.Empty)" />
|
|
||||||
</MudForm>
|
|
||||||
</DialogContent>
|
|
||||||
<DialogActions>
|
|
||||||
<MudButton OnClick="CloseDialog">취소</MudButton>
|
|
||||||
<MudButton Color="Color.Primary" OnClick="SaveRevenue">저장</MudButton>
|
|
||||||
</DialogActions>
|
|
||||||
</MudDialog>
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
private Task<AuthenticationState>? AuthStateTask { get; set; }
|
private Task<AuthenticationState>? AuthStateTask { get; set; }
|
||||||
@@ -127,9 +60,6 @@
|
|||||||
private List<RevenueTracking>? revenues;
|
private List<RevenueTracking>? revenues;
|
||||||
private List<Client> clients = [];
|
private List<Client> clients = [];
|
||||||
private Dictionary<int, string> clientMap = new();
|
private Dictionary<int, string> clientMap = new();
|
||||||
private MudForm? form;
|
|
||||||
private bool isDialogOpen;
|
|
||||||
private RevenueForm revenueForm = new();
|
|
||||||
|
|
||||||
private RenderFragment RevenueSkeleton => builder =>
|
private RenderFragment RevenueSkeleton => builder =>
|
||||||
{
|
{
|
||||||
@@ -174,111 +104,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OpenCreateDialog()
|
|
||||||
{
|
|
||||||
revenueForm = new RevenueForm
|
|
||||||
{
|
|
||||||
ClientId = clients.FirstOrDefault()?.Id ?? 0,
|
|
||||||
InvoiceDate = DateTime.Today,
|
|
||||||
DueDate = DateTime.Today.AddDays(14)
|
|
||||||
};
|
|
||||||
isDialogOpen = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task SaveRevenue()
|
|
||||||
{
|
|
||||||
if (form != null)
|
|
||||||
{
|
|
||||||
await form.Validate();
|
|
||||||
if (!form.IsValid)
|
|
||||||
{
|
|
||||||
Snackbar.Add("필수 항목을 입력해주세요.", Severity.Warning);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
{
|
|
||||||
{ "Title", "삭제 확인" },
|
|
||||||
{ "Message", "이 청구를 삭제하시겠습니까?" }
|
|
||||||
};
|
|
||||||
|
|
||||||
var dialog = await DialogService.ShowAsync<ConfirmDialog>("", 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 static string GetClientDisplayName(Client client)
|
private static string GetClientDisplayName(Client client)
|
||||||
=> !string.IsNullOrWhiteSpace(client.CompanyName)
|
=> !string.IsNullOrWhiteSpace(client.CompanyName) ? $"{client.Name} ({client.CompanyName})" : client.Name;
|
||||||
? client.CompanyName
|
|
||||||
: !string.IsNullOrWhiteSpace(client.Name)
|
|
||||||
? client.Name
|
|
||||||
: $"Client #{client.Id}";
|
|
||||||
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; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,100 +1,66 @@
|
|||||||
@inject NavigationManager Navigation
|
@inject NavigationManager Navigation
|
||||||
@implements IDisposable
|
@implements IDisposable
|
||||||
|
|
||||||
<MudLayout Class="admin-shell">
|
<div class="admin-shell">
|
||||||
<MudAppBar Elevation="0" Class="admin-topbar">
|
<header class="admin-topbar">
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.Menu"
|
<button type="button" class="admin-menu-button" @onclick="ToggleDrawer">Menu</button>
|
||||||
Color="Color.Inherit"
|
|
||||||
Edge="Edge.Start"
|
|
||||||
Class="admin-menu-button"
|
|
||||||
OnClick="@ToggleDrawer" />
|
|
||||||
<div class="admin-topbar-title">
|
<div class="admin-topbar-title">
|
||||||
<MudText Typo="Typo.body2" Class="font-weight-bold admin-brand-text">TaxBaik</MudText>
|
<div class="admin-brand-text">TaxBaik</div>
|
||||||
<MudText Typo="Typo.body2" Class="admin-brand-subtitle">세무회계 관리 대시보드</MudText>
|
<div class="admin-brand-subtitle">세무회계 관리 대시보드</div>
|
||||||
</div>
|
</div>
|
||||||
<MudSpacer />
|
|
||||||
|
|
||||||
<div class="admin-topbar-actions">
|
<div class="admin-topbar-actions">
|
||||||
<MudButton Class="admin-topbar-action"
|
<a class="admin-topbar-action" href="/taxbaik" target="_blank" rel="noopener">공개 사이트</a>
|
||||||
Variant="Variant.Text"
|
<a class="admin-topbar-action" href="/taxbaik/admin/logout">로그아웃</a>
|
||||||
Color="Color.Inherit"
|
|
||||||
Size="Size.Small"
|
|
||||||
StartIcon="@Icons.Material.Filled.OpenInNew"
|
|
||||||
Href="/taxbaik"
|
|
||||||
Target="_blank">
|
|
||||||
공개 사이트
|
|
||||||
</MudButton>
|
|
||||||
|
|
||||||
<MudDivider Vertical="true" FlexItem="true" Class="mx-2" />
|
|
||||||
|
|
||||||
<MudButton Class="admin-topbar-action"
|
|
||||||
Variant="Variant.Text"
|
|
||||||
Color="Color.Error"
|
|
||||||
Size="Size.Small"
|
|
||||||
StartIcon="@Icons.Material.Filled.Logout"
|
|
||||||
Href="/taxbaik/admin/logout">
|
|
||||||
로그아웃
|
|
||||||
</MudButton>
|
|
||||||
</div>
|
</div>
|
||||||
</MudAppBar>
|
</header>
|
||||||
|
|
||||||
<MudDrawer Open="@drawerOpen"
|
<div class="admin-body">
|
||||||
Elevation="0"
|
@if (drawerOpen)
|
||||||
Variant="DrawerVariant.Responsive"
|
{
|
||||||
Breakpoint="Breakpoint.Md"
|
<aside class="admin-drawer">
|
||||||
Class="admin-drawer">
|
<div class="admin-drawer-brand">
|
||||||
<div class="admin-drawer-brand">
|
<div class="admin-brand-mark">T</div>
|
||||||
<div class="admin-brand-mark">T</div>
|
<div>
|
||||||
<div>
|
<div class="admin-drawer-title">TaxBaik</div>
|
||||||
<MudText Typo="Typo.subtitle1">TaxBaik</MudText>
|
<div class="admin-drawer-subtitle">세무 운영 콘솔</div>
|
||||||
<MudText Typo="Typo.caption">세무 운영 콘솔</MudText>
|
</div>
|
||||||
|
</div>
|
||||||
|
<nav class="admin-nav">
|
||||||
|
<a href="/taxbaik/admin/dashboard">대시보드</a>
|
||||||
|
<div class="admin-nav-group">CRM & 세무관리</div>
|
||||||
|
<a href="/taxbaik/admin/tax-profiles">세무 프로필</a>
|
||||||
|
<a href="/taxbaik/admin/tax-filing-schedules">신고 일정</a>
|
||||||
|
<a href="/taxbaik/admin/contracts">계약 관리</a>
|
||||||
|
<a href="/taxbaik/admin/consulting-activities">상담 활동</a>
|
||||||
|
<a href="/taxbaik/admin/revenue-trackings">수익 추적</a>
|
||||||
|
<div class="admin-nav-group">고객 관리</div>
|
||||||
|
<a href="/taxbaik/admin/clients">고객 카드</a>
|
||||||
|
<a href="/taxbaik/admin/tax-filings">세무신고</a>
|
||||||
|
<div class="admin-nav-group">홈페이지</div>
|
||||||
|
<a href="/taxbaik/admin/announcements">공지사항</a>
|
||||||
|
<a href="/taxbaik/admin/faqs">FAQ 관리</a>
|
||||||
|
<a href="/taxbaik/admin/blog">블로그 관리</a>
|
||||||
|
<a href="/taxbaik/admin/season-simulator">시즌 시뮬레이터</a>
|
||||||
|
<a href="/taxbaik/admin/inquiries">문의 관리</a>
|
||||||
|
<a href="/taxbaik/admin/settings">설정</a>
|
||||||
|
<a href="/taxbaik/admin/common-codes">공통관리</a>
|
||||||
|
</nav>
|
||||||
|
</aside>
|
||||||
|
}
|
||||||
|
|
||||||
|
<main class="admin-main">
|
||||||
|
<div class="admin-content">
|
||||||
|
@ChildContent
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</main>
|
||||||
<MudNavMenu Class="admin-nav">
|
</div>
|
||||||
<MudNavLink Href="/taxbaik/admin/dashboard" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Dashboard" ClickPreventDefault="true" OnClick="@(() => NavigateTo("/taxbaik/admin/dashboard"))">대시보드</MudNavLink>
|
</div>
|
||||||
<MudNavGroup Title="CRM & 세무관리" Icon="@Icons.Material.Filled.BusinessCenter" @bind-Expanded="@expandedCRMGroup">
|
|
||||||
<MudNavLink Href="/taxbaik/admin/tax-profiles" Icon="@Icons.Material.Filled.Assignment" ClickPreventDefault="true" OnClick="@(() => NavigateTo("/taxbaik/admin/tax-profiles"))">세무 프로필</MudNavLink>
|
|
||||||
<MudNavLink Href="/taxbaik/admin/tax-filing-schedules" Icon="@Icons.Material.Filled.CalendarMonth" ClickPreventDefault="true" OnClick="@(() => NavigateTo("/taxbaik/admin/tax-filing-schedules"))">신고 일정</MudNavLink>
|
|
||||||
<MudNavLink Href="/taxbaik/admin/contracts" Icon="@Icons.Material.Filled.Description" ClickPreventDefault="true" OnClick="@(() => NavigateTo("/taxbaik/admin/contracts"))">계약 관리</MudNavLink>
|
|
||||||
<MudNavLink Href="/taxbaik/admin/consulting-activities" Icon="@Icons.Material.Filled.ChatBubble" ClickPreventDefault="true" OnClick="@(() => NavigateTo("/taxbaik/admin/consulting-activities"))">상담 활동</MudNavLink>
|
|
||||||
<MudNavLink Href="/taxbaik/admin/revenue-trackings" Icon="@Icons.Material.Filled.Receipt" ClickPreventDefault="true" OnClick="@(() => NavigateTo("/taxbaik/admin/revenue-trackings"))">수익 추적</MudNavLink>
|
|
||||||
</MudNavGroup>
|
|
||||||
<MudNavGroup Title="고객 관리" Icon="@Icons.Material.Filled.PeopleAlt" @bind-Expanded="@expandedCustomerGroup">
|
|
||||||
<MudNavLink Href="/taxbaik/admin/clients" Icon="@Icons.Material.Filled.ContactPage" ClickPreventDefault="true" OnClick="@(() => NavigateTo("/taxbaik/admin/clients"))">고객 카드</MudNavLink>
|
|
||||||
<MudNavLink Href="/taxbaik/admin/tax-filings" Icon="@Icons.Material.Filled.Assessment" ClickPreventDefault="true" OnClick="@(() => NavigateTo("/taxbaik/admin/tax-filings"))">세무신고</MudNavLink>
|
|
||||||
</MudNavGroup>
|
|
||||||
<MudNavGroup Title="홈페이지" Icon="@Icons.Material.Filled.Home" @bind-Expanded="@expandedWebsiteGroup">
|
|
||||||
<MudNavLink Href="/taxbaik/admin/announcements" Icon="@Icons.Material.Filled.Campaign" ClickPreventDefault="true" OnClick="@(() => NavigateTo("/taxbaik/admin/announcements"))">공지사항</MudNavLink>
|
|
||||||
<MudNavLink Href="/taxbaik/admin/faqs" Icon="@Icons.Material.Filled.QuestionAnswer" ClickPreventDefault="true" OnClick="@(() => NavigateTo("/taxbaik/admin/faqs"))">FAQ 관리</MudNavLink>
|
|
||||||
<MudNavLink Href="/taxbaik/admin/blog" Icon="@Icons.Material.Filled.Article" ClickPreventDefault="true" OnClick="@(() => NavigateTo("/taxbaik/admin/blog"))">블로그 관리</MudNavLink>
|
|
||||||
<MudNavLink Href="/taxbaik/admin/season-simulator" Icon="@Icons.Material.Filled.Preview" ClickPreventDefault="true" OnClick="@(() => NavigateTo("/taxbaik/admin/season-simulator"))">시즌 시뮬레이터</MudNavLink>
|
|
||||||
</MudNavGroup>
|
|
||||||
<MudNavLink Href="/taxbaik/admin/inquiries" Icon="@Icons.Material.Filled.Forum" ClickPreventDefault="true" OnClick="@(() => NavigateTo("/taxbaik/admin/inquiries"))">문의 관리</MudNavLink>
|
|
||||||
<MudNavLink Href="/taxbaik/admin/settings" Icon="@Icons.Material.Filled.Tune" ClickPreventDefault="true" OnClick="@(() => NavigateTo("/taxbaik/admin/settings"))">설정</MudNavLink>
|
|
||||||
<MudNavLink Href="/taxbaik/admin/common-codes" Icon="@Icons.Material.Filled.Category" ClickPreventDefault="true" OnClick="@(() => NavigateTo("/taxbaik/admin/common-codes"))">공통관리</MudNavLink>
|
|
||||||
</MudNavMenu>
|
|
||||||
<div class="admin-drawer-version">
|
|
||||||
<div class="admin-drawer-version-label">Version</div>
|
|
||||||
<div class="admin-drawer-version-value">vunknown</div>
|
|
||||||
<div class="admin-drawer-version-built">unknown</div>
|
|
||||||
</div>
|
|
||||||
</MudDrawer>
|
|
||||||
|
|
||||||
<MudMainContent Class="admin-main">
|
|
||||||
<MudContainer MaxWidth="MaxWidth.False" Class="admin-content">
|
|
||||||
@ChildContent
|
|
||||||
</MudContainer>
|
|
||||||
</MudMainContent>
|
|
||||||
</MudLayout>
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public RenderFragment? ChildContent { get; set; }
|
public RenderFragment? ChildContent { get; set; }
|
||||||
|
|
||||||
private bool drawerOpen = true;
|
private bool drawerOpen = true;
|
||||||
private bool expandedCRMGroup = true;
|
|
||||||
private bool expandedCustomerGroup = false;
|
|
||||||
private bool expandedWebsiteGroup = false;
|
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
@@ -110,11 +76,6 @@
|
|||||||
drawerOpen = !drawerOpen;
|
drawerOpen = !drawerOpen;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void NavigateTo(string url)
|
|
||||||
{
|
|
||||||
Navigation.NavigateTo(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Navigation.LocationChanged -= OnLocationChanged;
|
Navigation.LocationChanged -= OnLocationChanged;
|
||||||
|
|||||||
Reference in New Issue
Block a user