From e3940f72c88e221eeaeba3520031d90beabbca95 Mon Sep 17 00:00:00 2001 From: keyy1315 Date: Tue, 13 May 2025 09:22:35 +0900 Subject: [PATCH] feat: implement Qsp Inquriy API Request, Response type --- src/app/api/qna/detail/route.ts | 15 ++ src/app/api/qna/list/route.ts | 17 ++ src/app/api/qna/save/route.ts | 15 ++ src/app/inquiry/list/page.tsx | 4 +- src/components/inquiry/Answer.tsx | 28 ++- src/components/inquiry/Detail.tsx | 48 +++-- src/components/inquiry/InquiryDetail.tsx | 73 -------- src/components/inquiry/InquiryFilter.tsx | 20 -- src/components/inquiry/InquiryItems.tsx | 21 --- src/components/inquiry/InquiryList.tsx | 171 ------------------ src/components/inquiry/InquiryWriteForm.tsx | 67 ------- src/components/inquiry/ListTable.tsx | 93 ---------- .../inquiry/{ => list}/ListForm.tsx | 0 src/components/inquiry/list/ListTable.tsx | 76 ++++++++ src/hooks/useInquiry.ts | 60 ++++++ src/store/inquiryFilterStore.ts | 66 +++---- src/types/Inquiry.ts | 89 +++++++++ 17 files changed, 340 insertions(+), 523 deletions(-) create mode 100644 src/app/api/qna/detail/route.ts create mode 100644 src/app/api/qna/list/route.ts create mode 100644 src/app/api/qna/save/route.ts delete mode 100644 src/components/inquiry/InquiryDetail.tsx delete mode 100644 src/components/inquiry/InquiryFilter.tsx delete mode 100644 src/components/inquiry/InquiryItems.tsx delete mode 100644 src/components/inquiry/InquiryList.tsx delete mode 100644 src/components/inquiry/InquiryWriteForm.tsx delete mode 100644 src/components/inquiry/ListTable.tsx rename src/components/inquiry/{ => list}/ListForm.tsx (100%) create mode 100644 src/components/inquiry/list/ListTable.tsx create mode 100644 src/hooks/useInquiry.ts create mode 100644 src/types/Inquiry.ts diff --git a/src/app/api/qna/detail/route.ts b/src/app/api/qna/detail/route.ts new file mode 100644 index 0000000..a9b670e --- /dev/null +++ b/src/app/api/qna/detail/route.ts @@ -0,0 +1,15 @@ +import { queryStringFormatter } from '@/utils/common-utils' +import axios from 'axios' +import { NextResponse } from 'next/server' + +export const QSP_URL = 'http://localhost:8080' +export async function GET(request: Request) { + const body = await request.json() + + const response = await axios.get(`${QSP_URL}/qna/detail?${queryStringFormatter(body)}`) + + if (response.status === 200) { + return NextResponse.json(response.data) + } + return NextResponse.json({ error: 'Failed to fetch qna detail' }, { status: response.status }) +} diff --git a/src/app/api/qna/list/route.ts b/src/app/api/qna/list/route.ts new file mode 100644 index 0000000..9e0f651 --- /dev/null +++ b/src/app/api/qna/list/route.ts @@ -0,0 +1,17 @@ +import axios from 'axios' +import { NextResponse } from 'next/server' +import { queryStringFormatter } from '@/utils/common-utils' +import { QSP_URL } from '../detail/route' + +export async function GET(request: Request) { + const { searchParams } = new URL(request.url) + console.log('searchParams::: ', searchParams) + + const response = await axios.get(`${QSP_URL}/qna/list?${queryStringFormatter(searchParams)}`) + + if (response.status === 200) { + return NextResponse.json(response.data) + } + + return NextResponse.json({ error: 'Failed to fetch qna list' }, { status: response.status }) +} diff --git a/src/app/api/qna/save/route.ts b/src/app/api/qna/save/route.ts new file mode 100644 index 0000000..8f29873 --- /dev/null +++ b/src/app/api/qna/save/route.ts @@ -0,0 +1,15 @@ +import axios from 'axios' +import { NextResponse } from 'next/server' +import { QSP_URL } from '../detail/route' + +export async function POST(request: Request) { + const body = await request.json() + + const response = await axios.post(`${QSP_URL}/qna/save`, body) + + if (response.status === 200) { + return NextResponse.json(response.data) + } + + return NextResponse.json({ error: 'Failed to save qna' }, { status: response.status }) +} diff --git a/src/app/inquiry/list/page.tsx b/src/app/inquiry/list/page.tsx index 93fb334..7e7654e 100644 --- a/src/app/inquiry/list/page.tsx +++ b/src/app/inquiry/list/page.tsx @@ -1,5 +1,5 @@ -import ListForm from '@/components/inquiry/ListForm' -import ListTable from '@/components/inquiry/ListTable' +import ListForm from '@/components/inquiry/list/ListForm' +import ListTable from '@/components/inquiry/list/ListTable' export default function page() { return ( diff --git a/src/components/inquiry/Answer.tsx b/src/components/inquiry/Answer.tsx index c1a5ec9..5628f36 100644 --- a/src/components/inquiry/Answer.tsx +++ b/src/components/inquiry/Answer.tsx @@ -1,35 +1,31 @@ 'use client' -export default function Answer() { +import { Inquiry } from '@/types/Inquiry' + +export default function Answer({ inquiryDetail }: { inquiryDetail: Inquiry }) { return ( <>
Hanwha Japan 回答
- 佐藤一貴/ 2025.04.02 16:54:00 + {inquiryDetail?.ansRegNm}/ {inquiryDetail?.ansRegDt}
回答
-
- 一次側接続は, 自動切替開閉器と住宅分電盤昼間遮断器との間に蓄電システム遮断器を配線する方法です. 二次側接続は, - 住宅分電盤週間ブレーカの二次側に蓄電システムブレーカを接続する -
+
{inquiryDetail?.ansContents}
ファイル添付
    -
  • - -
  • -
  • - -
  • + {inquiryDetail?.ansListFile?.map((file) => ( +
  • + +
  • + ))}
diff --git a/src/components/inquiry/Detail.tsx b/src/components/inquiry/Detail.tsx index d41d5a0..51fe7d3 100644 --- a/src/components/inquiry/Detail.tsx +++ b/src/components/inquiry/Detail.tsx @@ -2,19 +2,22 @@ import { useState } from 'react' import Answer from './Answer' +import { useInquiry } from '@/hooks/useInquiry' +import { useRouter } from 'next/navigation' export default function Detail() { //todo: 답변 완료 표시를 위해 임시로 추가 해 놓은 state // 추후에 api 작업 완료후 삭제 // 답변 완료 클래스 & 하단 답변내용 출력도 - const [inquiry, setInquiry] = useState(false) + const { inquiryDetail } = useInquiry() + const router = useRouter() return ( <>
-
回答完了
+
回答完了
@@ -25,59 +28,50 @@ export default function Detail() { - + - + - + - + - +
登録日2025.04.10{inquiryDetail?.regDt}
作者Hong gi{inquiryDetail?.regNm}
販売店interplug{inquiryDetail?.storeNm}
施工店interplugs{inquiryDetail?.compCd}
E-mailHong@interplug.co.kr{inquiryDetail?.regEmail}
屋根適合
-
屋根材適合性確認依頼
-
- 入力した内容が表示されます. -
- インストール可能であることを確認してください. -
- 屋根の写真を添付しました. -
+
{inquiryDetail?.qstTitle}
+
{inquiryDetail?.qstContent}
ファイル添付
    -
  • - -
  • -
  • - -
  • + {inquiryDetail?.listFile?.map((file) => ( +
  • + +
  • + ))}
- {inquiry && } + {inquiryDetail?.answerYn === 'Y' && }
-
diff --git a/src/components/inquiry/InquiryDetail.tsx b/src/components/inquiry/InquiryDetail.tsx deleted file mode 100644 index b153f35..0000000 --- a/src/components/inquiry/InquiryDetail.tsx +++ /dev/null @@ -1,73 +0,0 @@ -'use client' - -import { useParams } from 'next/navigation' - -const inquiryDummyData = { - writer: { - name: 'writer', - email: 'writer@example.com', - }, - title: 'title', - content: 'content', - files: ['file1.jpg', 'file2.jpg', 'file3.jpg'], - createdAt: '2021-01-01', - answer: { - writer: '佐藤一貴', - content: - '一次側接続は、自動切替開閉器と住宅分電盤主幹ブレーカの間に蓄電システムブレーカを配線する方法です。\n二次側接続は、住宅分電盤主幹ブレ―カの二次側に蓄電システムブレーカを接続する', - createdAt: '2021-01-01 12:00:00', - files: ['file4.jpg', 'file5.jpg', 'file6.jpg'], - }, -} - -export default function InquiryDetail() { - const params = useParams() - const id = params.id - return ( -
-

InquiryDetail

-

{id}

-
-
-

writer

-

{inquiryDummyData.writer.name}

-
-
-

email

-

{inquiryDummyData.writer.email}

-
-
-

title

-

{inquiryDummyData.title}

-
-
-

content

-

{inquiryDummyData.content}

-
-
-

files

-
- {inquiryDummyData.files.map((file) => ( - {file} - ))} -
-
- {inquiryDummyData.answer && ( -
-

Reply: Hanwha Japan

-
-

{inquiryDummyData.answer.writer}

-

{inquiryDummyData.answer.createdAt}

-

{inquiryDummyData.answer.content}

-
- {inquiryDummyData.answer.files.map((file) => ( - {file} - ))} -
-
-
- )} -
-
- ) -} diff --git a/src/components/inquiry/InquiryFilter.tsx b/src/components/inquiry/InquiryFilter.tsx deleted file mode 100644 index c3911a2..0000000 --- a/src/components/inquiry/InquiryFilter.tsx +++ /dev/null @@ -1,20 +0,0 @@ -'use client' - -import { Search } from 'lucide-react' -import { useRouter } from 'next/navigation' - - -export default function InquiryFilter({ handleSearch }: { handleSearch: (e: React.ChangeEvent) => void }) { - const router = useRouter() - return ( -
- -
- - -
-
- ) -} diff --git a/src/components/inquiry/InquiryItems.tsx b/src/components/inquiry/InquiryItems.tsx deleted file mode 100644 index bc38ad6..0000000 --- a/src/components/inquiry/InquiryItems.tsx +++ /dev/null @@ -1,21 +0,0 @@ -'use client' - -import { useRouter } from 'next/navigation' - -export default function InquiryItems({ inquiryData }: { inquiryData: any }) { - const router = useRouter() - return ( -
- {inquiryData.map((item: any) => ( -
router.push(`/inquiry/${item.id}`)}> -
{item.title}
-
{item.content}
-
{item.createdAt}
-
{item.writer}
-
{item.category}
- {item.file &&
{item.file}
} -
- ))} -
- ) -} diff --git a/src/components/inquiry/InquiryList.tsx b/src/components/inquiry/InquiryList.tsx deleted file mode 100644 index f65478b..0000000 --- a/src/components/inquiry/InquiryList.tsx +++ /dev/null @@ -1,171 +0,0 @@ -'use client' -import { useState } from 'react' -import InquiryItems from './InquiryItems' -import InquiryFilter from './InquiryFilter' -import LoadMoreButton from '../LoadMoreButton' - -const inquiryDummyData = [ - { - id: 1, - title: 'post', - content: 'content', - file: 'file.png', - createdAt: '2024-01-01', - writer: 'writer', - category: 'A', - }, - { - id: 2, - title: 'post', - content: 'content', - file: 'file.png', - createdAt: '2024-01-01', - writer: 'writer1', - category: 'B', - }, - { - id: 3, - title: 'post', - content: 'content', - file: null, - createdAt: '2024-01-01', - writer: 'writer1', - category: 'C', - }, - { - id: 4, - title: 'post', - content: 'content', - file: null, - createdAt: '2024-01-01', - writer: 'writer1', - category: 'A', - }, - { - id: 5, - title: 'post', - content: 'content', - file: null, - createdAt: '2024-01-01', - writer: 'writer1', - category: 'B', - }, - { - id: 6, - title: 'post', - content: 'content', - file: null, - createdAt: '2024-01-01', - writer: 'writer1', - category: 'C', - }, - { - id: 7, - title: 'post', - content: 'content', - file: 'file.png', - createdAt: '2024-01-01', - writer: 'writer', - category: 'A', - }, - { - id: 8, - title: 'post', - content: 'content', - file: 'file.png', - createdAt: '2024-01-01', - writer: 'writer1', - category: 'B', - }, - { - id: 9, - title: 'post', - content: 'content', - file: null, - createdAt: '2024-01-01', - writer: 'writer1', - category: 'C', - }, - - { - id: 10, - title: 'post', - content: 'content', - file: 'file.png', - createdAt: '2024-01-01', - writer: 'writer1', - category: 'A', - }, - { - id: 11, - title: 'post', - content: 'content', - file: 'file.png', - createdAt: '2024-01-01', - writer: 'writer', - category: 'B', - }, - { - id: 12, - title: 'post', - content: 'content', - file: null, - createdAt: '2024-01-01', - writer: 'writer1', - category: 'C', - }, -] - -export default function InquiryList() { - const [visibleItems, setVisibleItems] = useState(5) - const [isMyPostsOnly, setIsMyPostsOnly] = useState(false) - const [category, setCategory] = useState('') - const [search, setSearch] = useState('') - const [hasMore, setHasMore] = useState(inquiryDummyData.length > 5) - - const inquriyData = () => { - if (isMyPostsOnly) { - return inquiryDummyData.filter((item) => item.writer === 'writer') - } - if (category.trim().length > 0) { - return inquiryDummyData.filter((item) => item.category === category) - } - if (search.trim().length > 0) { - return inquiryDummyData.filter((item) => item.title.includes(search)) - } - return inquiryDummyData - } - - const handleLoadMore = () => { - const newVisibleItems = Math.min(visibleItems + 5, inquriyData().length) - setVisibleItems(newVisibleItems) - setHasMore(newVisibleItems < inquriyData().length) - } - - const handleSearch = (e: React.ChangeEvent) => { - setSearch(e.target.value) - } - - const handleScrollToTop = () => { - window.scrollTo({ top: 0, behavior: 'smooth' }) - } - - return ( -
- -
- setIsMyPostsOnly(e.target.checked)} /> - -
- - total {inquriyData().length} - - -
- ) -} diff --git a/src/components/inquiry/InquiryWriteForm.tsx b/src/components/inquiry/InquiryWriteForm.tsx deleted file mode 100644 index 868f3da..0000000 --- a/src/components/inquiry/InquiryWriteForm.tsx +++ /dev/null @@ -1,67 +0,0 @@ -'use client' - -import { useState } from 'react' -import { useRouter } from 'next/navigation' - -export interface InquiryFormData { - category: string - title: string - content: string - file: File[] -} - -export default function InquiryWriteForm() { - const router = useRouter() - const [formData, setFormData] = useState({ - category: 'A', - title: '', - content: '', - file: [], - }) - - const handleFileChange = (e: React.ChangeEvent) => { - const file = Array.from(e.target.files || []) - setFormData({ ...formData, file: [...formData.file, ...file] }) - } - const handleSubmit = () => { - console.log('submit: ', formData) - // router.push(`/inquiry`) - } - - return ( -
-
- - -
-
- - setFormData({ ...formData, title: e.target.value })} /> -
-
- -