diff --git a/TaxBaik.Web/Components/Admin/Pages/Inquiries/InquiryDetail.razor b/TaxBaik.Web/Components/Admin/Pages/Inquiries/InquiryDetail.razor index f2b0080..be5432f 100644 --- a/TaxBaik.Web/Components/Admin/Pages/Inquiries/InquiryDetail.razor +++ b/TaxBaik.Web/Components/Admin/Pages/Inquiries/InquiryDetail.razor @@ -1,8 +1,7 @@ @page "/admin/inquiries/{InquiryId:int}" @attribute [Authorize] -@using TaxBaik.Application.Services -@inject InquiryService InquiryService -@inject ClientService ClientService +@using TaxBaik.Web.Services +@inject IInquiryBrowserClient InquiryClient @inject NavigationManager Navigation @inject ISnackbar Snackbar @@ -114,7 +113,7 @@ else protected override async Task OnInitializedAsync() { - inquiry = await InquiryService.GetByIdAsync(InquiryId); + inquiry = await InquiryClient.GetByIdAsync(InquiryId); adminMemo = inquiry?.AdminMemo ?? ""; } @@ -123,22 +122,43 @@ else if (inquiry == null) return; try { - await InquiryService.UpdateStatusAsync(inquiry.Id, status, "관리자"); - inquiry.Status = status; - Snackbar.Add("상태가 변경되었습니다.", Severity.Success); + var success = await InquiryClient.UpdateStatusAsync(inquiry.Id, status); + if (success) + { + inquiry.Status = status; + Snackbar.Add("상태가 변경되었습니다.", Severity.Success); + } + else + { + Snackbar.Add("상태 변경에 실패했습니다.", Severity.Error); + } } - catch (ValidationException ex) + catch (Exception ex) { - Snackbar.Add(ex.Message, Severity.Error); + Snackbar.Add($"오류: {ex.Message}", Severity.Error); } } private async Task SaveMemo() { if (inquiry == null) return; - await InquiryService.UpdateAdminMemoAsync(inquiry.Id, adminMemo); - inquiry.AdminMemo = adminMemo; - Snackbar.Add("메모가 저장되었습니다.", Severity.Success); + try + { + var success = await InquiryClient.UpdateAdminMemoAsync(inquiry.Id, adminMemo); + if (success) + { + inquiry.AdminMemo = adminMemo; + Snackbar.Add("메모가 저장되었습니다.", Severity.Success); + } + else + { + Snackbar.Add("메모 저장에 실패했습니다.", Severity.Error); + } + } + catch (Exception ex) + { + Snackbar.Add($"오류: {ex.Message}", Severity.Error); + } } private async Task ConvertToClient() @@ -146,12 +166,22 @@ else if (inquiry == null) return; try { - var clientId = await ClientService.CreateFromInquiryAsync(inquiry.Name, inquiry.Phone, inquiry.ServiceType); - await InquiryService.LinkClientAsync(inquiry.Id, clientId); - await InquiryService.UpdateStatusAsync(inquiry.Id, "consulting", "관리자"); - inquiry.ClientId = clientId; - inquiry.Status = "consulting"; - Snackbar.Add("고객 카드가 생성되었습니다.", Severity.Success); + var clientId = await InquiryClient.ConvertToClientAsync( + inquiry.Id, + inquiry.Name, + inquiry.Phone, + inquiry.ServiceType); + + if (clientId > 0) + { + inquiry.ClientId = clientId; + inquiry.Status = "consulting"; + Snackbar.Add("고객 카드가 생성되었습니다.", Severity.Success); + } + else + { + Snackbar.Add("고객 카드 생성에 실패했습니다.", Severity.Error); + } } catch (Exception ex) { diff --git a/TaxBaik.Web/Controllers/InquiryController.cs b/TaxBaik.Web/Controllers/InquiryController.cs index 4ed1b03..3dd8b9a 100644 --- a/TaxBaik.Web/Controllers/InquiryController.cs +++ b/TaxBaik.Web/Controllers/InquiryController.cs @@ -10,10 +10,12 @@ namespace TaxBaik.Web.Controllers; public class InquiryController : ControllerBase { private readonly InquiryService _inquiryService; + private readonly ClientService _clientService; - public InquiryController(InquiryService inquiryService) + public InquiryController(InquiryService inquiryService, ClientService clientService) { _inquiryService = inquiryService; + _clientService = clientService; } [HttpPost] @@ -76,6 +78,54 @@ public class InquiryController : ControllerBase return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest }); } } + + [HttpPut("{id}/memo")] + [Authorize] + public async Task UpdateAdminMemo(int id, [FromBody] UpdateAdminMemoRequest request) + { + var inquiry = await _inquiryService.GetByIdAsync(id); + if (inquiry == null) + return NotFound(new ProblemDetails { Title = "문의를 찾을 수 없습니다.", Status = StatusCodes.Status404NotFound }); + + try + { + await _inquiryService.UpdateAdminMemoAsync(id, request.AdminMemo); + return Ok(new { message = "메모가 저장되었습니다." }); + } + catch (Exception ex) + { + return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest }); + } + } + + [HttpPost("{id}/convert-to-client")] + [Authorize] + public async Task ConvertToClient(int id, [FromBody] ConvertToClientRequest request) + { + var inquiry = await _inquiryService.GetByIdAsync(id); + if (inquiry == null) + return NotFound(new ProblemDetails { Title = "문의를 찾을 수 없습니다.", Status = StatusCodes.Status404NotFound }); + + if (inquiry.ClientId != null) + return BadRequest(new ProblemDetails { Title = "이미 고객 카드가 연결되어 있습니다.", Status = StatusCodes.Status400BadRequest }); + + try + { + var clientId = await _clientService.CreateFromInquiryAsync( + request.Name ?? inquiry.Name, + request.Phone ?? inquiry.Phone, + request.ServiceType ?? inquiry.ServiceType); + + await _inquiryService.LinkClientAsync(inquiry.Id, clientId); + await _inquiryService.UpdateStatusAsync(inquiry.Id, "consulting", User.FindFirstValue(ClaimTypes.Name) ?? "system"); + + return Ok(new { clientId, message = "고객 카드가 생성되었습니다." }); + } + catch (Exception ex) + { + return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest }); + } + } } public class SubmitInquiryRequest @@ -91,3 +141,15 @@ public class UpdateStatusRequest { public string Status { get; set; } = string.Empty; } + +public class UpdateAdminMemoRequest +{ + public string? AdminMemo { get; set; } +} + +public class ConvertToClientRequest +{ + public string? Name { get; set; } + public string? Phone { get; set; } + public string? ServiceType { get; set; } +} diff --git a/TaxBaik.Web/Program.cs b/TaxBaik.Web/Program.cs index 0007730..03cda51 100644 --- a/TaxBaik.Web/Program.cs +++ b/TaxBaik.Web/Program.cs @@ -75,7 +75,10 @@ builder.Services.AddHttpClient(clie client.BaseAddress = new Uri("http://localhost:5001/taxbaik/api/"); }) .AddHttpMessageHandler(); -builder.Services.AddHttpClient() +builder.Services.AddHttpClient(client => +{ + client.BaseAddress = new Uri("http://localhost:5001/taxbaik/api/"); +}) .AddHttpMessageHandler(); // UI & 캐시 diff --git a/TaxBaik.Web/Services/InquiryBrowserClient.cs b/TaxBaik.Web/Services/InquiryBrowserClient.cs index b5553bf..20028ce 100644 --- a/TaxBaik.Web/Services/InquiryBrowserClient.cs +++ b/TaxBaik.Web/Services/InquiryBrowserClient.cs @@ -13,6 +13,8 @@ public interface IInquiryBrowserClient Task<(IEnumerable Items, int Total)> GetPagedAsync(int page = 1, int pageSize = 20, CancellationToken ct = default); Task GetByIdAsync(int id, CancellationToken ct = default); Task UpdateStatusAsync(int id, string status, CancellationToken ct = default); + Task UpdateAdminMemoAsync(int id, string adminMemo, CancellationToken ct = default); + Task ConvertToClientAsync(int id, string name, string phone, string serviceType, CancellationToken ct = default); } public class InquiryBrowserClient : IInquiryBrowserClient @@ -32,7 +34,7 @@ public class InquiryBrowserClient : IInquiryBrowserClient try { var result = await _http.GetFromJsonAsync( - $"http://localhost:5001/taxbaik/api/inquiry?page={page}&pageSize={pageSize}", + $"inquiry?page={page}&pageSize={pageSize}", cancellationToken: ct); return result != null @@ -51,7 +53,7 @@ public class InquiryBrowserClient : IInquiryBrowserClient try { return await _http.GetFromJsonAsync( - $"http://localhost:5001/taxbaik/api/inquiry/{id}", + $"inquiry/{id}", cancellationToken: ct); } catch (HttpRequestException ex) @@ -67,7 +69,7 @@ public class InquiryBrowserClient : IInquiryBrowserClient { var request = new { status }; var response = await _http.PutAsJsonAsync( - $"http://localhost:5001/taxbaik/api/inquiry/{id}/status", + $"inquiry/{id}/status", request, cancellationToken: ct); @@ -80,6 +82,51 @@ public class InquiryBrowserClient : IInquiryBrowserClient } } + public async Task UpdateAdminMemoAsync(int id, string adminMemo, CancellationToken ct = default) + { + try + { + var request = new { adminMemo }; + var response = await _http.PutAsJsonAsync( + $"inquiry/{id}/memo", + request, + cancellationToken: ct); + + return response.IsSuccessStatusCode; + } + catch (HttpRequestException ex) + { + _logger.LogError(ex, "Failed to update inquiry {InquiryId} memo", id); + throw; + } + } + + public async Task ConvertToClientAsync(int id, string name, string phone, string serviceType, CancellationToken ct = default) + { + try + { + var response = await _http.PostAsJsonAsync( + $"inquiry/{id}/convert-to-client", + new { name, phone, serviceType }, + cancellationToken: ct); + + if (!response.IsSuccessStatusCode) + return 0; + + var content = await response.Content.ReadAsStringAsync(ct); + var result = System.Text.Json.JsonSerializer.Deserialize( + content, + new System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + return result?.ClientId ?? 0; + } + catch (HttpRequestException ex) + { + _logger.LogError(ex, "Failed to convert inquiry {InquiryId} to client", id); + throw; + } + } + private class InquiryPagedResponse { public List Data { get; set; } = []; @@ -87,4 +134,9 @@ public class InquiryBrowserClient : IInquiryBrowserClient public int Page { get; set; } public int PageSize { get; set; } } + + private class ConvertToClientResponse + { + public int ClientId { get; set; } + } }