diff --git a/docs/ADMIN_LEGACY_BLAZOR.md b/docs/ADMIN_LEGACY_BLAZOR.md new file mode 100644 index 0000000..81b6148 --- /dev/null +++ b/docs/ADMIN_LEGACY_BLAZOR.md @@ -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/*` + diff --git a/docs/INDEX.md b/docs/INDEX.md index d02158f..1ef3894 100644 --- a/docs/INDEX.md +++ b/docs/INDEX.md @@ -8,6 +8,7 @@ | --- | --- | --- | | [ENGINEERING_HARNESS.md](./ENGINEERING_HARNESS.md) | 아키텍처, 코드 품질, 배포, 데이터 정합성 하네스 | 방향성 변경 또는 반복 위반 발견 | | [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 | 새 스택 채택 또는 렌더/인증 기준 변경 | | [DOUZONE_UX_GUIDE.md](./DOUZONE_UX_GUIDE.md) | 더존식 어드민 UX 원칙, 템플릿, 컴포넌트, 서빙 규칙 | 화면 패턴 변경 또는 신규 템플릿 추가 | | [OPS_LOG_DIAGNOSTICS_ROADMAP.md](./OPS_LOG_DIAGNOSTICS_ROADMAP.md) | 관리자 대시보드/클라이언트 로그 진단 로드맵 | 로그 진단 흐름 또는 관측성 작업 변경 | @@ -21,6 +22,7 @@ | 영역 | 라우트/파일 | 기준 | | --- | --- | --- | | 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 Inquiry | `/admin/inquiries`, `/admin/inquiries/create`, `/admin/inquiries/{id}/edit` | 현행 Blazor/Web API 경로, 전환 대상 | | Public API | `/api/*` | 현행 JWT 경로, 브라우저 어드민과 분리 | diff --git a/src/TaxBaik.Web/Pages/Admin/Customers/Create.cshtml b/src/TaxBaik.Web/Pages/Admin/Customers/Create.cshtml new file mode 100644 index 0000000..97aa732 --- /dev/null +++ b/src/TaxBaik.Web/Pages/Admin/Customers/Create.cshtml @@ -0,0 +1,66 @@ +@page +@model TaxBaik.Web.Pages.Admin.Customers.CreateModel +@{ + ViewData["Title"] = "고객 등록"; +} + +
+
+

Customer Management

+

고객 등록

+
+ +
+
+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
+ diff --git a/src/TaxBaik.Web/Pages/Admin/Customers/Create.cshtml.cs b/src/TaxBaik.Web/Pages/Admin/Customers/Create.cshtml.cs new file mode 100644 index 0000000..667dccb --- /dev/null +++ b/src/TaxBaik.Web/Pages/Admin/Customers/Create.cshtml.cs @@ -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 OnPostAsync(CancellationToken ct) + { + if (!ModelState.IsValid) + return Page(); + + await clientService.CreateAsync(Input, ct); + return RedirectToPage("/Admin/Customers/Index"); + } +} + diff --git a/src/TaxBaik.Web/Pages/Admin/Customers/Detail.cshtml b/src/TaxBaik.Web/Pages/Admin/Customers/Detail.cshtml new file mode 100644 index 0000000..7c82a59 --- /dev/null +++ b/src/TaxBaik.Web/Pages/Admin/Customers/Detail.cshtml @@ -0,0 +1,44 @@ +@page "{id:int}" +@model TaxBaik.Web.Pages.Admin.Customers.DetailModel +@{ + ViewData["Title"] = "고객 상세"; +} + +
+ @if (Model.Client is null) + { +
고객을 찾을 수 없습니다.
+ } + else + { +
+
+

Customer Management

+

@Model.Client.Name

+

고객 기본 정보와 변경 이력을 확인합니다.

+
+
+ 수정 + 목록 +
+
+ +
+
+
+
회사명
@Model.Client.CompanyName
+
전화
@Model.Client.Phone
+
이메일
@Model.Client.Email
+
상태
@Model.Client.Status
+
서비스 유형
@Model.Client.ServiceType
+
세금 유형
@Model.Client.TaxType
+
유입 경로
@Model.Client.Source
+
메모
@Model.Client.Memo
+
생성일
@Model.Client.CreatedAt.ToString("yyyy-MM-dd HH:mm")
+
수정일
@Model.Client.UpdatedAt.ToString("yyyy-MM-dd HH:mm")
+
+
+
+ } +
+ diff --git a/src/TaxBaik.Web/Pages/Admin/Customers/Detail.cshtml.cs b/src/TaxBaik.Web/Pages/Admin/Customers/Detail.cshtml.cs new file mode 100644 index 0000000..2dbfb1c --- /dev/null +++ b/src/TaxBaik.Web/Pages/Admin/Customers/Detail.cshtml.cs @@ -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); + } +} diff --git a/src/TaxBaik.Web/Pages/Admin/Customers/Edit.cshtml b/src/TaxBaik.Web/Pages/Admin/Customers/Edit.cshtml new file mode 100644 index 0000000..a42fb64 --- /dev/null +++ b/src/TaxBaik.Web/Pages/Admin/Customers/Edit.cshtml @@ -0,0 +1,65 @@ +@page "{id:int}" +@model TaxBaik.Web.Pages.Admin.Customers.EditModel +@{ + ViewData["Title"] = "고객 수정"; +} + +
+
+

Customer Management

+

고객 수정

+
+ +
+
+
+ +
+
+ + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
diff --git a/src/TaxBaik.Web/Pages/Admin/Customers/Edit.cshtml.cs b/src/TaxBaik.Web/Pages/Admin/Customers/Edit.cshtml.cs new file mode 100644 index 0000000..428104e --- /dev/null +++ b/src/TaxBaik.Web/Pages/Admin/Customers/Edit.cshtml.cs @@ -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 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 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 OnPostDeleteAsync(CancellationToken ct) + { + await clientService.DeleteAsync(Id, ct); + return RedirectToPage("/Admin/Customers/Index"); + } +} + diff --git a/src/TaxBaik.Web/Pages/Admin/Customers/Index.cshtml b/src/TaxBaik.Web/Pages/Admin/Customers/Index.cshtml new file mode 100644 index 0000000..aad63eb --- /dev/null +++ b/src/TaxBaik.Web/Pages/Admin/Customers/Index.cshtml @@ -0,0 +1,90 @@ +@page +@model TaxBaik.Web.Pages.Admin.Customers.IndexModel +@{ + ViewData["Title"] = "고객 목록"; +} + +
+
+
+

Customer Management

+

고객 목록

+

검색, 상태 필터, 페이지네이션 기준의 고객 목록입니다.

+
+ 고객 등록 +
+ +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + 초기화 +
+
+
+
+ +
+
+ + + + + + + + + + + + + @if (Model.Items.Count == 0) + { + + + + } + else + { + foreach (var item in Model.Items) + { + + + + + + + + + } + } + +
고객명회사명전화상태등록일작업
고객 데이터가 없습니다.
@item.Name@item.CompanyName@item.Phone@item.Status@item.CreatedAt.ToString("yyyy-MM-dd") + 상세 + 수정 +
+
+
+
+ diff --git a/src/TaxBaik.Web/Pages/Admin/Customers/Index.cshtml.cs b/src/TaxBaik.Web/Pages/Admin/Customers/Index.cshtml.cs new file mode 100644 index 0000000..ee3efc3 --- /dev/null +++ b/src/TaxBaik.Web/Pages/Admin/Customers/Index.cshtml.cs @@ -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 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(); + } +} diff --git a/src/TaxBaik.Web/Pages/Admin/Logout.cshtml b/src/TaxBaik.Web/Pages/Admin/Logout.cshtml new file mode 100644 index 0000000..e6734db --- /dev/null +++ b/src/TaxBaik.Web/Pages/Admin/Logout.cshtml @@ -0,0 +1,14 @@ +@page +@model TaxBaik.Web.Pages.Admin.LogoutModel +@{ + Layout = null; +} + + + + + + + + + diff --git a/src/TaxBaik.Web/Pages/Admin/Logout.cshtml.cs b/src/TaxBaik.Web/Pages/Admin/Logout.cshtml.cs new file mode 100644 index 0000000..590e226 --- /dev/null +++ b/src/TaxBaik.Web/Pages/Admin/Logout.cshtml.cs @@ -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 OnGetAsync() + { + await HttpContext.SignOutAsync(AdminAuthDefaults.Scheme); + return RedirectToPage("/Admin/Login"); + } +} +