Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/api/query/getQueryDetail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ResponseType } from "..";
import fetchData from "../fetchData";

export default async function getQueryDetail(params: DetailParams) {
const url = `/admin-support/queries/${params.id}`;
const url = `/admin-support/inquiries/${params.id}`;

const response = await fetchData<ResponseType<QueryType>>({ url });
return response.data;
Expand Down
4 changes: 2 additions & 2 deletions src/api/query/getQueryList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import { QueryListType } from ".";
import fetchData from "../fetchData";

export type QueryListResponseType = {
quleroquisDto: QueryListType[];
inquiries: QueryListType[];
} & ResponsePaginationType;

export default async function getQueryList(params: ListParams) {
const { page, perPage } = params.pagination;
const filter = "all";
const url = `/admin-support/notices?page=${page}&limit=${perPage}&filter=${filter}`;
const url = `/admin-support/inquiries?page=${page}&limit=${perPage}&filter=${filter}`;

const response = await fetchData<ResponseType<QueryListResponseType>>({
url,
Expand Down
14 changes: 13 additions & 1 deletion src/api/query/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
import { AuthorType } from "..";

export type QueryType = {
id: number;
createdDate: "2025-01-04T19:38:11.707+09:00";
processed: boolean;
title: string;
user: AuthorType;
answer: null | string;
type: "기술 지원 관련";
content: string;
};

export type QueryListType = QueryType;
export type QueryListType = Pick<
QueryType,
"id" | "title" | "processed" | "user" | "createdDate"
>;
21 changes: 21 additions & 0 deletions src/api/query/postQueryAnswer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import fetchData from "../fetchData";
import { z } from "zod";

export const PostQueryAnswerSchema = z.object({
answer: z
.string()
.min(10, { message: "10글자 이상의 답변변을 입력해주세요." }),
});

export type PostQueryAnswerBodyType = z.infer<typeof PostQueryAnswerSchema>;

export default async function postQueryAnswer(
id: number,
body: PostQueryAnswerBodyType
) {
const url = `/admin-support/inquiries/${id}`;

const response = await fetchData({ url, method: "POST", body });

return response;
}
22 changes: 22 additions & 0 deletions src/components/ui/textarea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import * as React from "react";

import { cn } from "../../lib/utils";

const Textarea = React.forwardRef<
HTMLTextAreaElement,
React.ComponentProps<"textarea">
>(({ className, ...props }, ref) => {
return (
<textarea
className={cn(
"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className
)}
ref={ref}
{...props}
/>
);
});
Textarea.displayName = "Textarea";

export { Textarea };
12 changes: 12 additions & 0 deletions src/hooks/useLocationId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { DefaultPaths } from "../router/paths";
import { getParamsFromPath } from "../lib/path.utils";
import { useLocation } from "react-router-dom";

const useLocationId = () => {
const { pathname } = useLocation();
const id = +getParamsFromPath(DefaultPaths.QUERY.DETAIL, pathname).id;

return id;
};

export default useLocationId;
61 changes: 36 additions & 25 deletions src/pages/query/Detail.tsx
Original file line number Diff line number Diff line change
@@ -1,54 +1,65 @@
import APIErrorProvider, {
useAPIError,
} from "../../components/fallback/APIErrorProvider";
import APILoadingProvider, {
useAPILoading,
} from "../../components/fallback/APILoadingProvider";

import { DefaultPaths } from "../../router/paths";
import ErrorFallback from "../../components/fallback/ErrorFallback";
import ItemContainer from "../../components/Detail/ItemContainer";
import UIErrorBoundary from "../../components/fallback/UIErrorBoundary";
import LoadingFallback from "../../components/fallback/LoadingFallback";
import QueryAnswerForm from "./QueryAnswerForm";
import { getParamsFromPath } from "../../lib/path.utils";
import queryQueryOptions from "../../queries/queryQueryOptions";
import { useLocation } from "react-router-dom";
import { useQuery } from "@tanstack/react-query";

export default function QueryDetail() {
return (
<UIErrorBoundary>
<APIErrorProvider>
<APILoadingProvider>
<QueryDetailContent />
</APILoadingProvider>
</APIErrorProvider>
</UIErrorBoundary>
);
return <QueryDetailContent />;
}

function QueryDetailContent() {
const { setAPIError } = useAPIError();
const { setAPILoading } = useAPILoading();
const { pathname } = useLocation();
const id = +getParamsFromPath(DefaultPaths.QUERY.DETAIL, pathname).id;

const { data, error, isLoading } = useQuery({
...queryQueryOptions.getQueryDetail({ id: 1 }),
...queryQueryOptions.getQueryDetail({ id }),
});

if (isLoading) {
setAPILoading();
return;
return <LoadingFallback />;
}
if (error instanceof Error) {
setAPIError(error.message);
return;
return <ErrorFallback />;
}

if (!data) {
return null;
}

return (
<article className="flex flex-col gap-[20px]">
<h3 className="text-2xl">문의사항 세부사항</h3>
<div className="flex flex-col gap-[20px] relative max-w-[1200px]">
<div className="grid grid-cols-2">
<div className="grid grid-cols-4">
<ItemContainer label="문의사항 ID">
<div>{data?.id}</div>
</ItemContainer>
<ItemContainer label="문의사항 유형">
<div>{data?.type}</div>
</ItemContainer>
<ItemContainer label="문의사항 작성 유저 닉네임">
<div>{data.user.nickname}</div>
</ItemContainer>
<ItemContainer label="문의사항 작성 유저 이메일">
<div>{data.user.email}</div>
</ItemContainer>
</div>
<ItemContainer label="문의사항 내용">
<div dangerouslySetInnerHTML={{ __html: data.content }} />
</ItemContainer>
</div>
{data?.answer ? (
<ItemContainer label="문의사항 답변 내용">
<div>{data.answer}</div>
</ItemContainer>
) : (
<QueryAnswerForm />
)}
</article>
);
}
37 changes: 25 additions & 12 deletions src/pages/query/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,12 @@ import List from "../../components/List";
import LoadingFallback from "../../components/fallback/LoadingFallback";
import Pagination from "../../components/Pagination";
import { QueryListType } from "../../api/query";
import UIErrorBoundary from "../../components/fallback/UIErrorBoundary";
import queryQueryOptions from "../../queries/queryQueryOptions";
import usePagination from "../../components/Pagination/usePagination";
import { useQuery } from "@tanstack/react-query";

export default function QueryList() {
return (
<UIErrorBoundary>
<QueryListContent />
</UIErrorBoundary>
);
return <QueryListContent />;
}

const QueryListContent = () => {
Expand All @@ -40,14 +35,22 @@ const QueryListContent = () => {

return (
<List>
<List.Header totalCount={data.total} label="공지사항" />

<List.ColumnContainer headers={[]} row={5} />
{data.quleroquisDto.length === 0 ? (
<List.Header totalCount={data.total} label="문의사항" />
<List.ColumnContainer
headers={[
"문의사항 번호",
"문의사항 제목",
"작성자명",
"작성일",
"답변여부",
]}
row={5}
/>
{data.inquiries.length === 0 ? (
<Blank />
) : (
<List.RowContainer row={10}>
{data.quleroquisDto.map((query) => (
{data.inquiries.map((query) => (
<QueryListItem key={query.id} {...query} />
))}
</List.RowContainer>
Expand All @@ -63,13 +66,23 @@ const QueryListContent = () => {

type QueryListItemProps = QueryListType;

const QueryListItem = ({ id }: QueryListItemProps) => {
const QueryListItem = ({
id,
title,
user,
createdDate,
processed,
}: QueryListItemProps) => {
return (
<Link
to={`/queries/${id}`}
className="grid grid-cols-5 items-center h-[50px] m-[10px] hover:bg-lightGray rounded-[8px]"
>
<div className="text-center">{id}</div>
<div className="text-center">{title}</div>
<div className="text-center">{user.nickname}</div>
<div className="text-center">{createdDate}</div>
<div className="text-center">{processed ? "완료" : "미완료"}</div>
</Link>
);
};
39 changes: 39 additions & 0 deletions src/pages/query/QueryAnswerForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Button } from "../../components/ui/button";
import { Textarea } from "../../components/ui/textarea";
import postQueryAnswer from "../../api/query/postQueryAnswer";
import useLocationId from "../../hooks/useLocationId";
import { useMutation } from "@tanstack/react-query";
import { useState } from "react";

export default function QueryAnswerForm() {
const id = useLocationId();
const [answerContent, setAnserContent] = useState("");
const mutation = useMutation({
mutationFn: () => postQueryAnswer(id, { answer: answerContent }),
onSuccess: () => {
console.log("답변 완료");
},
onError: (error: Error) => {
console.error("업로드 실패:", error);
},
});

const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();

mutation.mutate();
};

return (
<form className="flex flex-col gap-[20px]" onSubmit={onSubmit}>
<label>문의사항 답변 내용</label>
<Textarea
value={answerContent}
onChange={(e) => {
setAnserContent(e.target.value);
}}
/>
<Button type="submit">답변 완료</Button>
</form>
);
}
5 changes: 3 additions & 2 deletions src/queries/queryQueryOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ const queryQueryOptions = {
"query",
"list",
params.pagination.page,
params.filter?.keyword,
params.pagination.perPage,
params.filter?.status,
],
queryFn: () => getQueryList(params),
}),
getQueryDetail: (params: DetailParams) =>
queryOptions({
queryKey: ["query", params.id],
queryKey: ["query", "detail", params.id],
queryFn: () => getQueryDetail(params),
}),
};
Expand Down
6 changes: 4 additions & 2 deletions src/router/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import MyPage from "../pages/my";
import NotFound from "../pages/errors/NotFound";
import NoticeDetail from "../pages/notice/Detail";
import NoticeList from "../pages/notice/List";
import QueryDetail from "../pages/query/Detail";
import QueryList from "../pages/query/List";
import RegisterPage from "../pages/auth/Regsiter";
import ReportList from "../pages/report/List";
import ThemeList from "../pages/theme/List";
Expand Down Expand Up @@ -101,11 +103,11 @@ const router = createBrowserRouter([
},
{
path: DefaultPaths.QUERY.LIST,
element: <div>문의사항 리스트 페이지</div>,
element: <QueryList />,
},
{
path: DefaultPaths.QUERY.DETAIL,
element: <div>문의사항 리스트 페이지</div>,
element: <QueryDetail />,
},
{
path: DefaultPaths.VERSION,
Expand Down
1 change: 1 addition & 0 deletions src/types/params.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ export interface DetailParams {
}
export interface FilterParams {
keyword: string;
status?: string;
}
Loading