Files
taxbaik/TaxBaik.Web.Client/Components/Admin/Pages/Companies/CompanyList.razor
T
kjh2064 e5981769b9
TaxBaik CI/CD / build-and-deploy (push) Successful in 2m11s
fix: per-page WASM render mode, Contact checkbox binding, Telegram inquiry channel
- Admin: replace the global @rendermode on <Routes>/<Router> with per-page
  render mode. Login.razor now prerenders (form visible before WASM loads);
  every other [Authorize] page stays prerender: false to avoid the
  AuthorizeRouteView blank-render regression from earlier attempts. Adds a
  "준비 중" -> "로그인" splash tied to WASM boot completion, and lets the
  authenticated-shell loading overlay stay up until AdminShell actually renders.
- Contact.cshtml: fix the "Agree" checkbox missing value="true" - a checked
  box sent the browser-default "on", which bool model binding can't parse,
  so ModelState.IsValid silently went false and OnPostAsync returned a blank
  form with no visible error on every submission. Validation summary widened
  from ModelOnly to All so this class of failure isn't silent again.
- TelegramInquiryNotificationService: read Telegram:InquiryChatId (falling
  back to ChatId) instead of only ChatId, matching the channel routing
  CLAUDE.md documents and deploy.yml already provisions as separate secrets.
- Reconcile CLAUDE.md's self-contradicting Phase 8 prerender notes (Phase 9),
  rewrite validate_admin_render.sh for the per-page design, and add a
  SmartAdmin 5.5 design reference section to DOUZONE_UX_GUIDE.md for future
  admin screens (existing screens unchanged, tracked as WBS P4-03).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-03 10:15:27 +09:00

136 lines
5.3 KiB
Plaintext

@page "/admin/companies"
@rendermode @(new InteractiveWebAssemblyRenderMode(prerender: false))
@attribute [Authorize]
@inject IApiClient ApiClient
@inject ISnackbar Snackbar
<PageTitle>고객사 관리</PageTitle>
<section class="admin-page-hero">
<div>
<MudText Typo="Typo.caption" Class="admin-eyebrow">Settings</MudText>
<MudText Typo="Typo.h4" Class="admin-page-title">고객사 관리</MudText>
<MudText Typo="Typo.body2" Class="admin-page-subtitle">등록된 고객사를 관리하고 새로운 고객사를 추가합니다.</MudText>
</div>
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Add"
Href="/taxbaik/admin/companies/create">새 고객사 등록</MudButton>
</section>
<MudPaper Class="admin-surface mb-4 mt-4" Elevation="0">
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.subtitle1">@($"전체 고객사 {totalCompanies}개")</MudText>
<MudText Typo="Typo.body2">페이지 @currentPage / @totalPages</MudText>
</MudStack>
</MudPaper>
<MudDataGrid Items="@companies" Striped="true" Hoverable="true" Loading="@isLoading" Class="admin-grid">
<Columns>
<PropertyColumn Property="x => x.CompanyCode" Title="회사코드" />
<PropertyColumn Property="x => x.CompanyName" Title="회사명" />
<PropertyColumn Property="x => x.ContactPerson" Title="담당자" />
<PropertyColumn Property="x => x.Phone" Title="전화" />
<PropertyColumn Property="x => x.Email" Title="이메일" />
<PropertyColumn Property="x => x.IsActive" Title="활성">
<CellTemplate Context="cell">
<MudCheckBox T="bool" Value="@cell.Item.IsActive" Disabled="true" />
</CellTemplate>
</PropertyColumn>
<PropertyColumn Property="x => x.CreatedAt" Title="등록일" Format="yyyy-MM-dd" />
<TemplateColumn>
<CellTemplate Context="cell">
<MudButton Variant="Variant.Outlined" Size="Size.Small" Color="Color.Primary"
Href="@($"/taxbaik/admin/companies/{cell.Item.Id}/edit")">수정</MudButton>
</CellTemplate>
</TemplateColumn>
</Columns>
</MudDataGrid>
<MudStack Row="true" Justify="Justify.Center" Class="mt-4" Spacing="2">
<MudButton Variant="Variant.Outlined" Disabled="@(currentPage <= 1 || isLoading)" @onclick="PreviousPage">이전</MudButton>
<MudButton Variant="Variant.Outlined" Disabled="@(currentPage >= totalPages || isLoading)" @onclick="NextPage">다음</MudButton>
</MudStack>
@code {
private List<CompanyDto> companies = [];
private bool isLoading = true;
private int currentPage = 1;
private int totalPages = 1;
private int totalCompanies = 0;
private const int PageSize = 20;
protected override async Task OnInitializedAsync()
{
await LoadData();
}
private async Task LoadData()
{
try
{
isLoading = true;
var response = await ApiClient.GetAsync<dynamic>($"company?page={currentPage}&pageSize={PageSize}");
IDictionary<string, object>? dict = response as IDictionary<string, object>;
if (dict != null)
{
totalCompanies = (int)(dynamic)dict["total"];
totalPages = (totalCompanies + PageSize - 1) / PageSize;
if (dict["data"] is System.Collections.IEnumerable dataList)
{
companies = new List<CompanyDto>();
foreach (var item in dataList)
{
if (item is IDictionary<string, object> companyDict)
{
companies.Add(new CompanyDto
{
Id = (int)(dynamic)companyDict["id"],
CompanyCode = (string)companyDict["companyCode"],
CompanyName = (string)companyDict["companyName"],
ContactPerson = (string?)companyDict["contactPerson"],
Phone = (string?)companyDict["phone"],
Email = (string?)companyDict["email"],
IsActive = (bool)(dynamic)companyDict["isActive"],
CreatedAt = DateTime.Parse(companyDict["createdAt"].ToString()!)
});
}
}
}
}
}
catch (Exception ex)
{
Snackbar.Add($"고객사 로드 실패: {ex.Message}", Severity.Error);
}
finally
{
isLoading = false;
}
}
private async Task NextPage()
{
currentPage++;
await LoadData();
}
private async Task PreviousPage()
{
currentPage = Math.Max(1, currentPage - 1);
await LoadData();
}
private class CompanyDto
{
public int Id { get; set; }
public string CompanyCode { get; set; } = "";
public string CompanyName { get; set; } = "";
public string? ContactPerson { get; set; }
public string? Phone { get; set; }
public string? Email { get; set; }
public bool IsActive { get; set; }
public DateTime CreatedAt { get; set; }
}
}