From e3940f72c88e221eeaeba3520031d90beabbca95 Mon Sep 17 00:00:00 2001 From: keyy1315 Date: Tue, 13 May 2025 09:22:35 +0900 Subject: [PATCH 1/8] 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 })} /> -
-
- - +
@@ -46,29 +122,25 @@ export default function RegistForm() { - +
- 添付ファイル2個 + 添付ファイル{attachedFiles.length}
    -
  • -
    -
    添付ファイル名.jpg
    - -
    -
  • -
  • -
    -
    添付ファイル名.jpg
    - -
    -
  • + {attachedFiles.map((file, index) => ( +
  • +
    +
    {file.name}
    +
    +
  • + ))}
-
diff --git a/src/components/inquiry/list/ListForm.tsx b/src/components/inquiry/list/ListForm.tsx index 14f38bb..d22d424 100644 --- a/src/components/inquiry/list/ListForm.tsx +++ b/src/components/inquiry/list/ListForm.tsx @@ -1,8 +1,25 @@ 'use client' +import { useInquiryFilterStore } from '@/store/inquiryFilterStore' import { useRouter } from 'next/navigation' +import { useState } from 'react' export default function ListForm() { const router = useRouter() + const [searchKeyword, setSearchKeyword] = useState('') + const { inquiryListRequest, setInquiryListRequest } = useInquiryFilterStore() + + const handleSearch = () => { + if (searchKeyword.length >= 2) { + setInquiryListRequest({ ...inquiryListRequest, schTitle: searchKeyword }) + } + } + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + handleSearch() + } + } + return ( <>
@@ -13,8 +30,15 @@ export default function ListForm() {
- - + setSearchKeyword(e.target.value)} + onKeyDown={handleKeyDown} + /> +
diff --git a/src/components/inquiry/list/ListTable.tsx b/src/components/inquiry/list/ListTable.tsx index 16314aa..9598117 100644 --- a/src/components/inquiry/list/ListTable.tsx +++ b/src/components/inquiry/list/ListTable.tsx @@ -4,6 +4,9 @@ import { useEffect, useState } from 'react' import LoadMoreButton from '../../LoadMoreButton' import { useInquiry } from '@/hooks/useInquiry' import { InquiryList } from '@/types/Inquiry' +import { useRouter } from 'next/navigation' +import { useInquiryFilterStore } from '@/store/inquiryFilterStore' +import { useSessionStore } from '@/store/session' const badgeStyle = [ { @@ -21,14 +24,30 @@ export default function ListTable() { const [offset, setOffset] = useState(0) const [hasMore, setHasMore] = useState(true) + const router = useRouter() + const { inquiryList } = useInquiry() + const { inquiryListRequest, setInquiryListRequest } = useInquiryFilterStore() + + const { session } = useSessionStore() + useEffect(() => { - if (inquiryList.length > offset + 10) { - setHasMore(true) + if (inquiryList.length !== 0) { + const hasMoreItems = inquiryList[0].totCnt > offset + 10 + setHasMore(hasMoreItems) } else { setHasMore(false) } - }, [inquiryList]) + }, [inquiryList, offset]) + + const handleMyInquiry = () => { + setInquiryListRequest({ ...inquiryListRequest, schRegId: inquiryListRequest.schRegId ? null : session.userId }) + } + + const handleLoadMore = () => { + setOffset(offset + 10) + setInquiryListRequest({ ...inquiryListRequest, startRow: offset, endRow: offset + 10 }) + } return ( <> @@ -36,7 +55,7 @@ export default function ListTable() {
- +
@@ -53,21 +72,22 @@ export default function ListTable() { 合計 {inquiryList.length}
    - {inquiryList.map((inquiry: InquiryList) => ( -
  • -
    -
    {inquiry.qnaTpCd}
    -
    {inquiry.qstTitle}
    -
    {inquiry.regDt}
    -
    badge.id === inquiry.answerYn)?.color}`}> - {badgeStyle.find((badge) => badge.id === inquiry.answerYn)?.label} + {inquiryList.length > 0 && + inquiryList.map((inquiry: InquiryList) => ( +
  • router.push(`/inquiry/${inquiry.qnaNo}`)}> +
    +
    {inquiry.qnaClsLrgCd}
    +
    {inquiry.qstTitle}
    +
    {inquiry.regDt}
    +
    badge.id === inquiry.answerYn)?.color}`}> + {badgeStyle.find((badge) => badge.id === inquiry.answerYn)?.label} +
    - -
  • - ))} + + ))}
- setOffset(offset + 10)} /> + handleLoadMore()} />
diff --git a/src/hooks/useInquiry.ts b/src/hooks/useInquiry.ts index 07e74fe..17c3099 100644 --- a/src/hooks/useInquiry.ts +++ b/src/hooks/useInquiry.ts @@ -1,4 +1,4 @@ -import { InquiryList, Inquiry, InquiryRequest } from '@/types/Inquiry' +import { InquiryList, Inquiry, InquiryRequest, InquirySaveResponse } from '@/types/Inquiry' import { axiosInstance } from '@/libs/axios' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { useInquiryFilterStore } from '@/store/inquiryFilterStore' @@ -12,37 +12,47 @@ export function useInquiry( inquiryDetail: Inquiry | null isLoadingInquiryDetail: boolean isSavingInquiry: boolean - saveInquiry: (inquiryRequest: InquiryRequest) => Promise + saveInquiry: (inquiryRequest: InquiryRequest) => Promise } { const { session } = useSessionStore() const queryClient = useQueryClient() const { inquiryListRequest } = useInquiryFilterStore() const { data: inquiryList, isLoading: isLoadingInquiryList } = useQuery({ - queryKey: ['inquiryList', qnoNo, compCd, inquiryListRequest], + queryKey: ['inquiryList', inquiryListRequest], queryFn: async () => { - const resp = await axiosInstance(null).get('/api/qna/list', { - params: { inquiryListRequest }, - }) - return resp.data + try { + const resp = await axiosInstance(null).get<{ data: InquiryList[] }>(`/api/qna/list`, { + params: { inquiryListRequest }, + }) + return resp.data.data + } catch (error: any) { + console.error(error.response.data) + return [] + } }, }) const { data: inquiryDetail, isLoading: isLoadingInquiryDetail } = useQuery({ queryKey: ['inquiryDetail', qnoNo, compCd], queryFn: async () => { - const resp = await axiosInstance(null).get(`/api/qna/detail`, { - params: { qnoNo, compCd, loginId: session?.userNm }, - }) - return resp.data + try { + const resp = await axiosInstance(null).get<{ data: Inquiry }>(`/api/qna/detail`, { + params: { qnoNo, compCd, langCd: 'JA', loginId: 'x112' }, + }) + return resp.data.data + } catch (error: any) { + console.error(error.response) + return null + } }, enabled: qnoNo !== undefined && compCd !== undefined, }) const { mutateAsync: saveInquiry, isPending: isSavingInquiry } = useMutation({ mutationFn: async (inquiryRequest: InquiryRequest) => { - const resp = await axiosInstance(null).post('/api/qna/save', inquiryRequest) - return resp.data + const resp = await axiosInstance(null).post<{ data: InquirySaveResponse }>('/api/qna/save', inquiryRequest) + return resp.data.data }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['inquiryList'] }) diff --git a/src/store/inquiryFilterStore.ts b/src/store/inquiryFilterStore.ts index edfadc4..af862c2 100644 --- a/src/store/inquiryFilterStore.ts +++ b/src/store/inquiryFilterStore.ts @@ -9,33 +9,33 @@ type InquiryFilterState = { export const useInquiryFilterStore = create((set) => ({ inquiryListRequest: { - compCd: '', - langCd: '', - storeId: '', - siteTpCd: '', - schTitle: '', - schRegId: '', - schFromDt: '', - schToDt: '', + compCd: '5200', + langCd: 'JA', + storeId: 'X112', + siteTpCd: 'QC', + schTitle: null, + schRegId: null, + schFromDt: null, + schToDt: null, startRow: 0, - endRow: 0, - loginId: '', + endRow: 10, + loginId: 'x112', }, setInquiryListRequest: (inquiryListRequest) => set({ inquiryListRequest }), reset: () => set({ inquiryListRequest: { - compCd: '', - langCd: '', - storeId: '', - siteTpCd: '', + compCd: '5200', + langCd: 'JA', + storeId: 'X112', + siteTpCd: 'QC', schTitle: '', schRegId: '', schFromDt: '', schToDt: '', startRow: 0, - endRow: 0, - loginId: '', + endRow: 50, + loginId: 'x112', }, }), })) -- 2.47.2 From b4dfc2211f39a41c1dbe6fde9b7562e03f0e858d Mon Sep 17 00:00:00 2001 From: keyy1315 Date: Wed, 14 May 2025 11:06:24 +0900 Subject: [PATCH 3/8] feat: modify inquiry save requestParameter to formdata add files --- src/app/api/qna/detail/route.ts | 1 + src/app/api/qna/file/route.ts | 15 +++++++++++++++ src/app/api/qna/list/route.ts | 2 +- src/app/api/qna/save/route.ts | 20 ++++++++++++++------ src/components/inquiry/Answer.tsx | 5 +++-- src/components/inquiry/Detail.tsx | 6 +++--- src/components/inquiry/RegistForm.tsx | 6 +++--- src/hooks/useInquiry.ts | 21 ++++++++++++++++++--- 8 files changed, 58 insertions(+), 18 deletions(-) create mode 100644 src/app/api/qna/file/route.ts diff --git a/src/app/api/qna/detail/route.ts b/src/app/api/qna/detail/route.ts index c91931a..da709c0 100644 --- a/src/app/api/qna/detail/route.ts +++ b/src/app/api/qna/detail/route.ts @@ -13,6 +13,7 @@ export async function GET(request: Request) { try { const response = await axios.get(`${process.env.NEXT_PUBLIC_INQUIRY_API_URL}/api/qna/detail?${queryStringFormatter(params)}`) + console.log('response.data detail:: ', response.data) if (response.status === 200) { return NextResponse.json(response.data) } diff --git a/src/app/api/qna/file/route.ts b/src/app/api/qna/file/route.ts new file mode 100644 index 0000000..2c4556a --- /dev/null +++ b/src/app/api/qna/file/route.ts @@ -0,0 +1,15 @@ +import axios from 'axios' +import { NextResponse } from 'next/server' + +export async function GET(request: Request) { + const { searchParams } = new URL(request.url) + const encodeFileNo = searchParams.get('encodeFileNo') + + if (!encodeFileNo) { + return NextResponse.json({ error: 'fileNo is required' }, { status: 400 }) + } + const response = await axios.get(`${process.env.NEXT_PUBLIC_INQUIRY_API_URL}/api/file/downloadFile?encodeFileNo=${encodeFileNo}`) + console.log('response.data:: ', response.data) + + return NextResponse.json(response.data) +} diff --git a/src/app/api/qna/list/route.ts b/src/app/api/qna/list/route.ts index 2a63a3c..901aa20 100644 --- a/src/app/api/qna/list/route.ts +++ b/src/app/api/qna/list/route.ts @@ -17,7 +17,7 @@ export async function GET(request: Request) { try { const response = await axios.get(`${process.env.NEXT_PUBLIC_INQUIRY_API_URL}/api/qna/list?${queryStringFormatter(params)}`) - + console.log('response.data:: ', response.data) if (response.status === 200) { return NextResponse.json(response.data) } diff --git a/src/app/api/qna/save/route.ts b/src/app/api/qna/save/route.ts index e16593a..dd4edaa 100644 --- a/src/app/api/qna/save/route.ts +++ b/src/app/api/qna/save/route.ts @@ -2,13 +2,21 @@ import axios from 'axios' import { NextResponse } from 'next/server' export async function POST(request: Request) { - const body = await request.json() + const formData = await request.formData() + console.log('formData:: ', formData) + // const body = await request.json() + // console.log('body:: ', body) - const response = await axios.post(`${process.env.NEXT_PUBLIC_INQUIRY_API_URL}/api/qna/save`, body) + try { + const response = await axios.post(`${process.env.NEXT_PUBLIC_INQUIRY_API_URL}/api/qna/save`, formData) + console.log('response.data:: ', response.data) - if (response.status === 200) { - return NextResponse.json(response.data) + if (response.status === 200) { + return NextResponse.json(response.data) + } + return NextResponse.json({ error: response.data }, { status: response.status }) + } catch (error) { + console.error('error:: ', error) + return NextResponse.json({ error: 'Failed to save qna' }, { status: 500 }) } - - return NextResponse.json({ error: 'Failed to save qna' }, { status: response.status }) } diff --git a/src/components/inquiry/Answer.tsx b/src/components/inquiry/Answer.tsx index 485abde..4b44eb3 100644 --- a/src/components/inquiry/Answer.tsx +++ b/src/components/inquiry/Answer.tsx @@ -1,8 +1,9 @@ 'use client' +import { useInquiry } from '@/hooks/useInquiry' import { Inquiry } from '@/types/Inquiry' -export default function Answer({ inquiryDetail }: { inquiryDetail: Inquiry}) { +export default function Answer({ inquiryDetail, downloadFile }: { inquiryDetail: Inquiry; downloadFile: (encodeFileNo: number) => Promise }) { return ( <>
@@ -21,7 +22,7 @@ export default function Answer({ inquiryDetail }: { inquiryDetail: Inquiry}) {
    {inquiryDetail?.ansListFile?.map((file) => (
  • -
  • diff --git a/src/components/inquiry/Detail.tsx b/src/components/inquiry/Detail.tsx index a983bca..717b714 100644 --- a/src/components/inquiry/Detail.tsx +++ b/src/components/inquiry/Detail.tsx @@ -12,7 +12,7 @@ export default function Detail() { const params = useParams() const id = params.id - const { inquiryDetail } = useInquiry(Number(id), '5200') + const { inquiryDetail, downloadFile } = useInquiry(Number(id), '5200') const router = useRouter() return ( @@ -66,7 +66,7 @@ export default function Detail() {
      {inquiryDetail?.listFile?.map((file) => (
    • -
    • @@ -75,7 +75,7 @@ export default function Detail() {
- {inquiryDetail?.answerYn === 'Y' && inquiryDetail && } + {inquiryDetail?.answerYn === 'Y' && inquiryDetail && }
- 屋根 - 適合性 - 屋根材 -
-
屋根材適合性確認依頼
-
- 入力した内容が表示されます. -
- インストール可能であることを確認してください. -
- 屋根の写真を添付しました. + {inquiryDetail?.qnaClsLrgCd} + {inquiryDetail?.qnaClsMidCd} + {inquiryDetail?.qnaClsSmlCd}
{inquiryDetail?.qstTitle}
{inquiryDetail?.qstContents}
@@ -91,6 +69,11 @@ export default function Detail() { ))} +
  • + +
  • diff --git a/src/components/inquiry/RegistForm.tsx b/src/components/inquiry/RegistForm.tsx index 09599ea..1e97130 100644 --- a/src/components/inquiry/RegistForm.tsx +++ b/src/components/inquiry/RegistForm.tsx @@ -4,7 +4,7 @@ import { useInquiry } from '@/hooks/useInquiry' import { useSessionStore } from '@/store/session' import { InquiryRequest } from '@/types/Inquiry' -import { useEffect, useState } from 'react' +import { useState } from 'react' import { useRouter } from 'next/navigation' export default function RegistForm() { const { saveInquiry, isSavingInquiry } = useInquiry() @@ -20,14 +20,15 @@ export default function RegistForm() { compCd: '5200', siteTpCd: 'QC', qnaClsLrgCd: '', - qnaClsMidCd: 'B02', + qnaClsMidCd: '', qnaClsSmlCd: null, title: '', - contents: null, + contents: '', regId: 'X112', regUserNm: 'TEST', regUserTelNo: null, - storeId: null, + storeId: 'X112', + qstMail: '', }) const [attachedFiles, setAttachedFiles] = useState([]) @@ -45,12 +46,30 @@ export default function RegistForm() { } const handleSubmit = async () => { - if (confirm('お問い合わせを登録しますか? Hanwha Japanの担当者にお問い合わせメールが送信されます。')) { - const res = await saveInquiry({ inquiryRequest, files: attachedFiles }) - alert('保存されました。') - router.push(`/inquiry/${res.qnaNo}`) - } - return + const formData = new FormData() + attachedFiles.forEach((file) => { + formData.append('files', file) + }) + Object.entries(inquiryRequest).forEach(([key, value]) => { + formData.append(key, value ?? '') + }) + + // FormData를 객체로 변환하여 확인 + const formDataObj: Record = {} + formData.forEach((value, key) => { + formDataObj[key] = value + }) + console.log('formData contents:', formDataObj) + + window.neoConfirm( + 'お問い合わせを登録しますか? Hanwha Japanの担当者にお問い合わせメールが送信されます。', + async () => { + const res = await saveInquiry(formData) + alert('保存されました。') + router.push(`/inquiry/${res.qnaNo}`) + }, + () => null, + ) } return ( @@ -62,30 +81,42 @@ export default function RegistForm() { お問い合わせタイプ *
    - setInquiryRequest({ ...inquiryRequest, qnaClsLrgCd: e.target.value })} + > + + +
    - setInquiryRequest({ ...inquiryRequest, qnaClsMidCd: e.target.value })} + > + + +
    - setInquiryRequest({ ...inquiryRequest, qnaClsSmlCd: e.target.value })} + > + + +
    @@ -94,27 +125,36 @@ export default function RegistForm() { 名前 *
    - + setInquiryRequest({ ...inquiryRequest, regUserNm: e.target.value })} + />
    電話番号
    - + setInquiryRequest({ ...inquiryRequest, regUserTelNo: e.target.value })} + />
    - 名前 * + E-mail *
    - -
    -
    -
    -
    電話番号
    -
    - + setInquiryRequest({ ...inquiryRequest, qstMail: e.target.value })} + />
    @@ -122,7 +162,12 @@ export default function RegistForm() { お問い合わせタイトル *
    - + setInquiryRequest({ ...inquiryRequest, title: e.target.value })} + />
    diff --git a/src/components/inquiry/list/ListForm.tsx b/src/components/inquiry/list/ListForm.tsx index d22d424..0118f58 100644 --- a/src/components/inquiry/list/ListForm.tsx +++ b/src/components/inquiry/list/ListForm.tsx @@ -5,8 +5,8 @@ import { useState } from 'react' export default function ListForm() { const router = useRouter() - const [searchKeyword, setSearchKeyword] = useState('') const { inquiryListRequest, setInquiryListRequest } = useInquiryFilterStore() + const [searchKeyword, setSearchKeyword] = useState(inquiryListRequest.schTitle ?? '') const handleSearch = () => { if (searchKeyword.length >= 2) { diff --git a/src/components/inquiry/list/ListTable.tsx b/src/components/inquiry/list/ListTable.tsx index 9598117..eed1d34 100644 --- a/src/components/inquiry/list/ListTable.tsx +++ b/src/components/inquiry/list/ListTable.tsx @@ -7,6 +7,7 @@ import { InquiryList } from '@/types/Inquiry' import { useRouter } from 'next/navigation' import { useInquiryFilterStore } from '@/store/inquiryFilterStore' import { useSessionStore } from '@/store/session' +import ListForm from './ListForm' const badgeStyle = [ { @@ -29,14 +30,23 @@ export default function ListTable() { const { inquiryList } = useInquiry() const { inquiryListRequest, setInquiryListRequest } = useInquiryFilterStore() + const [heldInquiryList, setHeldInquiryList] = useState([]) + const { session } = useSessionStore() useEffect(() => { - if (inquiryList.length !== 0) { - const hasMoreItems = inquiryList[0].totCnt > offset + 10 - setHasMore(hasMoreItems) + if (inquiryList.length > 0) { + if (offset === 0) { + setHeldInquiryList(inquiryList) + } else { + const remainingList = heldInquiryList.slice(offset, offset + 10) + if (JSON.stringify(remainingList) !== JSON.stringify(inquiryList)) { + setHeldInquiryList((prev) => [...prev, ...inquiryList]) + } + } + setHasMore(inquiryList.length > offset + 10) } else { - setHasMore(false) + setHeldInquiryList([]) } }, [inquiryList, offset]) @@ -49,8 +59,19 @@ export default function ListTable() { setInquiryListRequest({ ...inquiryListRequest, startRow: offset, endRow: offset + 10 }) } + const handleFilter = (e: React.ChangeEvent) => { + console.log(e.target.value) + setHeldInquiryList(inquiryList.filter((inquiry: InquiryList) => inquiry.answerYn === e.target.value)) + if (e.target.value === '') { + setHeldInquiryList(inquiryList) + } + } + + console.log('heldInquiryList:: ', heldInquiryList) + return ( <> +
    @@ -60,7 +81,7 @@ export default function ListTable() {
    - handleFilter(e)}> @@ -69,11 +90,11 @@ export default function ListTable() {
    - 合計 {inquiryList.length}個 + 合計 {heldInquiryList.length > 0 ? heldInquiryList[0].totCnt : 0}
      - {inquiryList.length > 0 && - inquiryList.map((inquiry: InquiryList) => ( + {heldInquiryList.length > 0 && + heldInquiryList.map((inquiry: InquiryList) => (
    • router.push(`/inquiry/${inquiry.qnaNo}`)}>
      {inquiry.qnaClsLrgCd}
      diff --git a/src/hooks/useInquiry.ts b/src/hooks/useInquiry.ts index 1314dee..0e698b1 100644 --- a/src/hooks/useInquiry.ts +++ b/src/hooks/useInquiry.ts @@ -12,10 +12,10 @@ export function useInquiry( inquiryDetail: Inquiry | null isLoadingInquiryDetail: boolean isSavingInquiry: boolean - saveInquiry: (params: { inquiryRequest: InquiryRequest; files: File[] }) => Promise + saveInquiry: (formData: FormData) => Promise downloadFile: (encodeFileNo: number) => Promise } { - const { session } = useSessionStore() + // const { session } = useSessionStore() const queryClient = useQueryClient() const { inquiryListRequest } = useInquiryFilterStore() @@ -41,6 +41,7 @@ export function useInquiry( const resp = await axiosInstance(null).get<{ data: Inquiry }>(`/api/qna/detail`, { params: { qnoNo, compCd, langCd: 'JA', loginId: 'x112' }, }) + console.log('resp.data.data:: ', resp.data.data) return resp.data.data } catch (error: any) { console.error(error.response) @@ -51,15 +52,7 @@ export function useInquiry( }) const { mutateAsync: saveInquiry, isPending: isSavingInquiry } = useMutation({ - mutationFn: async ({ inquiryRequest, files }: { inquiryRequest: InquiryRequest; files: File[] }) => { - const formData = new FormData() - Object.entries(inquiryRequest).forEach(([key, value]) => { - formData.append(key, value ?? '') - }) - files.forEach((file) => { - formData.append('files', file) - }) - + mutationFn: async (formData: FormData) => { const resp = await axiosInstance(null).post<{ data: InquirySaveResponse }>('/api/qna/save', formData) return resp.data.data }, diff --git a/src/types/Inquiry.ts b/src/types/Inquiry.ts index 96a6fff..c876020 100644 --- a/src/types/Inquiry.ts +++ b/src/types/Inquiry.ts @@ -75,11 +75,12 @@ export type InquiryRequest = { qnaClsMidCd: string //qna CLS Mid Code qnaClsSmlCd: string | null //qna CLS Small Code title: string //title - contents: string | null //contents + contents: string //contents regId: string //registration Userid - storeId: string | null //store id + storeId: string //store id regUserNm: string //registration User name regUserTelNo: string | null //registration User tel number + qstMail: string //mail } export type InquirySaveResponse = { -- 2.47.2 From 1d77fec86d8bfd2da87115b20573144d72ce6b6c Mon Sep 17 00:00:00 2001 From: keyy1315 Date: Fri, 16 May 2025 14:36:41 +0900 Subject: [PATCH 5/8] fix: change inquiry save parameter type to form data --- src/app/api/qna/save/route.ts | 14 ++++++++------ src/components/inquiry/Detail.tsx | 5 ----- src/components/inquiry/list/ListTable.tsx | 2 +- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/app/api/qna/save/route.ts b/src/app/api/qna/save/route.ts index 00633bd..3afc339 100644 --- a/src/app/api/qna/save/route.ts +++ b/src/app/api/qna/save/route.ts @@ -3,17 +3,19 @@ import { NextResponse } from 'next/server' export async function POST(request: Request) { const formData = await request.formData() - console.log('formData:: ', formData) - try { - const response = await axios.post(`${process.env.NEXT_PUBLIC_INQUIRY_API_URL}/api/qna/save`, formData) - + const response = await axios.post(`${process.env.NEXT_PUBLIC_INQUIRY_API_URL}/api/qna/save`, formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }) + console.log('response:: ', response) if (response.status === 200) { return NextResponse.json(response.data) } return NextResponse.json({ error: response.data }, { status: response.status }) - } catch (error) { - console.error('error:: ', error) + } catch (error: any) { + console.error('error:: ', error.response) return NextResponse.json({ error: 'Failed to save qna' }, { status: 500 }) } } diff --git a/src/components/inquiry/Detail.tsx b/src/components/inquiry/Detail.tsx index 9bb22b4..850cc6d 100644 --- a/src/components/inquiry/Detail.tsx +++ b/src/components/inquiry/Detail.tsx @@ -69,11 +69,6 @@ export default function Detail() {
    • ))} -
    • - -
    diff --git a/src/components/inquiry/list/ListTable.tsx b/src/components/inquiry/list/ListTable.tsx index eed1d34..c12cf61 100644 --- a/src/components/inquiry/list/ListTable.tsx +++ b/src/components/inquiry/list/ListTable.tsx @@ -48,7 +48,7 @@ export default function ListTable() { } else { setHeldInquiryList([]) } - }, [inquiryList, offset]) + }, [inquiryList, offset, setHeldInquiryList]) const handleMyInquiry = () => { setInquiryListRequest({ ...inquiryListRequest, schRegId: inquiryListRequest.schRegId ? null : session.userId }) -- 2.47.2 From 96c725b45914a0fef76b6ae1a726cf06b6aeca07 Mon Sep 17 00:00:00 2001 From: keyy1315 Date: Fri, 16 May 2025 15:58:17 +0900 Subject: [PATCH 6/8] feat: implement inquriy file upload --- .env.development | 3 ++- src/app/api/qna/detail/route.ts | 2 +- src/components/inquiry/Detail.tsx | 2 +- src/components/inquiry/list/ListTable.tsx | 25 +++++++++-------------- src/hooks/useInquiry.ts | 7 +++---- 5 files changed, 17 insertions(+), 22 deletions(-) diff --git a/.env.development b/.env.development index 5aa380f..fb9112b 100644 --- a/.env.development +++ b/.env.development @@ -8,7 +8,8 @@ NEXT_PUBLIC_QSP_API_URL=http://1.248.227.176:8120 #1:1문의 api # NEXT_PUBLIC_INQUIRY_API_URL=http://1.248.227.176:38080 -NEXT_PUBLIC_INQUIRY_API_URL=http://172.23.4.129:8110 +# NEXT_PUBLIC_INQUIRY_API_URL=http://172.23.4.129:8110 +NEXT_PUBLIC_INQUIRY_API_URL=http://172.30.1.93:8120 #QPARTNER 로그인 api diff --git a/src/app/api/qna/detail/route.ts b/src/app/api/qna/detail/route.ts index da709c0..124ec68 100644 --- a/src/app/api/qna/detail/route.ts +++ b/src/app/api/qna/detail/route.ts @@ -6,7 +6,7 @@ export async function GET(request: Request) { const { searchParams } = new URL(request.url) const params = { compCd: searchParams.get('compCd'), - qnoNo: searchParams.get('qnoNo'), + qnaNo: searchParams.get('qnoNo'), langCd: searchParams.get('langCd'), loginId: searchParams.get('loginId'), } diff --git a/src/components/inquiry/Detail.tsx b/src/components/inquiry/Detail.tsx index 850cc6d..29482d4 100644 --- a/src/components/inquiry/Detail.tsx +++ b/src/components/inquiry/Detail.tsx @@ -63,7 +63,7 @@ export default function Detail() {
    ファイル添付
      {inquiryDetail?.listFile?.map((file) => ( -
    • +
    • diff --git a/src/components/inquiry/list/ListTable.tsx b/src/components/inquiry/list/ListTable.tsx index c12cf61..454dbb3 100644 --- a/src/components/inquiry/list/ListTable.tsx +++ b/src/components/inquiry/list/ListTable.tsx @@ -23,7 +23,7 @@ const badgeStyle = [ ] export default function ListTable() { const [offset, setOffset] = useState(0) - const [hasMore, setHasMore] = useState(true) + const [hasMore, setHasMore] = useState(false) const router = useRouter() @@ -35,20 +35,13 @@ export default function ListTable() { const { session } = useSessionStore() useEffect(() => { - if (inquiryList.length > 0) { - if (offset === 0) { - setHeldInquiryList(inquiryList) - } else { - const remainingList = heldInquiryList.slice(offset, offset + 10) - if (JSON.stringify(remainingList) !== JSON.stringify(inquiryList)) { - setHeldInquiryList((prev) => [...prev, ...inquiryList]) - } - } - setHasMore(inquiryList.length > offset + 10) - } else { - setHeldInquiryList([]) - } - }, [inquiryList, offset, setHeldInquiryList]) + if (!inquiryList) return + setHeldInquiryList(inquiryList) + setHasMore(inquiryList.length > offset + 10) + }, [inquiryList, offset]) + + console.log('heldInquiryList:: ', heldInquiryList) + console.log('inquiryList:: ', inquiryList) const handleMyInquiry = () => { setInquiryListRequest({ ...inquiryListRequest, schRegId: inquiryListRequest.schRegId ? null : session.userId }) @@ -57,6 +50,8 @@ export default function ListTable() { const handleLoadMore = () => { setOffset(offset + 10) setInquiryListRequest({ ...inquiryListRequest, startRow: offset, endRow: offset + 10 }) + + setHeldInquiryList((prev) => [...prev, ...inquiryList]) } const handleFilter = (e: React.ChangeEvent) => { diff --git a/src/hooks/useInquiry.ts b/src/hooks/useInquiry.ts index 0e698b1..c49df3e 100644 --- a/src/hooks/useInquiry.ts +++ b/src/hooks/useInquiry.ts @@ -1,8 +1,8 @@ -import { InquiryList, Inquiry, InquiryRequest, InquirySaveResponse } from '@/types/Inquiry' +import { InquiryList, Inquiry, InquirySaveResponse } from '@/types/Inquiry' import { axiosInstance } from '@/libs/axios' -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' +import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { useInquiryFilterStore } from '@/store/inquiryFilterStore' -import { useSessionStore } from '@/store/session' + export function useInquiry( qnoNo?: number, compCd?: string, @@ -15,7 +15,6 @@ export function useInquiry( saveInquiry: (formData: FormData) => Promise downloadFile: (encodeFileNo: number) => Promise } { - // const { session } = useSessionStore() const queryClient = useQueryClient() const { inquiryListRequest } = useInquiryFilterStore() -- 2.47.2 From b0878c853bc7f5128a37d79a7d6d789d37f15abd Mon Sep 17 00:00:00 2001 From: keyy1315 Date: Thu, 22 May 2025 14:26:51 +0900 Subject: [PATCH 7/8] feat: add inquiry types code option & filtering inquiry list by answer exist --- .env.development | 4 +- src/app/api/qna/detail/route.ts | 1 - src/app/api/qna/file/route.ts | 4 +- src/app/api/qna/list/route.ts | 1 - src/app/api/qna/save/route.ts | 1 - src/components/inquiry/Answer.tsx | 6 +- src/components/inquiry/RegistForm.tsx | 67 +++++++++---- src/components/inquiry/list/ListForm.tsx | 1 - src/components/inquiry/list/ListTable.tsx | 114 ++++++++++++++-------- src/hooks/useInquiry.ts | 22 ++++- src/store/inquiryFilterStore.ts | 16 +-- src/types/Inquiry.ts | 1 + 12 files changed, 153 insertions(+), 85 deletions(-) diff --git a/.env.development b/.env.development index 4e7f5a9..6bc3000 100644 --- a/.env.development +++ b/.env.development @@ -8,8 +8,8 @@ NEXT_PUBLIC_QSP_API_URL=http://1.248.227.176:8120 #1:1문의 api # NEXT_PUBLIC_INQUIRY_API_URL=http://1.248.227.176:38080 -# NEXT_PUBLIC_INQUIRY_API_URL=http://172.23.4.129:8110 -NEXT_PUBLIC_INQUIRY_API_URL=http://172.30.1.93:8120 +NEXT_PUBLIC_INQUIRY_API_URL=http://172.23.4.129:8110 +# NEXT_PUBLIC_INQUIRY_API_URL=http://172.30.1.93:8120 #QPARTNER 로그인 api diff --git a/src/app/api/qna/detail/route.ts b/src/app/api/qna/detail/route.ts index 124ec68..315c18e 100644 --- a/src/app/api/qna/detail/route.ts +++ b/src/app/api/qna/detail/route.ts @@ -13,7 +13,6 @@ export async function GET(request: Request) { try { const response = await axios.get(`${process.env.NEXT_PUBLIC_INQUIRY_API_URL}/api/qna/detail?${queryStringFormatter(params)}`) - console.log('response.data detail:: ', response.data) if (response.status === 200) { return NextResponse.json(response.data) } diff --git a/src/app/api/qna/file/route.ts b/src/app/api/qna/file/route.ts index 13a9232..a122da6 100644 --- a/src/app/api/qna/file/route.ts +++ b/src/app/api/qna/file/route.ts @@ -9,13 +9,11 @@ export async function GET(request: Request) { return NextResponse.json({ error: 'encodeFileNo is required' }, { status: 400 }) } try { - const response = await axios.get(`${process.env.NEXT_PUBLIC_INQUIRY_API_URL}/api/file/downloadFile`, { + const response = await axios.get(`${process.env.NEXT_PUBLIC_INQUIRY_API_URL}/file/downloadFile`, { params: { encodeFileNo, }, }) - console.log('response.data:: ', response.data) - return NextResponse.json(response.data) } catch (error: any) { console.error(error.response) diff --git a/src/app/api/qna/list/route.ts b/src/app/api/qna/list/route.ts index 2f7a52a..f793b98 100644 --- a/src/app/api/qna/list/route.ts +++ b/src/app/api/qna/list/route.ts @@ -17,7 +17,6 @@ export async function GET(request: Request) { try { const response = await axios.get(`${process.env.NEXT_PUBLIC_INQUIRY_API_URL}/api/qna/list?${queryStringFormatter(params)}`) - console.log('response.data:: ', response.data) if (response.status === 200) { return NextResponse.json(response.data) } diff --git a/src/app/api/qna/save/route.ts b/src/app/api/qna/save/route.ts index 3afc339..f51ae66 100644 --- a/src/app/api/qna/save/route.ts +++ b/src/app/api/qna/save/route.ts @@ -9,7 +9,6 @@ export async function POST(request: Request) { 'Content-Type': 'multipart/form-data', }, }) - console.log('response:: ', response) if (response.status === 200) { return NextResponse.json(response.data) } diff --git a/src/components/inquiry/Answer.tsx b/src/components/inquiry/Answer.tsx index 94b6152..76da68a 100644 --- a/src/components/inquiry/Answer.tsx +++ b/src/components/inquiry/Answer.tsx @@ -1,6 +1,5 @@ 'use client' -import { useInquiry } from '@/hooks/useInquiry' import { Inquiry } from '@/types/Inquiry' export default function Answer({ inquiryDetail, downloadFile }: { inquiryDetail: Inquiry; downloadFile: (encodeFileNo: number) => Promise }) { @@ -15,10 +14,7 @@ export default function Answer({ inquiryDetail, downloadFile }: { inquiryDetail:
    回答
    -
    - 一次側接続は, 自動切替開閉器と住宅分電盤昼間遮断器との間に蓄電システム遮断器を配線する方法です. 二次側接続は, - 住宅分電盤週間ブレーカの二次側に蓄電システムブレーカを接続する -
    +
    {inquiryDetail?.ansContents}
    ファイル添付
    diff --git a/src/components/inquiry/RegistForm.tsx b/src/components/inquiry/RegistForm.tsx index 1e97130..dd02baa 100644 --- a/src/components/inquiry/RegistForm.tsx +++ b/src/components/inquiry/RegistForm.tsx @@ -4,18 +4,13 @@ import { useInquiry } from '@/hooks/useInquiry' import { useSessionStore } from '@/store/session' import { InquiryRequest } from '@/types/Inquiry' -import { useState } from 'react' +import { useEffect, useState } from 'react' import { useRouter } from 'next/navigation' export default function RegistForm() { const { saveInquiry, isSavingInquiry } = useInquiry() const { session } = useSessionStore() const router = useRouter() - // TODO: 세션 정보 적용 | 현재는 test용 정보 적용 - // useEffect(() => { - // setInquiryRequest({ ...inquiryRequest, regId: session?.userId ?? '', regUserNm: session?.userNm ?? '' }) - // }, [session]) - const [inquiryRequest, setInquiryRequest] = useState({ compCd: '5200', siteTpCd: 'QC', @@ -24,12 +19,26 @@ export default function RegistForm() { qnaClsSmlCd: null, title: '', contents: '', - regId: 'X112', - regUserNm: 'TEST', + regId: '', + regUserNm: '', regUserTelNo: null, - storeId: 'X112', + storeId: '', qstMail: '', }) + const requiredFieldNames = [ + { id: 'qnaClsLrgCd', name: 'お問い合わせタイプ' }, + { id: 'qnaClsMidCd', name: 'お問い合わせタイプ' }, + { id: 'regUserNm', name: '名前' }, + { id: 'qstMail', name: 'E-mail' }, + { id: 'title', name: 'お問い合わせタイトル' }, + { id: 'contents', name: 'お問い合わせ内容' }, + ] + + useEffect(() => { + if (session?.isLoggedIn) { + setInquiryRequest({ ...inquiryRequest, regId: session?.userId ?? '', regUserNm: session?.userNm ?? '', storeId: session?.storeId ?? '' }) + } + }, [session]) const [attachedFiles, setAttachedFiles] = useState([]) @@ -45,7 +54,25 @@ export default function RegistForm() { setAttachedFiles(attachedFiles.filter((_, i) => i !== index)) } + const focusOnRequiredField = (fieldId: string) => { + const element = document.getElementById(fieldId) + if (element) element.focus() + } + const handleSubmit = async () => { + const emptyField = requiredFieldNames.find((field) => inquiryRequest[field.id as keyof InquiryRequest] === '') + if (emptyField) { + alert(`${emptyField?.name}を入力してください。`) + focusOnRequiredField(emptyField?.id ?? '') + return + } + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ + if (!emailRegex.test(inquiryRequest.qstMail)) { + alert('有効なメールアドレスを入力してください。') + focusOnRequiredField('qstMail') + return + } + const formData = new FormData() attachedFiles.forEach((file) => { formData.append('files', file) @@ -54,12 +81,10 @@ export default function RegistForm() { formData.append(key, value ?? '') }) - // FormData를 객체로 변환하여 확인 const formDataObj: Record = {} formData.forEach((value, key) => { formDataObj[key] = value }) - console.log('formData contents:', formDataObj) window.neoConfirm( 'お問い合わせを登録しますか? Hanwha Japanの担当者にお問い合わせメールが送信されます。', @@ -83,40 +108,40 @@ export default function RegistForm() {
    @@ -130,6 +155,8 @@ export default function RegistForm() { type="text" placeholder="名前を書いてください" onChange={(e) => setInquiryRequest({ ...inquiryRequest, regUserNm: e.target.value })} + value={inquiryRequest.regUserNm} + id="regUserNm" /> @@ -141,6 +168,7 @@ export default function RegistForm() { type="text" placeholder="電話番号を書き留めてください" onChange={(e) => setInquiryRequest({ ...inquiryRequest, regUserTelNo: e.target.value })} + id="regUserTelNo" /> @@ -154,6 +182,7 @@ export default function RegistForm() { type="text" placeholder="E-mailを書いてください" onChange={(e) => setInquiryRequest({ ...inquiryRequest, qstMail: e.target.value })} + id="qstMail" /> @@ -167,6 +196,7 @@ export default function RegistForm() { type="text" placeholder="お問い合わせタイトルを記入してください" onChange={(e) => setInquiryRequest({ ...inquiryRequest, title: e.target.value })} + id="title" /> @@ -178,8 +208,7 @@ export default function RegistForm() { diff --git a/src/components/inquiry/list/ListForm.tsx b/src/components/inquiry/list/ListForm.tsx index 0118f58..bb41fb3 100644 --- a/src/components/inquiry/list/ListForm.tsx +++ b/src/components/inquiry/list/ListForm.tsx @@ -13,7 +13,6 @@ export default function ListForm() { setInquiryListRequest({ ...inquiryListRequest, schTitle: searchKeyword }) } } - const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { handleSearch() diff --git a/src/components/inquiry/list/ListTable.tsx b/src/components/inquiry/list/ListTable.tsx index 454dbb3..bf5ce88 100644 --- a/src/components/inquiry/list/ListTable.tsx +++ b/src/components/inquiry/list/ListTable.tsx @@ -4,7 +4,7 @@ import { useEffect, useState } from 'react' import LoadMoreButton from '../../LoadMoreButton' import { useInquiry } from '@/hooks/useInquiry' import { InquiryList } from '@/types/Inquiry' -import { useRouter } from 'next/navigation' +import { usePathname, useRouter } from 'next/navigation' import { useInquiryFilterStore } from '@/store/inquiryFilterStore' import { useSessionStore } from '@/store/session' import ListForm from './ListForm' @@ -22,48 +22,66 @@ const badgeStyle = [ }, ] export default function ListTable() { - const [offset, setOffset] = useState(0) - const [hasMore, setHasMore] = useState(false) - const router = useRouter() + const pathname = usePathname() const { inquiryList } = useInquiry() - const { inquiryListRequest, setInquiryListRequest } = useInquiryFilterStore() + const { inquiryListRequest, setInquiryListRequest, reset } = useInquiryFilterStore() - const [heldInquiryList, setHeldInquiryList] = useState([]) + const [offset, setOffset] = useState(inquiryListRequest.startRow) + const [hasMore, setHasMore] = useState(false) + + const [heldInquiryList, setHeldInquiryList] = useState([]) const { session } = useSessionStore() useEffect(() => { - if (!inquiryList) return - setHeldInquiryList(inquiryList) - setHasMore(inquiryList.length > offset + 10) - }, [inquiryList, offset]) + setInquiryListRequest({ ...inquiryListRequest, startRow: 1, endRow: 10 }) + setHeldInquiryList([]) + setOffset(1) + setHasMore(false) + }, [pathname]) - console.log('heldInquiryList:: ', heldInquiryList) - console.log('inquiryList:: ', inquiryList) + useEffect(() => { + if (!session.isLoggedIn || !inquiryList) return + if (session.isLoggedIn) { + setInquiryListRequest({ ...inquiryListRequest, storeId: session.storeId ?? '', loginId: session.userId ?? '' }) + // setInquiryListRequest({ ...inquiryListRequest, storeId: 'X112', loginId: 'x112' }) + } + console.log('inquiryListRequest', inquiryListRequest) + if (inquiryList.length > 0 && inquiryList[0].totCnt > 0) { + if (inquiryListRequest.startRow > 1) { + const isDuplicate = inquiryList.every((newItem) => heldInquiryList.some((existingItem) => existingItem.qnaNo === newItem.qnaNo)) + if (isDuplicate) return + setHeldInquiryList((prev) => [...prev, ...inquiryList]) + } else { + setHeldInquiryList(inquiryList) + } + setHasMore(inquiryList[0].totCnt > inquiryListRequest.endRow) + } else { + setHeldInquiryList([]) + setHasMore(false) + } + }, [session, inquiryList, inquiryListRequest.startRow]) const handleMyInquiry = () => { setInquiryListRequest({ ...inquiryListRequest, schRegId: inquiryListRequest.schRegId ? null : session.userId }) } - const handleLoadMore = () => { - setOffset(offset + 10) - setInquiryListRequest({ ...inquiryListRequest, startRow: offset, endRow: offset + 10 }) - - setHeldInquiryList((prev) => [...prev, ...inquiryList]) - } - const handleFilter = (e: React.ChangeEvent) => { - console.log(e.target.value) - setHeldInquiryList(inquiryList.filter((inquiry: InquiryList) => inquiry.answerYn === e.target.value)) - if (e.target.value === '') { - setHeldInquiryList(inquiryList) + switch (e.target.value) { + case 'N': + setInquiryListRequest({ ...inquiryListRequest, schAnswerYn: 'N' }) + break + case 'Y': + setInquiryListRequest({ ...inquiryListRequest, schAnswerYn: 'Y' }) + break + default: + reset() + break } } - console.log('heldInquiryList:: ', heldInquiryList) - return ( <> @@ -87,23 +105,39 @@ export default function ListTable() {
    合計 {heldInquiryList.length > 0 ? heldInquiryList[0].totCnt : 0}
    -
      - {heldInquiryList.length > 0 && - heldInquiryList.map((inquiry: InquiryList) => ( -
    • router.push(`/inquiry/${inquiry.qnaNo}`)}> -
      -
      {inquiry.qnaClsLrgCd}
      -
      {inquiry.qstTitle}
      -
      {inquiry.regDt}
      -
      badge.id === inquiry.answerYn)?.color}`}> - {badgeStyle.find((badge) => badge.id === inquiry.answerYn)?.label} + {heldInquiryList.length === 0 || (heldInquiryList.length > 0 && heldInquiryList[0].totCnt === 0) ? ( +
      +
      照会されたデータがありません。
      +
      + ) : ( +
        + {heldInquiryList.length > 0 && + heldInquiryList.map((inquiry: InquiryList) => ( +
      • router.push(`/inquiry/${inquiry.qnaNo}`)}> +
        +
        + {inquiry.qnaClsLrgCd} + {inquiry.qnaClsMidCd} + {inquiry.qnaClsSmlCd} +
        +
        {inquiry.qstTitle}
        +
        {inquiry.regDt}
        +
        badge.id === inquiry.answerYn)?.color}`}> + {badgeStyle.find((badge) => badge.id === inquiry.answerYn)?.label} +
        -
      -
    • - ))} -
    + + ))} + + )}
    - handleLoadMore()} /> + { + setInquiryListRequest({ ...inquiryListRequest, startRow: offset + 10, endRow: offset + 19 }) + setOffset(inquiryListRequest.startRow) + }} + />
    diff --git a/src/hooks/useInquiry.ts b/src/hooks/useInquiry.ts index c49df3e..a497806 100644 --- a/src/hooks/useInquiry.ts +++ b/src/hooks/useInquiry.ts @@ -1,7 +1,9 @@ import { InquiryList, Inquiry, InquirySaveResponse } from '@/types/Inquiry' import { axiosInstance } from '@/libs/axios' -import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query' +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { useInquiryFilterStore } from '@/store/inquiryFilterStore' +import { useMemo } from 'react' +import { useSessionStore } from '@/store/session' export function useInquiry( qnoNo?: number, @@ -17,6 +19,7 @@ export function useInquiry( } { const queryClient = useQueryClient() const { inquiryListRequest } = useInquiryFilterStore() + const { session } = useSessionStore() const { data: inquiryList, isLoading: isLoadingInquiryList } = useQuery({ queryKey: ['inquiryList', inquiryListRequest], @@ -31,16 +34,25 @@ export function useInquiry( return [] } }, + placeholderData: (previousData) => previousData, }) + const inquriyListData = useMemo(() => { + if (isLoadingInquiryList) { + return { inquiryList: inquiryList ?? [] } + } + return { + inquiryList: inquiryList ?? [], + } + }, [inquiryList, isLoadingInquiryList]) + const { data: inquiryDetail, isLoading: isLoadingInquiryDetail } = useQuery({ - queryKey: ['inquiryDetail', qnoNo, compCd], + queryKey: ['inquiryDetail', qnoNo, compCd, session?.userId], queryFn: async () => { try { const resp = await axiosInstance(null).get<{ data: Inquiry }>(`/api/qna/detail`, { - params: { qnoNo, compCd, langCd: 'JA', loginId: 'x112' }, + params: { qnoNo, compCd, langCd: 'JA', loginId: session?.userId ?? '' }, }) - console.log('resp.data.data:: ', resp.data.data) return resp.data.data } catch (error: any) { console.error(error.response) @@ -66,7 +78,7 @@ export function useInquiry( } return { - inquiryList: inquiryList ?? [], + inquiryList: inquriyListData.inquiryList, inquiryDetail: inquiryDetail ?? null, isLoadingInquiryList, isLoadingInquiryDetail, diff --git a/src/store/inquiryFilterStore.ts b/src/store/inquiryFilterStore.ts index af862c2..62f70df 100644 --- a/src/store/inquiryFilterStore.ts +++ b/src/store/inquiryFilterStore.ts @@ -11,15 +11,16 @@ export const useInquiryFilterStore = create((set) => ({ inquiryListRequest: { compCd: '5200', langCd: 'JA', - storeId: 'X112', + storeId: '', siteTpCd: 'QC', schTitle: null, schRegId: null, schFromDt: null, schToDt: null, - startRow: 0, + schAnswerYn: null, + startRow: 1, endRow: 10, - loginId: 'x112', + loginId: '', }, setInquiryListRequest: (inquiryListRequest) => set({ inquiryListRequest }), reset: () => @@ -27,15 +28,16 @@ export const useInquiryFilterStore = create((set) => ({ inquiryListRequest: { compCd: '5200', langCd: 'JA', - storeId: 'X112', + storeId: '', siteTpCd: 'QC', schTitle: '', schRegId: '', schFromDt: '', schToDt: '', - startRow: 0, - endRow: 50, - loginId: 'x112', + schAnswerYn: null, + startRow: 1, + endRow: 10, + loginId: '', }, }), })) diff --git a/src/types/Inquiry.ts b/src/types/Inquiry.ts index c876020..79ac368 100644 --- a/src/types/Inquiry.ts +++ b/src/types/Inquiry.ts @@ -7,6 +7,7 @@ export type InquiryListRequest = { schRegId: string | null //search regId schFromDt: string | null //search start date schToDt: string | null //search end date + schAnswerYn: string | null //search answer yn startRow: number //start row endRow: number //end row loginId: string //login id -- 2.47.2 From 333f06bb642e0e2aaf4a8136d27e3299c87abfb8 Mon Sep 17 00:00:00 2001 From: keyy1315 Date: Thu, 22 May 2025 14:31:25 +0900 Subject: [PATCH 8/8] fix : changed to using axios in custom hooks to apply interceptor from axios --- src/hooks/useInquiry.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hooks/useInquiry.ts b/src/hooks/useInquiry.ts index a497806..816f31d 100644 --- a/src/hooks/useInquiry.ts +++ b/src/hooks/useInquiry.ts @@ -1,5 +1,5 @@ import { InquiryList, Inquiry, InquirySaveResponse } from '@/types/Inquiry' -import { axiosInstance } from '@/libs/axios' +import { useAxios } from '@/hooks/useAxios' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { useInquiryFilterStore } from '@/store/inquiryFilterStore' import { useMemo } from 'react' @@ -20,6 +20,7 @@ export function useInquiry( const queryClient = useQueryClient() const { inquiryListRequest } = useInquiryFilterStore() const { session } = useSessionStore() + const { axiosInstance } = useAxios() const { data: inquiryList, isLoading: isLoadingInquiryList } = useQuery({ queryKey: ['inquiryList', inquiryListRequest], -- 2.47.2