Implement admin CRUD for announcements inquiries and faqs
TaxBaik CI/CD / build-and-deploy (push) Successful in 54s
TaxBaik CI/CD / build-and-deploy (push) Successful in 54s
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
@page
|
||||
@model TaxBaik.Web.Pages.Admin.Announcements.CreateModel
|
||||
@{ ViewData["Title"] = "공지사항 작성"; }
|
||||
|
||||
<section class="admin-page-shell">
|
||||
<partial name="_AdminPageHeader" model='("Content", "공지사항 작성", "Tabler 표준 폼으로 공지사항을 등록합니다.", null, null)' />
|
||||
|
||||
<form method="post" class="card">
|
||||
<div class="card-body">
|
||||
<partial name="_ValidationSummary" />
|
||||
<div class="row g-3">
|
||||
<div class="col-lg-8">
|
||||
<label asp-for="Input.Title" class="form-label"></label>
|
||||
<input asp-for="Input.Title" class="form-control" />
|
||||
<span asp-validation-for="Input.Title" class="text-danger small"></span>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<label asp-for="Input.DisplayType" class="form-label"></label>
|
||||
<select asp-for="Input.DisplayType" class="form-select">
|
||||
<option value="info">info</option>
|
||||
<option value="success">success</option>
|
||||
<option value="warning">warning</option>
|
||||
<option value="danger">danger</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label asp-for="Input.Content" class="form-label"></label>
|
||||
<textarea asp-for="Input.Content" class="form-control" rows="8"></textarea>
|
||||
<span asp-validation-for="Input.Content" class="text-danger small"></span>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label asp-for="Input.SortOrder" class="form-label"></label>
|
||||
<input asp-for="Input.SortOrder" class="form-control" type="number" />
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label asp-for="Input.StartsAt" class="form-label"></label>
|
||||
<input asp-for="Input.StartsAt" class="form-control" type="datetime-local" />
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label asp-for="Input.EndsAt" class="form-label"></label>
|
||||
<input asp-for="Input.EndsAt" class="form-control" type="datetime-local" />
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-check">
|
||||
<input asp-for="Input.IsActive" class="form-check-input" type="checkbox" />
|
||||
<span class="form-check-label">활성</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer d-flex justify-content-end gap-2">
|
||||
<a class="btn btn-outline-secondary" href="/admin/announcements">취소</a>
|
||||
<button class="btn btn-primary" type="submit">저장</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
@@ -0,0 +1,26 @@
|
||||
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.Announcements;
|
||||
|
||||
[Authorize(AuthenticationSchemes = AdminAuthDefaults.Scheme)]
|
||||
public class CreateModel(AnnouncementService announcementService) : PageModel
|
||||
{
|
||||
[BindProperty]
|
||||
public AnnouncementDto Input { get; set; } = new();
|
||||
|
||||
public IActionResult OnGet() => Page();
|
||||
|
||||
public async Task<IActionResult> OnPostAsync(CancellationToken ct)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return Page();
|
||||
|
||||
await announcementService.CreateAsync(Input, ct);
|
||||
return RedirectToPage("/Admin/Announcements/Index");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
@page "{id:int}"
|
||||
@model TaxBaik.Web.Pages.Admin.Announcements.EditModel
|
||||
@{ ViewData["Title"] = "공지사항 수정"; }
|
||||
|
||||
<section class="admin-page-shell">
|
||||
<partial name="_AdminPageHeader" model='("Content", "공지사항 수정", "기존 공지사항을 Tabler 표준 폼으로 수정합니다.", null, null)' />
|
||||
|
||||
<form method="post" class="card">
|
||||
<div class="card-body">
|
||||
<partial name="_ValidationSummary" />
|
||||
<input type="hidden" asp-for="Input.Id" />
|
||||
<div class="row g-3">
|
||||
<div class="col-lg-8">
|
||||
<label asp-for="Input.Title" class="form-label"></label>
|
||||
<input asp-for="Input.Title" class="form-control" />
|
||||
<span asp-validation-for="Input.Title" class="text-danger small"></span>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<label asp-for="Input.DisplayType" class="form-label"></label>
|
||||
<select asp-for="Input.DisplayType" class="form-select">
|
||||
<option value="info">info</option>
|
||||
<option value="success">success</option>
|
||||
<option value="warning">warning</option>
|
||||
<option value="danger">danger</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label asp-for="Input.Content" class="form-label"></label>
|
||||
<textarea asp-for="Input.Content" class="form-control" rows="8"></textarea>
|
||||
<span asp-validation-for="Input.Content" class="text-danger small"></span>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label asp-for="Input.SortOrder" class="form-label"></label>
|
||||
<input asp-for="Input.SortOrder" class="form-control" type="number" />
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label asp-for="Input.StartsAt" class="form-label"></label>
|
||||
<input asp-for="Input.StartsAt" class="form-control" type="datetime-local" />
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label asp-for="Input.EndsAt" class="form-label"></label>
|
||||
<input asp-for="Input.EndsAt" class="form-control" type="datetime-local" />
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-check">
|
||||
<input asp-for="Input.IsActive" class="form-check-input" type="checkbox" />
|
||||
<span class="form-check-label">활성</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer 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/announcements">취소</a>
|
||||
<button class="btn btn-primary" type="submit">저장</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
@@ -0,0 +1,54 @@
|
||||
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.Announcements;
|
||||
|
||||
[Authorize(AuthenticationSchemes = AdminAuthDefaults.Scheme)]
|
||||
public class EditModel(AnnouncementService announcementService) : PageModel
|
||||
{
|
||||
[BindProperty(SupportsGet = true)]
|
||||
public int Id { get; set; }
|
||||
|
||||
[BindProperty]
|
||||
public AnnouncementDto Input { get; set; } = new();
|
||||
|
||||
public async Task<IActionResult> OnGetAsync(CancellationToken ct)
|
||||
{
|
||||
var item = await announcementService.GetByIdAsync(Id, ct);
|
||||
if (item is null)
|
||||
return NotFound();
|
||||
|
||||
Input = new AnnouncementDto
|
||||
{
|
||||
Id = item.Id,
|
||||
Title = item.Title,
|
||||
Content = item.Content,
|
||||
DisplayType = item.DisplayType,
|
||||
IsActive = item.IsActive,
|
||||
StartsAt = item.StartsAt,
|
||||
EndsAt = item.EndsAt,
|
||||
SortOrder = item.SortOrder
|
||||
};
|
||||
|
||||
return Page();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostAsync(CancellationToken ct)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return Page();
|
||||
|
||||
await announcementService.UpdateAsync(Input, ct);
|
||||
return RedirectToPage("/Admin/Announcements/Index");
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostDeleteAsync(CancellationToken ct)
|
||||
{
|
||||
await announcementService.DeleteAsync(Id, ct);
|
||||
return RedirectToPage("/Admin/Announcements/Index");
|
||||
}
|
||||
}
|
||||
@@ -3,27 +3,39 @@
|
||||
@{ ViewData["Title"] = "공지사항"; }
|
||||
|
||||
<section class="admin-page-shell">
|
||||
<partial name="_AdminPageHeader" model='("Content", "공지사항 관리", "공지 목록을 서버 렌더링으로 표시합니다.", null, null)' />
|
||||
<partial name="_AdminPageHeader" model='("Content", "공지사항 관리", "공지 목록을 서버 렌더링으로 표시합니다.", "/admin/announcements/create", "새 공지 작성")' />
|
||||
<div class="card">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-vcenter card-table">
|
||||
<table class="table table-vcenter card-table table-hover">
|
||||
<thead>
|
||||
<tr><th>제목</th><th>유형</th><th>활성</th><th>정렬</th></tr>
|
||||
<tr>
|
||||
<th>제목</th>
|
||||
<th>유형</th>
|
||||
<th>활성</th>
|
||||
<th>정렬</th>
|
||||
<th class="text-end">작업</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@if (Model.Items.Count == 0)
|
||||
{
|
||||
<tr><td colspan="4"><partial name="_EmptyState" model="공지사항이 없습니다." /></td></tr>
|
||||
<tr><td colspan="5"><partial name="_EmptyState" model="공지사항이 없습니다." /></td></tr>
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var item in Model.Items)
|
||||
{
|
||||
<tr>
|
||||
<td>@item.Title</td>
|
||||
<td class="fw-semibold">@item.Title</td>
|
||||
<td>@item.DisplayType</td>
|
||||
<td><partial name="_StatusBadge" model="@(item.IsActive ? "active" : "inactive")" /></td>
|
||||
<td>@item.SortOrder</td>
|
||||
<td class="text-end">
|
||||
<div class="btn-list flex-nowrap justify-content-end">
|
||||
<a class="btn btn-sm btn-outline-secondary" href="/admin/announcements/@item.Id">상세</a>
|
||||
<a class="btn btn-sm btn-primary" href="/admin/announcements/@item.Id/edit">수정</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
@page
|
||||
@model TaxBaik.Web.Pages.Admin.Faqs.CreateModel
|
||||
@{ ViewData["Title"] = "FAQ 작성"; }
|
||||
|
||||
<section class="admin-page-shell">
|
||||
<partial name="_AdminPageHeader" model='("Support", "FAQ 작성", "Tabler 표준 폼으로 FAQ를 등록합니다.", null, null)' />
|
||||
|
||||
<form method="post" class="card">
|
||||
<div class="card-body">
|
||||
<partial name="_ValidationSummary" />
|
||||
<div class="row g-3">
|
||||
<div class="col-lg-8">
|
||||
<label asp-for="Input.Question" class="form-label"></label>
|
||||
<input asp-for="Input.Question" class="form-control" />
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<label asp-for="Input.Category" class="form-label"></label>
|
||||
<input asp-for="Input.Category" class="form-control" list="faq-categories" />
|
||||
<datalist id="faq-categories">
|
||||
@foreach (var category in Model.Categories)
|
||||
{
|
||||
<option value="@category"></option>
|
||||
}
|
||||
</datalist>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label asp-for="Input.Answer" class="form-label"></label>
|
||||
<textarea asp-for="Input.Answer" class="form-control" rows="10"></textarea>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label asp-for="Input.SortOrder" class="form-label"></label>
|
||||
<input asp-for="Input.SortOrder" class="form-control" type="number" />
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-check">
|
||||
<input asp-for="Input.IsActive" class="form-check-input" type="checkbox" />
|
||||
<span class="form-check-label">활성</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer d-flex justify-content-end gap-2">
|
||||
<a class="btn btn-outline-secondary" href="/admin/faqs">취소</a>
|
||||
<button class="btn btn-primary" type="submit">저장</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
@@ -0,0 +1,32 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using TaxBaik.Domain.Entities;
|
||||
using TaxBaik.Application.Services;
|
||||
using TaxBaik.Web.Services;
|
||||
|
||||
namespace TaxBaik.Web.Pages.Admin.Faqs;
|
||||
|
||||
[Authorize(AuthenticationSchemes = AdminAuthDefaults.Scheme)]
|
||||
public class CreateModel(FaqService faqService) : PageModel
|
||||
{
|
||||
[BindProperty]
|
||||
public Faq Input { get; set; } = new();
|
||||
|
||||
public IReadOnlyList<string> Categories => FaqService.Categories;
|
||||
|
||||
public IActionResult OnGet()
|
||||
{
|
||||
Input.IsActive = true;
|
||||
return Page();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostAsync(CancellationToken ct)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return Page();
|
||||
|
||||
await faqService.CreateAsync(Input, ct);
|
||||
return RedirectToPage("/Admin/Faqs/Index");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
@page "{id:int}"
|
||||
@model TaxBaik.Web.Pages.Admin.Faqs.DetailModel
|
||||
@{ ViewData["Title"] = "FAQ 상세"; }
|
||||
|
||||
<section class="admin-page-shell">
|
||||
<partial name="_AdminPageHeader" model='("Support", "FAQ 상세", "FAQ의 질문, 답변, 상태를 확인합니다.", null, null)' />
|
||||
|
||||
@if (Model.Item is null)
|
||||
{
|
||||
<partial name="_EmptyState" model="FAQ를 찾을 수 없습니다." />
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-8">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<dl class="row details-grid mb-0">
|
||||
<dt class="col-sm-3">질문</dt>
|
||||
<dd class="col-sm-9">@Model.Item.Question</dd>
|
||||
<dt class="col-sm-3">답변</dt>
|
||||
<dd class="col-sm-9"><div class="pre-wrap">@Model.Item.Answer</div></dd>
|
||||
<dt class="col-sm-3">카테고리</dt>
|
||||
<dd class="col-sm-9">@Model.Item.Category</dd>
|
||||
<dt class="col-sm-3">활성</dt>
|
||||
<dd class="col-sm-9"><partial name="_StatusBadge" model="@(Model.Item.IsActive ? "active" : "inactive")" /></dd>
|
||||
<dt class="col-sm-3">정렬</dt>
|
||||
<dd class="col-sm-9">@Model.Item.SortOrder</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div class="card">
|
||||
<div class="card-body d-grid gap-2">
|
||||
<a class="btn btn-primary" href="/admin/faqs/@Model.Item.Id/edit">수정</a>
|
||||
<a class="btn btn-outline-secondary" href="/admin/faqs">목록</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
@@ -0,0 +1,20 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using TaxBaik.Application.Services;
|
||||
using TaxBaik.Domain.Entities;
|
||||
using TaxBaik.Web.Services;
|
||||
|
||||
namespace TaxBaik.Web.Pages.Admin.Faqs;
|
||||
|
||||
[Authorize(AuthenticationSchemes = AdminAuthDefaults.Scheme)]
|
||||
public class DetailModel(FaqService faqService) : PageModel
|
||||
{
|
||||
public Faq? Item { get; private set; }
|
||||
|
||||
public async Task<IActionResult> OnGetAsync(int id, CancellationToken ct)
|
||||
{
|
||||
Item = await faqService.GetByIdAsync(id, ct);
|
||||
return Page();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
@page "{id:int}"
|
||||
@model TaxBaik.Web.Pages.Admin.Faqs.EditModel
|
||||
@{ ViewData["Title"] = "FAQ 수정"; }
|
||||
|
||||
<section class="admin-page-shell">
|
||||
<partial name="_AdminPageHeader" model='("Support", "FAQ 수정", "기존 FAQ를 Tabler 표준 폼으로 수정합니다.", null, null)' />
|
||||
|
||||
<form method="post" class="card">
|
||||
<div class="card-body">
|
||||
<partial name="_ValidationSummary" />
|
||||
<input type="hidden" asp-for="Input.Id" />
|
||||
<div class="row g-3">
|
||||
<div class="col-lg-8">
|
||||
<label asp-for="Input.Question" class="form-label"></label>
|
||||
<input asp-for="Input.Question" class="form-control" />
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<label asp-for="Input.Category" class="form-label"></label>
|
||||
<input asp-for="Input.Category" class="form-control" list="faq-categories" />
|
||||
<datalist id="faq-categories">
|
||||
@foreach (var category in Model.Categories)
|
||||
{
|
||||
<option value="@category"></option>
|
||||
}
|
||||
</datalist>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label asp-for="Input.Answer" class="form-label"></label>
|
||||
<textarea asp-for="Input.Answer" class="form-control" rows="10"></textarea>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label asp-for="Input.SortOrder" class="form-label"></label>
|
||||
<input asp-for="Input.SortOrder" class="form-control" type="number" />
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-check">
|
||||
<input asp-for="Input.IsActive" class="form-check-input" type="checkbox" />
|
||||
<span class="form-check-label">활성</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer d-flex justify-content-between gap-2">
|
||||
<button class="btn btn-outline-danger" type="submit" formaction="?handler=Delete" formmethod="post" onclick="return confirm('정말 삭제하시겠습니까?');">삭제</button>
|
||||
<div class="d-flex gap-2">
|
||||
<a class="btn btn-outline-secondary" href="/admin/faqs">취소</a>
|
||||
<button class="btn btn-primary" type="submit">저장</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
@@ -0,0 +1,54 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using TaxBaik.Domain.Entities;
|
||||
using TaxBaik.Application.Services;
|
||||
using TaxBaik.Web.Services;
|
||||
|
||||
namespace TaxBaik.Web.Pages.Admin.Faqs;
|
||||
|
||||
[Authorize(AuthenticationSchemes = AdminAuthDefaults.Scheme)]
|
||||
public class EditModel(FaqService faqService) : PageModel
|
||||
{
|
||||
[BindProperty(SupportsGet = true)]
|
||||
public int Id { get; set; }
|
||||
|
||||
[BindProperty]
|
||||
public Faq Input { get; set; } = new();
|
||||
|
||||
public IReadOnlyList<string> Categories => FaqService.Categories;
|
||||
|
||||
public async Task<IActionResult> OnGetAsync(CancellationToken ct)
|
||||
{
|
||||
var faq = await faqService.GetByIdAsync(Id, ct);
|
||||
if (faq is null)
|
||||
return NotFound();
|
||||
|
||||
Input = new Faq
|
||||
{
|
||||
Id = faq.Id,
|
||||
Question = faq.Question,
|
||||
Answer = faq.Answer,
|
||||
Category = faq.Category,
|
||||
SortOrder = faq.SortOrder,
|
||||
IsActive = faq.IsActive
|
||||
};
|
||||
|
||||
return Page();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostAsync(CancellationToken ct)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return Page();
|
||||
|
||||
await faqService.UpdateAsync(Input, ct);
|
||||
return RedirectToPage("/Admin/Faqs/Index");
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostDeleteAsync(CancellationToken ct)
|
||||
{
|
||||
await faqService.DeleteAsync(Id, ct);
|
||||
return RedirectToPage("/Admin/Faqs/Index");
|
||||
}
|
||||
}
|
||||
@@ -3,17 +3,17 @@
|
||||
@{ ViewData["Title"] = "FAQ"; }
|
||||
|
||||
<section class="admin-page-shell">
|
||||
<partial name="_AdminPageHeader" model='("Support", "FAQ 관리", "질문과 답변을 관리합니다.", null, null)' />
|
||||
<partial name="_AdminPageHeader" model='("Support", "FAQ 관리", "질문과 답변을 관리합니다.", "/admin/faqs/create", "새 FAQ 작성")' />
|
||||
<div class="card">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-vcenter card-table">
|
||||
<table class="table table-vcenter card-table table-hover">
|
||||
<thead>
|
||||
<tr><th>질문</th><th>카테고리</th><th>활성</th><th>정렬</th></tr>
|
||||
<tr><th>질문</th><th>카테고리</th><th>활성</th><th>정렬</th><th class="text-end">작업</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@if (Model.Items.Count == 0)
|
||||
{
|
||||
<tr><td colspan="4"><partial name="_EmptyState" model="FAQ가 없습니다." /></td></tr>
|
||||
<tr><td colspan="5"><partial name="_EmptyState" model="FAQ가 없습니다." /></td></tr>
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -24,6 +24,12 @@
|
||||
<td>@item.Category</td>
|
||||
<td><partial name="_StatusBadge" model="@(item.IsActive ? "active" : "inactive")" /></td>
|
||||
<td>@item.SortOrder</td>
|
||||
<td class="text-end">
|
||||
<div class="btn-list flex-nowrap justify-content-end">
|
||||
<a class="btn btn-sm btn-outline-secondary" href="/admin/faqs/@item.Id">상세</a>
|
||||
<a class="btn btn-sm btn-primary" href="/admin/faqs/@item.Id/edit">수정</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,47 @@
|
||||
@page
|
||||
@model TaxBaik.Web.Pages.Admin.Inquiries.CreateModel
|
||||
@{ ViewData["Title"] = "문의 등록"; }
|
||||
<section class="container py-5"><h1 class="h2 fw-bold">문의 등록</h1></section>
|
||||
|
||||
<section class="admin-page-shell">
|
||||
<partial name="_AdminPageHeader" model='("Customer Requests", "문의 등록", "관리자가 직접 상담 요청을 등록할 수 있습니다.", null, null)' />
|
||||
|
||||
<form method="post" class="card">
|
||||
<div class="card-body">
|
||||
<partial name="_ValidationSummary" />
|
||||
<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" />
|
||||
</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-6">
|
||||
<label asp-for="Input.ServiceType" class="form-label"></label>
|
||||
<input asp-for="Input.ServiceType" class="form-control" />
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label asp-for="Input.Message" class="form-label"></label>
|
||||
<textarea asp-for="Input.Message" class="form-control" rows="8"></textarea>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label asp-for="Input.Status" class="form-label"></label>
|
||||
<input asp-for="Input.Status" class="form-control" />
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label asp-for="Input.AdminMemo" class="form-label"></label>
|
||||
<textarea asp-for="Input.AdminMemo" class="form-control" rows="4"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer d-flex justify-content-end gap-2">
|
||||
<a class="btn btn-outline-secondary" href="/admin/inquiries">취소</a>
|
||||
<button class="btn btn-primary" type="submit">저장</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
@@ -1,7 +1,44 @@
|
||||
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.Inquiries;
|
||||
[Authorize(AuthenticationSchemes = AdminAuthDefaults.Scheme)]
|
||||
public class CreateModel : PageModel { }
|
||||
|
||||
namespace TaxBaik.Web.Pages.Admin.Inquiries;
|
||||
|
||||
[Authorize(AuthenticationSchemes = AdminAuthDefaults.Scheme)]
|
||||
public class CreateModel(InquiryService inquiryService) : PageModel
|
||||
{
|
||||
[BindProperty]
|
||||
public UpdateInquiryDto Input { get; set; } = new();
|
||||
|
||||
public IActionResult OnGet()
|
||||
{
|
||||
Input.Status = "new";
|
||||
return Page();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostAsync(CancellationToken ct)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return Page();
|
||||
|
||||
var inquiryId = await inquiryService.SubmitAsync(
|
||||
Input.Name,
|
||||
Input.Phone,
|
||||
string.IsNullOrWhiteSpace(Input.ServiceType) ? "기타" : Input.ServiceType,
|
||||
Input.Message,
|
||||
Input.Email,
|
||||
suppressNotification: true,
|
||||
ct: ct);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(Input.Status))
|
||||
await inquiryService.UpdateStatusAsync(inquiryId, Input.Status, changedBy: User.Identity?.Name, ct: ct);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(Input.AdminMemo))
|
||||
await inquiryService.UpdateAdminMemoAsync(inquiryId, Input.AdminMemo, ct);
|
||||
|
||||
return RedirectToPage("/Admin/Inquiries/Index");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,51 @@
|
||||
@page "{id:int}"
|
||||
@model TaxBaik.Web.Pages.Admin.Inquiries.DetailModel
|
||||
@{ ViewData["Title"] = "문의 상세"; }
|
||||
<section class="container py-5"><h1 class="h2 fw-bold">문의 상세</h1></section>
|
||||
|
||||
<section class="admin-page-shell">
|
||||
<partial name="_AdminPageHeader" model='("Customer Requests", "문의 상세", "문의 요청과 처리 내역을 확인합니다.", null, null)' />
|
||||
|
||||
@if (Model.Inquiry is null)
|
||||
{
|
||||
<partial name="_EmptyState" model="문의 정보를 찾을 수 없습니다." />
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-8">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<dl class="row details-grid mb-0">
|
||||
<dt class="col-sm-3">이름</dt>
|
||||
<dd class="col-sm-9">@Model.Inquiry.Name</dd>
|
||||
<dt class="col-sm-3">전화</dt>
|
||||
<dd class="col-sm-9">@Model.Inquiry.Phone</dd>
|
||||
<dt class="col-sm-3">이메일</dt>
|
||||
<dd class="col-sm-9">@Model.Inquiry.Email</dd>
|
||||
<dt class="col-sm-3">서비스</dt>
|
||||
<dd class="col-sm-9">@Model.Inquiry.ServiceType</dd>
|
||||
<dt class="col-sm-3">상태</dt>
|
||||
<dd class="col-sm-9"><partial name="_StatusBadge" model="Model.Inquiry.Status" /></dd>
|
||||
<dt class="col-sm-3">메시지</dt>
|
||||
<dd class="col-sm-9"><div class="pre-wrap">@Model.Inquiry.Message</div></dd>
|
||||
<dt class="col-sm-3">관리 메모</dt>
|
||||
<dd class="col-sm-9">@Model.Inquiry.AdminMemo</dd>
|
||||
<dt class="col-sm-3">등록일</dt>
|
||||
<dd class="col-sm-9">@Model.Inquiry.CreatedAt.ToString("yyyy-MM-dd HH:mm")</dd>
|
||||
<dt class="col-sm-3">수정일</dt>
|
||||
<dd class="col-sm-9">@Model.Inquiry.UpdatedAt?.ToString("yyyy-MM-dd HH:mm")</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div class="card">
|
||||
<div class="card-body d-grid gap-2">
|
||||
<a class="btn btn-primary" href="/admin/inquiries/@Model.Inquiry.Id/edit">수정</a>
|
||||
<a class="btn btn-outline-secondary" href="/admin/inquiries">목록</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
|
||||
@@ -1,7 +1,20 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using TaxBaik.Application.Services;
|
||||
using TaxBaik.Domain.Entities;
|
||||
using TaxBaik.Web.Services;
|
||||
namespace TaxBaik.Web.Pages.Admin.Inquiries;
|
||||
[Authorize(AuthenticationSchemes = AdminAuthDefaults.Scheme)]
|
||||
public class DetailModel : PageModel { }
|
||||
|
||||
namespace TaxBaik.Web.Pages.Admin.Inquiries;
|
||||
|
||||
[Authorize(AuthenticationSchemes = AdminAuthDefaults.Scheme)]
|
||||
public class DetailModel(InquiryService inquiryService) : PageModel
|
||||
{
|
||||
public Inquiry? Inquiry { get; private set; }
|
||||
|
||||
public async Task<IActionResult> OnGetAsync(int id, CancellationToken ct)
|
||||
{
|
||||
Inquiry = await inquiryService.GetByIdAsync(id, ct);
|
||||
return Page();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,51 @@
|
||||
@page "{id:int}"
|
||||
@model TaxBaik.Web.Pages.Admin.Inquiries.EditModel
|
||||
@{ ViewData["Title"] = "문의 수정"; }
|
||||
<section class="container py-5"><h1 class="h2 fw-bold">문의 수정</h1></section>
|
||||
|
||||
<section class="admin-page-shell">
|
||||
<partial name="_AdminPageHeader" model='("Customer Requests", "문의 수정", "문의 내용을 수정하고 상태를 갱신합니다.", null, null)' />
|
||||
|
||||
<form method="post" class="card">
|
||||
<div class="card-body">
|
||||
<partial name="_ValidationSummary" />
|
||||
<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" />
|
||||
</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-6">
|
||||
<label asp-for="Input.ServiceType" class="form-label"></label>
|
||||
<input asp-for="Input.ServiceType" class="form-control" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label asp-for="Input.Status" class="form-label"></label>
|
||||
<input asp-for="Input.Status" class="form-control" />
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label asp-for="Input.Message" class="form-label"></label>
|
||||
<textarea asp-for="Input.Message" class="form-control" rows="8"></textarea>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label asp-for="Input.AdminMemo" class="form-label"></label>
|
||||
<textarea asp-for="Input.AdminMemo" class="form-control" rows="4"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer d-flex justify-content-between gap-2">
|
||||
<button class="btn btn-outline-danger" type="submit" formaction="?handler=Delete" formmethod="post" onclick="return confirm('정말 삭제하시겠습니까?');">삭제</button>
|
||||
<div class="d-flex gap-2">
|
||||
<a class="btn btn-outline-secondary" href="/admin/inquiries/@Model.Id">취소</a>
|
||||
<button class="btn btn-primary" type="submit">저장</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
@@ -1,7 +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.Inquiries;
|
||||
[Authorize(AuthenticationSchemes = AdminAuthDefaults.Scheme)]
|
||||
public class EditModel : PageModel { }
|
||||
|
||||
namespace TaxBaik.Web.Pages.Admin.Inquiries;
|
||||
|
||||
[Authorize(AuthenticationSchemes = AdminAuthDefaults.Scheme)]
|
||||
public class EditModel(InquiryService inquiryService) : PageModel
|
||||
{
|
||||
[BindProperty(SupportsGet = true)]
|
||||
public int Id { get; set; }
|
||||
|
||||
[BindProperty]
|
||||
public UpdateInquiryDto Input { get; set; } = new();
|
||||
|
||||
public async Task<IActionResult> OnGetAsync(CancellationToken ct)
|
||||
{
|
||||
var inquiry = await inquiryService.GetByIdAsync(Id, ct);
|
||||
if (inquiry is null)
|
||||
return NotFound();
|
||||
|
||||
Input = new UpdateInquiryDto
|
||||
{
|
||||
Name = inquiry.Name,
|
||||
Phone = inquiry.Phone,
|
||||
Email = inquiry.Email,
|
||||
ServiceType = inquiry.ServiceType,
|
||||
Message = inquiry.Message,
|
||||
Status = inquiry.Status,
|
||||
AdminMemo = inquiry.AdminMemo
|
||||
};
|
||||
|
||||
return Page();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostAsync(CancellationToken ct)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return Page();
|
||||
|
||||
var updated = await inquiryService.UpdateAsync(Id, Input, ct);
|
||||
if (updated is null)
|
||||
return NotFound();
|
||||
|
||||
return RedirectToPage("/Admin/Inquiries/Detail", new { id = Id });
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostDeleteAsync(CancellationToken ct)
|
||||
{
|
||||
await inquiryService.DeleteAsync(Id, ct);
|
||||
return RedirectToPage("/Admin/Inquiries/Index");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
</form>
|
||||
<div class="card">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-vcenter card-table">
|
||||
<table class="table table-vcenter card-table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>이름</th>
|
||||
@@ -27,12 +27,13 @@
|
||||
<th>서비스</th>
|
||||
<th>상태</th>
|
||||
<th>등록일</th>
|
||||
<th class="text-end">작업</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@if (Model.Items.Count == 0)
|
||||
{
|
||||
<tr><td colspan="5"><partial name="_EmptyState" model="문의 목록이 없습니다." /></td></tr>
|
||||
<tr><td colspan="6"><partial name="_EmptyState" model="문의 목록이 없습니다." /></td></tr>
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -44,6 +45,12 @@
|
||||
<td>@item.ServiceType</td>
|
||||
<td><partial name="_StatusBadge" model="@(item.Status)" /></td>
|
||||
<td>@item.CreatedAt.ToString("yyyy-MM-dd")</td>
|
||||
<td class="text-end">
|
||||
<div class="btn-list flex-nowrap justify-content-end">
|
||||
<a class="btn btn-sm btn-outline-secondary" href="/admin/inquiries/@item.Id">상세</a>
|
||||
<a class="btn btn-sm btn-primary" href="/admin/inquiries/@item.Id/edit">수정</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user