Skip to content

Conversation

@kimgho
Copy link
Contributor

@kimgho kimgho commented Aug 23, 2025

✅ Linked Issue

🔍 What I did

  • 관리자가 작성한 답변 삭제 추가
  • api 스키마에 명시된 정렬 옵션 추가

Summary by CodeRabbit

  • New Features

    • Delete inquiry replies directly from the Inquiry Detail page (with loading state).
    • Enhanced inquiry list: server-backed status filtering (미응답/완료) and sorting (최신순/오래된순/제목순) alongside pagination.
  • Refactor

    • Moved filtering and sorting to the server for more accurate, consistent results across pages.
  • Bug Fixes

    • Improved error handling when loading inquiry lists to provide more reliable feedback.

@vercel
Copy link

vercel bot commented Aug 23, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
mosu-client Ready Ready Preview Comment Aug 23, 2025 5:44pm

@coderabbitai
Copy link

coderabbitai bot commented Aug 23, 2025

Walkthrough

Adds server-driven pagination/filtering/sorting for inquiry lists by introducing a params interface and updating the API, hook, and UI usage. Implements reply deletion via a new mutation, exposes deleteReply in the reply actions hook, and wires it into the inquiry detail page UI.

Changes

Cohort / File(s) Summary
Inquiry list API + hook
mosu-admin/src/api/inquiry/getInquiryList.ts
Introduces InquiryListParams; refactors getInquiryList to accept a single params object (page, size, status, sort, asc) with defaults; sends unified axios params; adds error handling; updates useInquiryList to accept and key by params.
Inquiry list UI consumption
mosu-admin/src/components/inquiry/InquiryTabContent.tsx
Replaces client-side filtering/sorting with server-side via apiParams; maps UI state to status/sort/asc; updates useInquiryList usage; feeds data?.content directly to table; totalPages from data?.totalPages.
Inquiry reply actions
mosu-admin/src/hooks/inquiry/useInquiryReplyAction.tsx
Adds delete reply mutation and deleteReply function; invalidates ["inquiry"] on delete; exposes deleteReply alongside create/update; minor formatting.
Inquiry detail page integration
mosu-admin/src/pages/InquiryDetailPage.tsx
Consumes deleteReply from hook; adds Delete button for existing answers; disables during loading; rendering adjustments.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Admin as Admin UI
  participant Comp as InquiryTabContent
  participant Hook as useInquiryList
  participant API as getInquiryList
  participant Server as Backend

  Admin->>Comp: Change filters/sort/pagination
  Comp->>Hook: useInquiryList({ page, size, status?, sort, asc })
  Hook->>API: getInquiryList(params)
  API->>Server: GET /inquiries?{page,size,status?,sort,asc}
  Server-->>API: 200 { content, totalPages, ... }
  API-->>Hook: data
  Hook-->>Comp: data
  Comp-->>Admin: Render table with data.content
  note over API,Server: Server-side filtering/sorting/paging
Loading
sequenceDiagram
  autonumber
  actor Admin as Admin UI
  participant Detail as InquiryDetailPage
  participant Actions as useInquiryReplyActions
  participant Mut as useDeleteInquiry
  participant Query as React Query
  participant Server as Backend

  Admin->>Detail: Click "Delete" on reply
  Detail->>Actions: deleteReply()
  Actions->>Mut: mutate(inquiryId)
  Mut->>Server: DELETE /inquiries/{id}/reply
  alt Success
    Server-->>Mut: 204 No Content
    Mut-->>Actions: onSuccess
    Actions->>Query: invalidate ["inquiry"]
    Query->>Server: Refetch affected lists
  else Error
    Server-->>Mut: 4xx/5xx
    Mut-->>Actions: onError(error)
    Actions-->>Detail: propagate error
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • toothlessdev

Poem

A rabbit taps keys with delicate might,
Paging and sorting now travel by byte.
Replies can vanish—poof!—on delete,
Queries refreshed, the list is neat.
Hippity-hop through code we wend,
Another tidy merge, tail-end. 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature#337

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions
Copy link

Messages
📖 ✅ PR 제목에 이슈 번호가 포함되어 있습니다.
📖 ✅ PR에 Reviewers가 지정되어 있습니다.
📖 ✅ PR에 라벨이 지정되어 있습니다.
📖 ✅ PR에 Assignees가 지정되어 있습니다.
📖 ✅ package.json에 변경사항이 없습니다.
📖 ✅ 브랜치 이름 'feature#337'이 컨벤션을 따릅니다.
📖 ✅ TypeScript 컴파일이 성공적으로 완료되었습니다.
📖 ✅ ESLint 검사 결과 문제가 없습니다.

📝 추가 및 변경된 파일

총 4개 파일 변경

└── 📂 mosu-admin/
    └── 📂 src/
        ├── 📂 api/
        │   └── 📂 inquiry/
        │       └── 📘 getInquiryList.ts
        ├── 📂 components/
        │   └── 📂 inquiry/
        │       └── ⚛️ InquiryTabContent.tsx
        ├── 📂 hooks/
        │   └── 📂 inquiry/
        │       └── ⚛️ useInquiryReplyAction.tsx
        └── 📂 pages/
            └── ⚛️ InquiryDetailPage.tsx

Generated by 🚫 dangerJS against 0e95360

@github-actions
Copy link

✅ Mosu Admin 빌드가 성공적으로 완료되었습니다!

📋 빌드 결과

  • ✅ DangerJS 검사 통과
  • ✅ React 앱 빌드 성공

🚀 다음 단계

코드 리뷰 후 머지하면 프로덕션에 배포됩니다.

@kimgho kimgho merged commit 75667d3 into main Aug 23, 2025
6 of 7 checks passed
@kimgho kimgho deleted the feature#337 branch August 23, 2025 17:45
@github-project-automation github-project-automation bot moved this from 진행중 to 완료 in mosu-client Aug 23, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
mosu-admin/src/api/inquiry/getInquiryList.ts (1)

75-77: Don’t swallow the original error; rethrow it with context

Rewrapping the error as a new Error() loses the HTTP status/message. Preserve it and optionally add context.

Apply:

-    } catch (error) {
-        console.log(error);
-        throw new Error();
+    } catch (error) {
+        console.error("getInquiryList failed:", error);
+        throw (error instanceof Error ? error : new Error("getInquiryList failed"));
     }
mosu-admin/src/pages/InquiryDetailPage.tsx (1)

21-32: Deletion state not reflected; on delete success, detail remains stale

  • isLoading only tracks create/update and ignores deletion. The Delete button may be clickable during an in-flight delete.
  • The delete mutation (in the hook) doesn’t invalidate the detail query or call onSuccess; the page won’t refresh to hide the deleted answer and won’t trigger your success toast.

Apply:

-    const { createReply, updateReply, deleteReply, isLoading } = useInquiryReplyActions({
+    const { createReply, updateReply, deleteReply, isLoading, isDeleting } = useInquiryReplyActions({
         inquiryId,
         onSuccess: () => {
             setMode("view");
             console.log("답변이 성공적으로 처리되었습니다.");
             // TODO: 성공 토스트 표시
         },

And update the Delete button disabled state (see below).
Additionally, update the hook to:

  • invalidate ["inquiry", inquiryId, "detail"] on delete success,
  • call onSuccess() on delete success,
  • expose isDeleting and include it in isLoading.
    See hook comments for the diff.
mosu-admin/src/hooks/inquiry/useInquiryReplyAction.tsx (2)

55-66: Invalidate list queries after create to reflect status changes immediately

After creating an answer, the inquiry’s status likely flips to "완료". Invalidate list queries too so list pages reflect the change without a hard refresh.

Apply:

                     onSuccess: () => {
                         queryClient.invalidateQueries({
                             queryKey: ["inquiry", inquiryId, "detail"],
                         });
+                        queryClient.invalidateQueries({
+                            queryKey: ["inquiry"],
+                        });
                         onSuccess?.();
                     },

117-124: Expose isDeleting and include it in isLoading for consistent button states

Make deletion state available to UIs and ensure isLoading covers all three operations.

Apply:

     return {
         createReply,
         updateReply,
-        deleteReply,
-        isCreating: createAnswerMutation.isPending,
-        isUpdating: updateAnswerMutation.isPending,
-        isLoading: createAnswerMutation.isPending || updateAnswerMutation.isPending,
+        deleteReply,
+        isCreating: createAnswerMutation.isPending,
+        isUpdating: updateAnswerMutation.isPending,
+        isDeleting: deleteAnswerMutation.isPending,
+        isLoading:
+            createAnswerMutation.isPending ||
+            updateAnswerMutation.isPending ||
+            deleteAnswerMutation.isPending,
     };
🧹 Nitpick comments (7)
mosu-admin/src/api/inquiry/getInquiryList.ts (3)

42-48: Tighten param typing for sort/status to prevent backend mismatches

Good addition. To catch typos at compile time, consider narrowing types:

  • status is already a union; keep it.
  • sort can be a union of allowed server fields (e.g., "id" | "title" | "createdAt" if supported). This prevents accidental invalid values from callers.

Apply this diff to narrow sort safely while keeping extensibility:

-export interface InquiryListParams {
+export interface InquiryListParams {
     page?: number;
     size?: number;
     status?: "PENDING" | "COMPLETED";
-    sort?: string;
+    sort?: "id" | "title" | "createdAt";
     asc?: boolean;
 }

58-67: Param assembly is fine; optionally omit undefined and defaults for cleaner URLs

Current approach works. If you want cleaner query strings, you can drop undefined and default-valued entries before sending.

Example (optional):

-        const params: Record<string, unknown> = {
-            page,
-            size,
-            sort,
-            asc,
-        };
-
-        if (status) {
-            params.status = status;
-        }
+        const params = Object.fromEntries(
+            Object.entries({ page, size, sort, asc, status }).filter(
+                ([, v]) => v !== undefined,
+            ),
+        );

80-84: Add query ergonomics: staleTime and keepPreviousData for smoother pagination

Since this query backs pagination, enable result reuse between pages and reduce refetch churn.

Apply:

 export const useInquiryList = (params: InquiryListParams = {}) => {
     return useSuspenseQuery({
         queryKey: ["inquiry", params],
-        queryFn: () => getInquiryList(params),
+        queryFn: () => getInquiryList(params),
+        staleTime: 10_000,
+        gcTime: 5 * 60_000,
+        placeholderData: (prev) => prev, // keepPreviousData equivalent with Suspense
     });
 };

Add the following import at the top if you opt for keepPreviousData utility instead:

import { useSuspenseQuery /*, keepPreviousData*/ } from "@tanstack/react-query";
mosu-admin/src/components/inquiry/InquiryTabContent.tsx (2)

48-52: Suspense query + manual loading UI mismatch

useInquiryList wraps useSuspenseQuery. With Suspense enabled, the component will suspend on load, so the isLoading branch likely won’t execute. Either:

  • keep Suspense and remove local isLoading UI here (use a Suspense boundary/Route fallback), or
  • switch to useQuery in the hook if you prefer in-component spinners.

92-92: Table rendering is fine; consider passing total count if pagination component needs it

If CommonPagination or the table needs total elements, you may pass data.totalElements too.

mosu-admin/src/pages/InquiryDetailPage.tsx (1)

75-81: Prefer real author metadata over hard-coded '관리자'

If the API provides the answer author, display it for auditability.

Apply:

-                                <PostContent
-                                    author="관리자"
+                                <PostContent
+                                    author={inquiryAnswer.author ?? "관리자"}
                                     createdAt={inquiryAnswer.createdAt}
                                     content={inquiryAnswer.content}
                                     title={inquiryAnswer.title}
                                     attachments={inquiryAnswer.attachments}
                                 />
mosu-admin/src/hooks/inquiry/useInquiryReplyAction.tsx (1)

86-97: Same for updates: invalidate list as well as detail

If the update affects content surfaced in list rows (e.g., title), refresh the list caches too.

Apply:

                     onSuccess: () => {
                         queryClient.invalidateQueries({
                             queryKey: ["inquiry", inquiryId, "detail"],
                         });
+                        queryClient.invalidateQueries({
+                            queryKey: ["inquiry"],
+                        });
                         onSuccess?.();
                     },
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ec781e1 and 0e95360.

📒 Files selected for processing (4)
  • mosu-admin/src/api/inquiry/getInquiryList.ts (2 hunks)
  • mosu-admin/src/components/inquiry/InquiryTabContent.tsx (2 hunks)
  • mosu-admin/src/hooks/inquiry/useInquiryReplyAction.tsx (3 hunks)
  • mosu-admin/src/pages/InquiryDetailPage.tsx (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
mosu-admin/src/hooks/inquiry/useInquiryReplyAction.tsx (1)
mosu-admin/src/api/inquiry/deleteInquiry.ts (1)
  • useDeleteInquiry (16-20)
mosu-admin/src/pages/InquiryDetailPage.tsx (2)
mosu-admin/src/hooks/inquiry/useInquiryReplyAction.tsx (1)
  • useInquiryReplyActions (13-125)
mosu-admin/src/components/PostContent.tsx (1)
  • PostContent (13-73)
mosu-admin/src/components/inquiry/InquiryTabContent.tsx (1)
mosu-admin/src/api/inquiry/getInquiryList.ts (1)
  • useInquiryList (80-85)
🔇 Additional comments (2)
mosu-admin/src/components/inquiry/InquiryTabContent.tsx (2)

39-46: LGTM on server-side param construction

Returning a single, memoized apiParams object ensures stable query keys and minimal re-renders.


24-37: Confirm backend sort field names for recency

I didn’t find any references to how the sort parameter is consumed by our API in the client or service layer, so it’s unclear whether "id" will actually sort by creation date. Please verify the backend contract—e.g. check the request payload in src/services/inquiryService.ts or our Swagger/OpenAPI spec—to confirm whether recency should use "id" or "createdAt".

• File: mosu-admin/src/components/inquiry/InquiryTabContent.tsx
• Lines 24–37: mapping "최신순"/"오래된순" to sort = "id"
• If the API expects createdAt for date‐based sorting, update those cases:

-   case "최신순":
-       sort = "id";
+   case "최신순":
+       sort = "createdAt";
        asc = false;
        break;
-   case "오래된순":
-       sort = "id";
+   case "오래된순":
+       sort = "createdAt";
        asc = true;
        break;

Comment on lines +50 to +56
export const getInquiryList = async ({
page = 0,
size = 10,
status,
sort = "id",
asc = false,
}: InquiryListParams = {}): Promise<InquiryListResponse> => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Signature refactor looks good; ensure all callers switched to object params

Moving to a single params object is the right call for server-driven sorting/filtering.

Run to confirm no legacy calls with positional args remain:


🏁 Script executed:

#!/bin/bash
# Find any lingering positional-arg calls like useInquiryList(x, y) or getInquiryList(x, y)
rg -nP '\buseInquiryList\s*\([^)]*,\s*[^)]*\)' -C2
rg -nP '\bgetInquiryList\s*\([^)]*,\s*[^)]*\)' -C2

Length of output: 454


Update lingering positional calls to use object parameters

We’ve detected a call site still using positional arguments for getInquiryList, which will break the new signature.

• mosu-app/src/entities/posts/services/getInquiryList.ts:

  • Line 78: change
    - queryFn: () => getInquiryList(page, size),
    + queryFn: () => getInquiryList({ page, size }),

This ensures all callers align with the refactored function signature.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const getInquiryList = async ({
page = 0,
size = 10,
status,
sort = "id",
asc = false,
}: InquiryListParams = {}): Promise<InquiryListResponse> => {
// In mosu-app/src/entities/posts/services/getInquiryList.ts, around line 78
// …other config…
queryFn: () => getInquiryList({ page, size }),
// …other config…
🤖 Prompt for AI Agents
In mosu-app/src/entities/posts/services/getInquiryList.ts around line 78, there
is a call still using positional arguments for getInquiryList which now expects
a single object parameter; update the call to pass an object with named
properties (e.g. { page, size, status, sort, asc }) instead of positional args,
making sure to include only the fields you intend to override (omit undefined to
allow defaults) so the new signature is honored.

Comment on lines +14 to 19
const apiParams = useMemo(() => {
let status: "PENDING" | "COMPLETED" | undefined = undefined;
if (!selectedFilters.includes("전체")) {
filtered = inquiryData.filter((item) => selectedFilters.includes(item.status));
if (selectedFilters.includes("미응답")) status = "PENDING";
else if (selectedFilters.includes("완료")) status = "COMPLETED";
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Bug: selecting both '미응답' and '완료' forces PENDING; should show all (no status filter)

With multiselect filters, if both are chosen (and "전체" is not), the current logic picks "PENDING" due to the first includes() check. This yields incorrect server filtering.

Apply:

-    const apiParams = useMemo(() => {
-        let status: "PENDING" | "COMPLETED" | undefined = undefined;
-        if (!selectedFilters.includes("전체")) {
-            if (selectedFilters.includes("미응답")) status = "PENDING";
-            else if (selectedFilters.includes("완료")) status = "COMPLETED";
-        }
+    const apiParams = useMemo(() => {
+        let status: "PENDING" | "COMPLETED" | undefined = undefined;
+        if (!selectedFilters.includes("전체")) {
+            const picked = selectedFilters.filter((f) => f === "미응답" || f === "완료");
+            if (picked.length === 1) {
+                status = picked[0] === "미응답" ? "PENDING" : "COMPLETED";
+            }
+            // picked.length === 0 or 2 -> no status filter (show all)
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const apiParams = useMemo(() => {
let status: "PENDING" | "COMPLETED" | undefined = undefined;
if (!selectedFilters.includes("전체")) {
filtered = inquiryData.filter((item) => selectedFilters.includes(item.status));
if (selectedFilters.includes("미응답")) status = "PENDING";
else if (selectedFilters.includes("완료")) status = "COMPLETED";
}
const apiParams = useMemo(() => {
let status: "PENDING" | "COMPLETED" | undefined = undefined;
if (!selectedFilters.includes("전체")) {
const picked = selectedFilters.filter((f) => f === "미응답" || f === "완료");
if (picked.length === 1) {
status = picked[0] === "미응답" ? "PENDING" : "COMPLETED";
}
// picked.length === 0 or 2 -> no status filter (show all)
}
// ...rest of useMemo body unchanged
}, [selectedFilters]);
🤖 Prompt for AI Agents
In mosu-admin/src/components/inquiry/InquiryTabContent.tsx around lines 14 to
19, the status calculation currently checks includes("미응답") first and forces
status = "PENDING" when both "미응답" and "완료" are selected; change the logic so
that when both status options are selected (and "전체" is not), status remains
undefined (no server-side status filter). Concretely, detect the combination: if
selectedFilters includes both "미응답" and "완료" then leave status undefined;
otherwise set status to "PENDING" or "COMPLETED" depending on which single
option is present (keeping existing "전체" bypass).

import { usePostInquiryAnswer } from "@/api/inquiry/postInquiryAnswer";
import { useUpdateInquiryAnswer } from "@/api/inquiry/updateInquiryAnswer";
import { uploadFile } from "@/api/file/uploadFile";
import { useDeleteInquiry } from "@/api/inquiry/deleteInquiry";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Confirm API: useDeleteInquiry deletes an answer vs the entire inquiry

Naming suggests it deletes the inquiry, but here it is used to delete the admin’s answer. Ensure the endpoint and function name are aligned to prevent destructive operations.

If it deletes the whole inquiry, replace with a dedicated delete-answer endpoint/hook (e.g., useDeleteInquiryAnswer) or adjust the implementation.


🏁 Script executed:

#!/bin/bash
# Inspect deleteInquiry API to ensure it targets the answer resource
rg -n "deleteInquiry" -C3 mosu-admin/src/api/inquiry
cat mosu-admin/src/api/inquiry/deleteInquiry.ts

Length of output: 1622


Rename API hook to reflect deleting an inquiry answer, not the inquiry itself

The useDeleteInquiry hook currently calls DELETE on /admin/inquiry/{postId}/answer, so it removes only the administrator’s answer, not the entire inquiry. To avoid confusion (and potential destructive mistakes), please refactor:

• mosu-admin/src/api/inquiry/deleteInquiry.ts
– Rename file to deleteInquiryAnswer.ts
– Change exported function from deleteInquirydeleteInquiryAnswer
– Change hook export from useDeleteInquiryuseDeleteInquiryAnswer

• mosu-admin/src/hooks/inquiry/useInquiryReplyAction.tsx
– Update import to:
diff - import { useDeleteInquiry } from "@/api/inquiry/deleteInquiry"; + import { useDeleteInquiryAnswer } from "@/api/inquiry/deleteInquiryAnswer";
– Update any call sites from useDeleteInquiry(...)useDeleteInquiryAnswer(...)

This alignment will make it crystal-clear that you’re deleting an answer, not the inquiry itself.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { useDeleteInquiry } from "@/api/inquiry/deleteInquiry";
import { useDeleteInquiryAnswer } from "@/api/inquiry/deleteInquiryAnswer";
🤖 Prompt for AI Agents
In mosu-admin/src/hooks/inquiry/useInquiryReplyAction.tsx around line 5, the
imported hook useDeleteInquiry is misnamed because the API actually DELETEs the
inquiry answer; rename the API file mosu-admin/src/api/inquiry/deleteInquiry.ts
→ mosu-admin/src/api/inquiry/deleteInquiryAnswer.ts, rename the exported
function deleteInquiry → deleteInquiryAnswer and the hook export
useDeleteInquiry → useDeleteInquiryAnswer, then update this file to import {
useDeleteInquiryAnswer } from "@/api/inquiry/deleteInquiryAnswer" and update all
call sites in this hook (and any other usages) from useDeleteInquiry(...) →
useDeleteInquiryAnswer(...).

Comment on lines +103 to +115
const deleteReply = async () => {
deleteAnswerMutation.mutate(inquiryId, {
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ["inquiry"],
});
},
onError: (error) => {
console.error("답변 삭제 실패:", error);
onError?.(error);
},
});
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Deletion flow: invalidate detail, propagate success, and handle optimistic UX

Currently only ["inquiry"] is invalidated and onSuccess isn’t invoked. This leaves the detail page stale.

Apply:

-    const deleteReply = async () => {
-        deleteAnswerMutation.mutate(inquiryId, {
-            onSuccess: () => {
-                queryClient.invalidateQueries({
-                    queryKey: ["inquiry"],
-                });
-            },
-            onError: (error) => {
-                console.error("답변 삭제 실패:", error);
-                onError?.(error);
-            },
-        });
-    };
+    const deleteReply = async () => {
+        deleteAnswerMutation.mutate(inquiryId, {
+            onSuccess: () => {
+                // Refresh detail view and any lists
+                queryClient.invalidateQueries({ queryKey: ["inquiry", inquiryId, "detail"] });
+                queryClient.invalidateQueries({ queryKey: ["inquiry"] });
+                onSuccess?.();
+            },
+            onError: (error) => {
+                console.error("답변 삭제 실패:", error);
+                onError?.(error as Error);
+            },
+        });
+    };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const deleteReply = async () => {
deleteAnswerMutation.mutate(inquiryId, {
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ["inquiry"],
});
},
onError: (error) => {
console.error("답변 삭제 실패:", error);
onError?.(error);
},
});
};
const deleteReply = async () => {
deleteAnswerMutation.mutate(inquiryId, {
onSuccess: () => {
// Refresh detail view and any lists
queryClient.invalidateQueries({ queryKey: ["inquiry", inquiryId, "detail"] });
queryClient.invalidateQueries({ queryKey: ["inquiry"] });
onSuccess?.();
},
onError: (error) => {
console.error("답변 삭제 실패:", error);
onError?.(error as Error);
},
});
};
🤖 Prompt for AI Agents
mosu-admin/src/hooks/inquiry/useInquiryReplyAction.tsx lines 103-115: The
deleteReply onSuccess only invalidates ["inquiry"] and never calls the provided
onSuccess, leaving the detail page stale and preventing caller UX updates;
update the onSuccess to invalidate both the inquiry list and the specific
inquiry detail (e.g. ["inquiry", inquiryId] or however detail key is shaped) and
call onSuccess() so the caller can react, and for better UX implement an
optimistic update: cancel relevant queries, snapshot and setQueryData to remove
the reply immediately, then invalidate both queries on success and restore
snapshot on error while calling onError.

Comment on lines +82 to +88
<Button
onClick={() => deleteReply()}
variant="outline"
disabled={isLoading}
>
답변 삭제하기
</Button>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add a lightweight confirm and reflect deleting state in the button

Prevent accidental deletions and disable the button while deleting.

Apply:

-                                <Button
-                                    onClick={() => deleteReply()}
+                                <Button
+                                    onClick={() => {
+                                        if (confirm("정말 삭제하시겠습니까?")) deleteReply();
+                                    }}
                                     variant="outline"
-                                    disabled={isLoading}
+                                    disabled={isLoading || isDeleting}
                                 >
                                     답변 삭제하기
                                 </Button>

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In mosu-admin/src/pages/InquiryDetailPage.tsx around lines 82-88, the "답변 삭제하기"
button lacks a confirmation prompt and does not reflect a deleting state; wrap
the delete flow in an onClick handler that first shows a lightweight confirm
(e.g., window.confirm) and returns early if cancelled, add a local isDeleting
state (boolean) to set true before calling deleteReply and false in finally, set
the Button's disabled prop to isLoading || isDeleting and optionally change its
label to indicate progress (e.g., "삭제 중...") so the UI prevents accidental
deletions and shows the deleting state.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: 완료

Development

Successfully merging this pull request may close these issues.

2 participants