This commit is contained in:
@@ -0,0 +1,17 @@
|
|||||||
|
# 관리자 Blazor 레거시 분리
|
||||||
|
|
||||||
|
현재 저장소에는 `src/TaxBaik.Web.Client/Components/Admin/*` 아래 Blazor 기반 관리자 화면이 남아 있다.
|
||||||
|
|
||||||
|
이 영역은 다음 기준으로 레거시 취급한다.
|
||||||
|
|
||||||
|
- 신규 어드민 화면은 `src/TaxBaik.Web/Pages/Admin/*` 의 Razor Pages 기준으로 작성한다.
|
||||||
|
- 관리자 인증은 Cookie Authentication 을 사용한다.
|
||||||
|
- 브라우저 토큰 저장은 신규 경로에서 사용하지 않는다.
|
||||||
|
- Blazor 관리자 화면은 유지보수용 레거시로만 남기고, 신규 CRUD 는 Razor Pages 로 확장한다.
|
||||||
|
|
||||||
|
후속 정리 대상 예시:
|
||||||
|
|
||||||
|
- `src/TaxBaik.Web.Client/Components/Admin/Pages/*`
|
||||||
|
- `src/TaxBaik.Web.Client/Components/Admin/Shared/*`
|
||||||
|
- `src/TaxBaik.Web.Client/Components/Admin/Services/*`
|
||||||
|
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| [ENGINEERING_HARNESS.md](./ENGINEERING_HARNESS.md) | 아키텍처, 코드 품질, 배포, 데이터 정합성 하네스 | 방향성 변경 또는 반복 위반 발견 |
|
| [ENGINEERING_HARNESS.md](./ENGINEERING_HARNESS.md) | 아키텍처, 코드 품질, 배포, 데이터 정합성 하네스 | 방향성 변경 또는 반복 위반 발견 |
|
||||||
| [ADMIN_RAZORPAGES_ROADMAP.md](./ADMIN_RAZORPAGES_ROADMAP.md) | 고객관리 어드민 Razor Pages 전환 로드맵과 WBS | 어드민 인증/레이아웃/CRUD 전환 작업 변경 |
|
| [ADMIN_RAZORPAGES_ROADMAP.md](./ADMIN_RAZORPAGES_ROADMAP.md) | 고객관리 어드민 Razor Pages 전환 로드맵과 WBS | 어드민 인증/레이아웃/CRUD 전환 작업 변경 |
|
||||||
|
| [ADMIN_LEGACY_BLAZOR.md](./ADMIN_LEGACY_BLAZOR.md) | 관리자 Blazor 레거시 분리 기준 | Blazor 관리자 경로 정리 또는 제거 |
|
||||||
| [STACK_TEMPLATE_WBS.md](./STACK_TEMPLATE_WBS.md) | MudBlazor + ASP.NET Core 10 + Dapper + PostgreSQL 18.4 + API-first + SSR/Interactive render mode 기준 템플릿, 로드맵, WBS | 새 스택 채택 또는 렌더/인증 기준 변경 |
|
| [STACK_TEMPLATE_WBS.md](./STACK_TEMPLATE_WBS.md) | MudBlazor + ASP.NET Core 10 + Dapper + PostgreSQL 18.4 + API-first + SSR/Interactive render mode 기준 템플릿, 로드맵, WBS | 새 스택 채택 또는 렌더/인증 기준 변경 |
|
||||||
| [DOUZONE_UX_GUIDE.md](./DOUZONE_UX_GUIDE.md) | 더존식 어드민 UX 원칙, 템플릿, 컴포넌트, 서빙 규칙 | 화면 패턴 변경 또는 신규 템플릿 추가 |
|
| [DOUZONE_UX_GUIDE.md](./DOUZONE_UX_GUIDE.md) | 더존식 어드민 UX 원칙, 템플릿, 컴포넌트, 서빙 규칙 | 화면 패턴 변경 또는 신규 템플릿 추가 |
|
||||||
| [OPS_LOG_DIAGNOSTICS_ROADMAP.md](./OPS_LOG_DIAGNOSTICS_ROADMAP.md) | 관리자 대시보드/클라이언트 로그 진단 로드맵 | 로그 진단 흐름 또는 관측성 작업 변경 |
|
| [OPS_LOG_DIAGNOSTICS_ROADMAP.md](./OPS_LOG_DIAGNOSTICS_ROADMAP.md) | 관리자 대시보드/클라이언트 로그 진단 로드맵 | 로그 진단 흐름 또는 관측성 작업 변경 |
|
||||||
@@ -21,6 +22,7 @@
|
|||||||
| 영역 | 라우트/파일 | 기준 |
|
| 영역 | 라우트/파일 | 기준 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| Public Home/Blog/Contact | `/`, `/blog`, `/contact` | 서버 사이드 렌더링, SEO 우선, WASM 의존 금지 |
|
| Public Home/Blog/Contact | `/`, `/blog`, `/contact` | 서버 사이드 렌더링, SEO 우선, WASM 의존 금지 |
|
||||||
|
| Admin Dashboard / Customers | `/admin`, `/admin/dashboard`, `/admin/customers/*` | Razor Pages + Cookie Authentication 기준 |
|
||||||
| Admin Blog | `/admin/blog`, `/admin/blog/create`, `/admin/blog/{id}/edit` | 현행 Blazor/Web API 경로, 전환 대상 |
|
| Admin Blog | `/admin/blog`, `/admin/blog/create`, `/admin/blog/{id}/edit` | 현행 Blazor/Web API 경로, 전환 대상 |
|
||||||
| Admin Inquiry | `/admin/inquiries`, `/admin/inquiries/create`, `/admin/inquiries/{id}/edit` | 현행 Blazor/Web API 경로, 전환 대상 |
|
| Admin Inquiry | `/admin/inquiries`, `/admin/inquiries/create`, `/admin/inquiries/{id}/edit` | 현행 Blazor/Web API 경로, 전환 대상 |
|
||||||
| Public API | `/api/*` | 현행 JWT 경로, 브라우저 어드민과 분리 |
|
| Public API | `/api/*` | 현행 JWT 경로, 브라우저 어드민과 분리 |
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
@page
|
||||||
|
@model TaxBaik.Web.Pages.Admin.Customers.CreateModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "고객 등록";
|
||||||
|
}
|
||||||
|
|
||||||
|
<section class="container py-4 py-lg-5">
|
||||||
|
<div class="mb-4">
|
||||||
|
<p class="text-uppercase text-muted small mb-2">Customer Management</p>
|
||||||
|
<h1 class="h2 fw-bold mb-1">고객 등록</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="post" class="card shadow-sm border-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<div asp-validation-summary="All" class="alert alert-danger"></div>
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label asp-for="Input.Name" class="form-label"></label>
|
||||||
|
<input asp-for="Input.Name" class="form-control" />
|
||||||
|
<span asp-validation-for="Input.Name" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label asp-for="Input.CompanyName" class="form-label"></label>
|
||||||
|
<input asp-for="Input.CompanyName" class="form-control" />
|
||||||
|
<span asp-validation-for="Input.CompanyName" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label asp-for="Input.Phone" class="form-label"></label>
|
||||||
|
<input asp-for="Input.Phone" class="form-control" />
|
||||||
|
<span asp-validation-for="Input.Phone" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label asp-for="Input.Email" class="form-label"></label>
|
||||||
|
<input asp-for="Input.Email" class="form-control" />
|
||||||
|
<span asp-validation-for="Input.Email" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="Input.Status" class="form-label"></label>
|
||||||
|
<input asp-for="Input.Status" class="form-control" />
|
||||||
|
<span asp-validation-for="Input.Status" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="Input.ServiceType" class="form-label"></label>
|
||||||
|
<input asp-for="Input.ServiceType" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="Input.TaxType" class="form-label"></label>
|
||||||
|
<input asp-for="Input.TaxType" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label asp-for="Input.Source" class="form-label"></label>
|
||||||
|
<input asp-for="Input.Source" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label asp-for="Input.Memo" class="form-label"></label>
|
||||||
|
<textarea asp-for="Input.Memo" class="form-control" rows="5"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer bg-white d-flex justify-content-end gap-2">
|
||||||
|
<a class="btn btn-outline-secondary" href="/admin/customers">취소</a>
|
||||||
|
<button class="btn btn-dark" type="submit">저장</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
using TaxBaik.Application.DTOs;
|
||||||
|
using TaxBaik.Application.Services;
|
||||||
|
using TaxBaik.Web.Services;
|
||||||
|
|
||||||
|
namespace TaxBaik.Web.Pages.Admin.Customers;
|
||||||
|
|
||||||
|
[Authorize(AuthenticationSchemes = AdminAuthDefaults.Scheme)]
|
||||||
|
public class CreateModel(ClientService clientService) : PageModel
|
||||||
|
{
|
||||||
|
[BindProperty]
|
||||||
|
public CreateClientDto Input { get; set; } = new();
|
||||||
|
|
||||||
|
public IActionResult OnGet() => Page();
|
||||||
|
|
||||||
|
public async Task<IActionResult> OnPostAsync(CancellationToken ct)
|
||||||
|
{
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
return Page();
|
||||||
|
|
||||||
|
await clientService.CreateAsync(Input, ct);
|
||||||
|
return RedirectToPage("/Admin/Customers/Index");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
@page "{id:int}"
|
||||||
|
@model TaxBaik.Web.Pages.Admin.Customers.DetailModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "고객 상세";
|
||||||
|
}
|
||||||
|
|
||||||
|
<section class="container py-4 py-lg-5">
|
||||||
|
@if (Model.Client is null)
|
||||||
|
{
|
||||||
|
<div class="alert alert-warning">고객을 찾을 수 없습니다.</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="d-flex flex-wrap justify-content-between align-items-end gap-3 mb-4">
|
||||||
|
<div>
|
||||||
|
<p class="text-uppercase text-muted small mb-2">Customer Management</p>
|
||||||
|
<h1 class="h2 fw-bold mb-1">@Model.Client.Name</h1>
|
||||||
|
<p class="text-muted mb-0">고객 기본 정보와 변경 이력을 확인합니다.</p>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<a class="btn btn-outline-secondary" href="/admin/customers/@Model.Client.Id/edit">수정</a>
|
||||||
|
<a class="btn btn-dark" href="/admin/customers">목록</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow-sm border-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<dl class="row mb-0">
|
||||||
|
<dt class="col-sm-3">회사명</dt><dd class="col-sm-9">@Model.Client.CompanyName</dd>
|
||||||
|
<dt class="col-sm-3">전화</dt><dd class="col-sm-9">@Model.Client.Phone</dd>
|
||||||
|
<dt class="col-sm-3">이메일</dt><dd class="col-sm-9">@Model.Client.Email</dd>
|
||||||
|
<dt class="col-sm-3">상태</dt><dd class="col-sm-9">@Model.Client.Status</dd>
|
||||||
|
<dt class="col-sm-3">서비스 유형</dt><dd class="col-sm-9">@Model.Client.ServiceType</dd>
|
||||||
|
<dt class="col-sm-3">세금 유형</dt><dd class="col-sm-9">@Model.Client.TaxType</dd>
|
||||||
|
<dt class="col-sm-3">유입 경로</dt><dd class="col-sm-9">@Model.Client.Source</dd>
|
||||||
|
<dt class="col-sm-3">메모</dt><dd class="col-sm-9">@Model.Client.Memo</dd>
|
||||||
|
<dt class="col-sm-3">생성일</dt><dd class="col-sm-9">@Model.Client.CreatedAt.ToString("yyyy-MM-dd HH:mm")</dd>
|
||||||
|
<dt class="col-sm-3">수정일</dt><dd class="col-sm-9">@Model.Client.UpdatedAt.ToString("yyyy-MM-dd HH:mm")</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</section>
|
||||||
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
using TaxBaik.Application.Services;
|
||||||
|
using TaxBaik.Web.Services;
|
||||||
|
|
||||||
|
namespace TaxBaik.Web.Pages.Admin.Customers;
|
||||||
|
|
||||||
|
[Authorize(AuthenticationSchemes = AdminAuthDefaults.Scheme)]
|
||||||
|
public class DetailModel(ClientService clientService) : PageModel
|
||||||
|
{
|
||||||
|
public TaxBaik.Domain.Entities.Client? Client { get; private set; }
|
||||||
|
|
||||||
|
public async Task OnGetAsync(int id, CancellationToken ct)
|
||||||
|
{
|
||||||
|
Client = await clientService.GetByIdAsync(id, ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
@page "{id:int}"
|
||||||
|
@model TaxBaik.Web.Pages.Admin.Customers.EditModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "고객 수정";
|
||||||
|
}
|
||||||
|
|
||||||
|
<section class="container py-4 py-lg-5">
|
||||||
|
<div class="mb-4">
|
||||||
|
<p class="text-uppercase text-muted small mb-2">Customer Management</p>
|
||||||
|
<h1 class="h2 fw-bold mb-1">고객 수정</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="post" class="card shadow-sm border-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<div asp-validation-summary="All" class="alert alert-danger"></div>
|
||||||
|
<input type="hidden" asp-for="Id" />
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label asp-for="Input.Name" class="form-label"></label>
|
||||||
|
<input asp-for="Input.Name" class="form-control" />
|
||||||
|
<span asp-validation-for="Input.Name" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label asp-for="Input.CompanyName" class="form-label"></label>
|
||||||
|
<input asp-for="Input.CompanyName" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label asp-for="Input.Phone" class="form-label"></label>
|
||||||
|
<input asp-for="Input.Phone" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label asp-for="Input.Email" class="form-label"></label>
|
||||||
|
<input asp-for="Input.Email" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="Input.Status" class="form-label"></label>
|
||||||
|
<input asp-for="Input.Status" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="Input.ServiceType" class="form-label"></label>
|
||||||
|
<input asp-for="Input.ServiceType" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="Input.TaxType" class="form-label"></label>
|
||||||
|
<input asp-for="Input.TaxType" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label asp-for="Input.Source" class="form-label"></label>
|
||||||
|
<input asp-for="Input.Source" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label asp-for="Input.Memo" class="form-label"></label>
|
||||||
|
<textarea asp-for="Input.Memo" class="form-control" rows="5"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer bg-white d-flex justify-content-between gap-2">
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<button class="btn btn-outline-danger" type="submit" formaction="?handler=Delete" formmethod="post" onclick="return confirm('삭제하시겠습니까?');">삭제</button>
|
||||||
|
<a class="btn btn-outline-secondary" href="/admin/customers/@Model.Id">취소</a>
|
||||||
|
<button class="btn btn-dark" type="submit">저장</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
using TaxBaik.Application.DTOs;
|
||||||
|
using TaxBaik.Application.Services;
|
||||||
|
using TaxBaik.Web.Services;
|
||||||
|
|
||||||
|
namespace TaxBaik.Web.Pages.Admin.Customers;
|
||||||
|
|
||||||
|
[Authorize(AuthenticationSchemes = AdminAuthDefaults.Scheme)]
|
||||||
|
public class EditModel(ClientService clientService) : PageModel
|
||||||
|
{
|
||||||
|
[BindProperty(SupportsGet = true)]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
[BindProperty]
|
||||||
|
public CreateClientDto Input { get; set; } = new();
|
||||||
|
|
||||||
|
public async Task<IActionResult> OnGetAsync(CancellationToken ct)
|
||||||
|
{
|
||||||
|
var client = await clientService.GetByIdAsync(Id, ct);
|
||||||
|
if (client is null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
Input = new CreateClientDto
|
||||||
|
{
|
||||||
|
Name = client.Name,
|
||||||
|
CompanyName = client.CompanyName,
|
||||||
|
Phone = client.Phone,
|
||||||
|
Email = client.Email,
|
||||||
|
ServiceType = client.ServiceType,
|
||||||
|
TaxType = client.TaxType,
|
||||||
|
Status = client.Status,
|
||||||
|
Source = client.Source,
|
||||||
|
Memo = client.Memo
|
||||||
|
};
|
||||||
|
|
||||||
|
return Page();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> OnPostAsync(CancellationToken ct)
|
||||||
|
{
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
return Page();
|
||||||
|
|
||||||
|
await clientService.UpdateAsync(Id, Input, ct);
|
||||||
|
return RedirectToPage("/Admin/Customers/Detail", new { id = Id });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> OnPostDeleteAsync(CancellationToken ct)
|
||||||
|
{
|
||||||
|
await clientService.DeleteAsync(Id, ct);
|
||||||
|
return RedirectToPage("/Admin/Customers/Index");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
@page
|
||||||
|
@model TaxBaik.Web.Pages.Admin.Customers.IndexModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "고객 목록";
|
||||||
|
}
|
||||||
|
|
||||||
|
<section class="container py-4 py-lg-5">
|
||||||
|
<div class="d-flex flex-wrap justify-content-between align-items-end gap-3 mb-4">
|
||||||
|
<div>
|
||||||
|
<p class="text-uppercase text-muted small mb-2">Customer Management</p>
|
||||||
|
<h1 class="h2 fw-bold mb-1">고객 목록</h1>
|
||||||
|
<p class="text-muted mb-0">검색, 상태 필터, 페이지네이션 기준의 고객 목록입니다.</p>
|
||||||
|
</div>
|
||||||
|
<a class="btn btn-dark" href="/admin/customers/create">고객 등록</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="get" class="card shadow-sm border-0 mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row g-3 align-items-end">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label class="form-label" for="Search">검색</label>
|
||||||
|
<input class="form-control" id="Search" name="Search" value="@Model.Search" placeholder="이름, 전화, 회사명" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label class="form-label" for="Status">상태</label>
|
||||||
|
<select class="form-select" id="Status" name="Status">
|
||||||
|
<option value="" selected="@(string.IsNullOrWhiteSpace(Model.Status))">전체</option>
|
||||||
|
<option value="active" selected="@(Model.Status == "active")">active</option>
|
||||||
|
<option value="inactive" selected="@(Model.Status == "inactive")">inactive</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label class="form-label" for="PageSize">페이지 크기</label>
|
||||||
|
<select class="form-select" id="PageSize" name="PageSize">
|
||||||
|
<option value="10" selected="@(Model.PageSize == 10)">10</option>
|
||||||
|
<option value="20" selected="@(Model.PageSize == 20)">20</option>
|
||||||
|
<option value="50" selected="@(Model.PageSize == 50)">50</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3 d-grid d-sm-flex gap-2">
|
||||||
|
<button class="btn btn-dark" type="submit">검색</button>
|
||||||
|
<a class="btn btn-outline-secondary" href="/admin/customers">초기화</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="card shadow-sm border-0">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle mb-0">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>고객명</th>
|
||||||
|
<th>회사명</th>
|
||||||
|
<th>전화</th>
|
||||||
|
<th>상태</th>
|
||||||
|
<th>등록일</th>
|
||||||
|
<th class="text-end">작업</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@if (Model.Items.Count == 0)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td colspan="6" class="text-center text-muted py-5">고객 데이터가 없습니다.</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var item in Model.Items)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td class="fw-semibold">@item.Name</td>
|
||||||
|
<td>@item.CompanyName</td>
|
||||||
|
<td>@item.Phone</td>
|
||||||
|
<td><span class="badge text-bg-secondary">@item.Status</span></td>
|
||||||
|
<td>@item.CreatedAt.ToString("yyyy-MM-dd")</td>
|
||||||
|
<td class="text-end">
|
||||||
|
<a class="btn btn-sm btn-outline-secondary" href="/admin/customers/@item.Id">상세</a>
|
||||||
|
<a class="btn btn-sm btn-outline-primary" href="/admin/customers/@item.Id/edit">수정</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
using TaxBaik.Application.Services;
|
||||||
|
using TaxBaik.Web.Services;
|
||||||
|
|
||||||
|
namespace TaxBaik.Web.Pages.Admin.Customers;
|
||||||
|
|
||||||
|
[Authorize(AuthenticationSchemes = AdminAuthDefaults.Scheme)]
|
||||||
|
public class IndexModel(ClientService clientService) : PageModel
|
||||||
|
{
|
||||||
|
public List<TaxBaik.Domain.Entities.Client> Items { get; private set; } = [];
|
||||||
|
|
||||||
|
public string? Search { get; set; }
|
||||||
|
public string? Status { get; set; }
|
||||||
|
public int PageSize { get; set; } = 10;
|
||||||
|
|
||||||
|
public async Task OnGetAsync(string? search, string? status, int pageSize = 10, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
Search = search;
|
||||||
|
Status = status;
|
||||||
|
PageSize = pageSize;
|
||||||
|
|
||||||
|
var (items, _) = await clientService.GetPagedAsync(1, pageSize, status, search, ct);
|
||||||
|
Items = items.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
@page
|
||||||
|
@model TaxBaik.Web.Pages.Admin.LogoutModel
|
||||||
|
@{
|
||||||
|
Layout = null;
|
||||||
|
}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ko">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="refresh" content="0;url=/admin/login" />
|
||||||
|
</head>
|
||||||
|
<body></body>
|
||||||
|
</html>
|
||||||
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
using TaxBaik.Web.Services;
|
||||||
|
|
||||||
|
namespace TaxBaik.Web.Pages.Admin;
|
||||||
|
|
||||||
|
[Authorize(AuthenticationSchemes = AdminAuthDefaults.Scheme)]
|
||||||
|
public class LogoutModel : PageModel
|
||||||
|
{
|
||||||
|
public async Task<IActionResult> OnGetAsync()
|
||||||
|
{
|
||||||
|
await HttpContext.SignOutAsync(AdminAuthDefaults.Scheme);
|
||||||
|
return RedirectToPage("/Admin/Login");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user