diff --git a/.env.development b/.env.development index 205b2a4..d821f10 100644 --- a/.env.development +++ b/.env.development @@ -9,7 +9,8 @@ NEXT_PUBLIC_QSP_API_URL=http://1.248.227.176:8120 # NEXT_PUBLIC_QSP_API_URL=https://jp-dev.qsalesplatform.com #1:1문의 api -NEXT_PUBLIC_INQUIRY_API_URL=http://172.23.4.129:8110 +NEXT_PUBLIC_INQUIRY_API_URL=https://jp-dev.qsalesplatform.com + #QPARTNER 로그인 api DB_HOST=202.218.61.226 diff --git a/.env.localhost b/.env.localhost index 5f61c6f..1480c7a 100644 --- a/.env.localhost +++ b/.env.localhost @@ -9,7 +9,7 @@ NEXT_PUBLIC_QSP_API_URL=http://1.248.227.176:8120 # NEXT_PUBLIC_QSP_API_URL=https://jp-dev.qsalesplatform.com #1:1문의 api -NEXT_PUBLIC_INQUIRY_API_URL=http://172.23.4.129:8110 +NEXT_PUBLIC_INQUIRY_API_URL=https://jp-dev.qsalesplatform.com #QPARTNER 로그인 api DB_HOST=202.218.61.226 diff --git a/src/app/api/qna/detail/route.ts b/src/app/api/qna/detail/route.ts new file mode 100644 index 0000000..315c18e --- /dev/null +++ b/src/app/api/qna/detail/route.ts @@ -0,0 +1,24 @@ +import { queryStringFormatter } from '@/utils/common-utils' +import axios from 'axios' +import { NextResponse } from 'next/server' + +export async function GET(request: Request) { + const { searchParams } = new URL(request.url) + const params = { + compCd: searchParams.get('compCd'), + qnaNo: searchParams.get('qnoNo'), + langCd: searchParams.get('langCd'), + loginId: searchParams.get('loginId'), + } + + try { + const response = await axios.get(`${process.env.NEXT_PUBLIC_INQUIRY_API_URL}/api/qna/detail?${queryStringFormatter(params)}`) + if (response.status === 200) { + return NextResponse.json(response.data) + } + return NextResponse.json({ error: response.data.result }, { status: response.status }) + } catch (error: any) { + console.error(error.response) + return NextResponse.json({ error: 'route error' }, { status: 500 }) + } +} diff --git a/src/app/api/qna/file/route.ts b/src/app/api/qna/file/route.ts new file mode 100644 index 0000000..01930f2 --- /dev/null +++ b/src/app/api/qna/file/route.ts @@ -0,0 +1,33 @@ +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') + + const srcFileNm = searchParams.get('srcFileNm') + + if (!encodeFileNo) { + return NextResponse.json({ error: 'encodeFileNo is required' }, { status: 400 }) + } + try { + const response = await axios.get(`${process.env.NEXT_PUBLIC_INQUIRY_API_URL}/api/file/downloadFile2`, { + responseType: 'arraybuffer', + params: { + encodeFileNo, + }, + }) + if (response.headers['content-type'] === 'text/html;charset=utf-8') { + return NextResponse.json({ error: 'file not found' }, { status: 404 }) + } + return new NextResponse(response.data, { + status: 200, + headers: { + 'Content-Type': 'application/octet-stream;charset=UTF-8', + 'Content-Disposition': `attachment; filename="${srcFileNm}"`, + }, + }) + } catch (error: any) { + return NextResponse.json({ error: error.response.data }, { status: 500 }) + } +} diff --git a/src/app/api/qna/list/route.ts b/src/app/api/qna/list/route.ts new file mode 100644 index 0000000..f793b98 --- /dev/null +++ b/src/app/api/qna/list/route.ts @@ -0,0 +1,28 @@ +import axios from 'axios' +import { NextResponse } from 'next/server' +import { queryStringFormatter } from '@/utils/common-utils' + +export async function GET(request: Request) { + const { searchParams } = new URL(request.url) + + const params: Record = {} + searchParams.forEach((value, key) => { + const match = key.match(/inquiryListRequest\[(.*)\]/) + if (match) { + params[match[1]] = value + } else { + params[key] = value + } + }) + + try { + const response = await axios.get(`${process.env.NEXT_PUBLIC_INQUIRY_API_URL}/api/qna/list?${queryStringFormatter(params)}`) + if (response.status === 200) { + return NextResponse.json(response.data) + } + return NextResponse.json({ error: 'Failed to fetch qna list' }, { status: response.status }) + } catch (error: any) { + console.error('Error fetching qna list:', error.response.data) + return NextResponse.json({ error: 'route error' }, { status: 500 }) + } +} diff --git a/src/app/api/qna/route.ts b/src/app/api/qna/route.ts new file mode 100644 index 0000000..994a86a --- /dev/null +++ b/src/app/api/qna/route.ts @@ -0,0 +1,19 @@ +import { NextResponse } from 'next/server' +import axios from 'axios' +import { CommonCode } from '@/types/Inquiry' + +export async function GET() { + const response = await axios.get(`${process.env.NEXT_PUBLIC_INQUIRY_API_URL}/api/system/commonCodeListData`) + const codeList: CommonCode[] = [] + response.data.data.apiCommCdList.forEach((item: any) => { + if (item.headCd === '204200' || item.headCd === '204300' || item.headCd === '204400') { + codeList.push({ + headCd: item.headCd, + code: item.code, + name: item.codeJp, + refChar1: item.refChr1, + }) + } + }) + return NextResponse.json({ data: codeList }) +} diff --git a/src/app/api/qna/save/route.ts b/src/app/api/qna/save/route.ts new file mode 100644 index 0000000..b440ef8 --- /dev/null +++ b/src/app/api/qna/save/route.ts @@ -0,0 +1,21 @@ +import axios from 'axios' +import { NextResponse } from 'next/server' + +export async function POST(request: Request) { + const formData = await request.formData() + console.log(formData) + try { + const response = await axios.post(`${process.env.NEXT_PUBLIC_INQUIRY_API_URL}/api/qna/save`, formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }) + if (response.status === 200) { + return NextResponse.json(response.data) + } + return NextResponse.json({ error: response.data }, { status: response.status }) + } catch (error: any) { + console.error('error:: ', error.response) + return NextResponse.json({ error: 'Failed to save qna' }, { status: 500 }) + } +} diff --git a/src/app/inquiry/list/page.tsx b/src/app/inquiry/list/page.tsx index 93fb334..3e69749 100644 --- a/src/app/inquiry/list/page.tsx +++ b/src/app/inquiry/list/page.tsx @@ -1,11 +1,9 @@ -import ListForm from '@/components/inquiry/ListForm' -import ListTable from '@/components/inquiry/ListTable' +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 4eaa9a0..4df9c3b 100644 --- a/src/components/inquiry/Answer.tsx +++ b/src/components/inquiry/Answer.tsx @@ -1,35 +1,37 @@ 'use client' -export default function Answer() { +import { Inquiry } from '@/types/Inquiry' + +export default function Answer({ + inquiryDetail, + downloadFile, +}: { + inquiryDetail: Inquiry + downloadFile: (encodeFileNo: number, srcFileNm: string) => Promise +}) { 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 3dcb625..0aae351 100644 --- a/src/components/inquiry/Detail.tsx +++ b/src/components/inquiry/Detail.tsx @@ -1,20 +1,28 @@ 'use client' -import { useState } from 'react' import Answer from './Answer' +import { useInquiry } from '@/hooks/useInquiry' +import { useParams, useRouter } from 'next/navigation' +import { useSessionStore } from '@/store/session' export default function Detail() { - //todo: 답변 완료 표시를 위해 임시로 추가 해 놓은 state - // 추후에 api 작업 완료후 삭제 - // 답변 완료 클래스 & 하단 답변내용 출력도 - const [inquiry, setInquiry] = useState(true) + const params = useParams() + const id = params.id + + const { inquiryDetail, downloadFile } = useInquiry(Number(id), '5200') + const { commonCodeList } = useInquiry() + const router = useRouter() + + const { session } = useSessionStore() return ( <>
-
回答完了
+
+ {inquiryDetail?.answerYn === 'Y' ? '回答完了' : '回答待ち'} +
@@ -25,71 +33,65 @@ export default function Detail() { - + - - - - - - - - - - + + - - - - - + - + + + + + + + + +
登録日2025.04.10{inquiryDetail?.regDt.split(' ')[0]}
作者Hong gi
名前Kim
番号070-1234-5678顧客名 + {session?.userNm} {session?.builderNo ? `[${session?.builderNo}]` : ''} +
販売店interplug
施工店interplugs{inquiryDetail?.storeNm}
E-mailHong@interplug.co.kr{inquiryDetail?.regEmail}
名前{inquiryDetail?.regUserNm}
お問い合わせ{inquiryDetail?.regUserTelNo}
- 屋根 - 適合性 - 屋根材 -
-
屋根材適合性確認依頼
-
- 入力した内容が表示されます. -
- インストール可能であることを確認してください. -
- 屋根の写真を添付しました. + {commonCodeList.find((code) => code.code === inquiryDetail?.qnaClsLrgCd)?.name} + {commonCodeList.find((code) => code.code === inquiryDetail?.qnaClsMidCd)?.name} + {commonCodeList.find((code) => code.code === inquiryDetail?.qnaClsSmlCd)?.name}
+
{inquiryDetail?.qstTitle}
+
{inquiryDetail?.qstContents}
ファイル添付
    -
  • - -
  • -
  • - -
  • + {inquiryDetail?.listFile?.map((file) => ( +
  • + +
  • + ))}
- {inquiry && } + {inquiryDetail?.answerYn === 'Y' && inquiryDetail && }
-
diff --git a/src/components/inquiry/ListForm.tsx b/src/components/inquiry/ListForm.tsx deleted file mode 100644 index 14f38bb..0000000 --- a/src/components/inquiry/ListForm.tsx +++ /dev/null @@ -1,23 +0,0 @@ -'use client' -import { useRouter } from 'next/navigation' - -export default function ListForm() { - const router = useRouter() - return ( - <> -
-
- -
-
-
- - -
-
-
- - ) -} diff --git a/src/components/inquiry/ListTable.tsx b/src/components/inquiry/ListTable.tsx deleted file mode 100644 index d65fa85..0000000 --- a/src/components/inquiry/ListTable.tsx +++ /dev/null @@ -1,134 +0,0 @@ -'use client' - -import { use, useEffect, useState } from 'react' -import LoadMoreButton from '../LoadMoreButton' - -const inquiryDummy = [ - { id: 1, category: '屋根', title: '屋根材適合性確認依頼', date: '2025.04.02', status: 'completed' }, - { id: 2, category: '外壁', title: '外壁仕上げ材確認', date: '2025.04.03', status: 'completed' }, - { id: 3, category: '設備', title: '換気システム図面確認', date: '2025.04.04', status: 'completed' }, - { id: 4, category: '基礎', title: '基礎配筋検査依頼', date: '2025.04.05', status: 'completed' }, - { id: 5, category: '内装', title: 'クロス仕様確認', date: '2025.04.06', status: 'waiting' }, - { id: 6, category: '構造', title: '耐震壁位置変更申請', date: '2025.04.07', status: 'completed' }, - { id: 7, category: '屋根', title: '雨樋取付方法確認', date: '2025.04.08', status: 'completed' }, - { id: 8, category: '外構', title: 'フェンス高さ変更相談', date: '2025.04.09', status: 'completed' }, - { id: 9, category: '設備', title: '給湯器設置位置確認', date: '2025.04.10', status: 'completed' }, - { id: 10, category: '外壁', title: 'タイル割付案確認依頼', date: '2025.04.11', status: 'waiting' }, - { id: 11, category: '内装', title: '照明配置図面確認', date: '2025.04.12', status: 'completed' }, - { id: 12, category: '構造', title: '梁補強案確認', date: '2025.04.13', status: 'completed' }, - { id: 13, category: '基礎', title: '杭長設計確認依頼', date: '2025.04.14', status: 'completed' }, - { id: 14, category: '屋根', title: '断熱材施工方法確認', date: '2025.04.15', status: 'completed' }, - { id: 15, category: '外構', title: '駐車場勾配図確認', date: '2025.04.16', status: 'completed' }, -] - -const badgeStyle = [ - { - id: 'completed', - label: '回答完了', - color: 'blue', - }, - { - id: 'waiting', - label: '回答待ち', - color: 'orange', - }, -] -export default function ListTable() { - const [offset, setOffset] = useState(0) - const [hasMore, setHasMore] = useState(true) - - const inquiryList = inquiryDummy.slice(0, offset + 10) - - useEffect(() => { - if (inquiryDummy.length > offset + 10) { - setHasMore(true) - } else { - setHasMore(false) - } - }, [inquiryList]) - - return ( - <> -
-
-
-
- - -
-
-
- -
-
-
-
- 合計 98個 -
-
    -
  • -
    -
    - 屋根 - 適合性 - 屋根材 -
    -
    屋根材適合性確認依頼
    -
    2025.04.02
    -
    回答待ち
    -
    -
  • -
  • -
    -
    - 屋根 - 適合性 - 屋根材 -
    -
    設置可能ですか?
    -
    2025.04.02
    -
    回答完了
    -
    -
  • -
  • -
    -
    - 屋根 - 適合性 - 屋根材 -
    -
    屋根材適合性確認依頼屋根材適合性確認依頼屋根材適合性確認依頼屋根材適合性確認依頼
    -
    2025.04.02
    -
    回答待ち
    -
    -
  • -
  • -
    -
    - 屋根 - 適合性 - 屋根材 -
    -
    設置可能ですか?
    -
    2025.04.02
    -
    回答完了
    -
    -
  • -
  • -
    -
    조회된 데이터가 없습니다
    -
    -
  • -
-
- setOffset(offset + 10)} /> -
-
-
- - ) -} diff --git a/src/components/inquiry/RegistForm.tsx b/src/components/inquiry/RegistForm.tsx index fc55a63..93df62f 100644 --- a/src/components/inquiry/RegistForm.tsx +++ b/src/components/inquiry/RegistForm.tsx @@ -1,5 +1,129 @@ 'use client' + +import { useInquiry } from '@/hooks/useInquiry' +import { useSessionStore } from '@/store/session' +import { InquiryRequest } from '@/types/Inquiry' + +import { useEffect, useState } from 'react' +import { useRouter } from 'next/navigation' export default function RegistForm() { + const { saveInquiry, isSavingInquiry } = useInquiry() + const { session } = useSessionStore() + const router = useRouter() + + const [inquiryRequest, setInquiryRequest] = useState({ + compCd: '5200', + siteTpCd: 'QC', + qnaClsLrgCd: '', + qnaClsMidCd: '', + qnaClsSmlCd: null, + title: '', + contents: '', + regId: '', + regUserNm: '', + regUserTelNo: null, + 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 ?? '', + qstMail: session?.email ?? '', + }) + } + }, [session]) + + const { commonCodeList } = useInquiry() + + const [attachedFiles, setAttachedFiles] = useState([]) + + const handleFileChange = (e: React.ChangeEvent) => { + const files = e.target.files + if (files && files.length > 0) { + setAttachedFiles(attachedFiles.concat(Array.from(files))) + } + e.target.value = '' + } + + const handleRemoveFile = (index: number) => { + 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 + } + if (inquiryRequest.title.length > 100) { + alert('お問い合わせタイトルは100文字以内で入力してください。') + focusOnRequiredField('title') + return + } + if (inquiryRequest.contents.length > 2000) { + alert('お問い合わせ内容は2,000文字以内で入力してください。') + focusOnRequiredField('contents') + return + } + + const formData = new FormData() + attachedFiles.forEach((file) => { + formData.append('files', file) + }) + Object.entries(inquiryRequest).forEach(([key, value]) => { + formData.append(key, value ?? '') + }) + window.neoConfirm( + 'お問い合わせを登録しますか? Hanwha Japanの担当者にお問い合わせメールが送信されます。', + async () => { + const res = await saveInquiry(formData) + alert('保存されました。') + router.push(`/inquiry/${res.qnaNo}`) + }, + () => null, + ) + } + + const handlePhoneNumberChange = (e: React.ChangeEvent) => { + const value = e.target.value.replace(/[^\d]/g, '') + + let formattedNumber = '' + if (value.length <= 3) { + formattedNumber = value + } else if (value.length <= 7) { + formattedNumber = `${value.slice(0, 3)}-${value.slice(3)}` + } else { + formattedNumber = `${value.slice(0, 3)}-${value.slice(3, 7)}-${value.slice(7, 11)}` + } + + setInquiryRequest({ ...inquiryRequest, regUserTelNo: formattedNumber }) + } + return ( <>
@@ -9,45 +133,113 @@ export default function RegistForm() { お問い合わせタイプ *
- -
-
- -
-
- setInquiryRequest({ ...inquiryRequest, qnaClsLrgCd: e.target.value })} + > + + {commonCodeList + .filter((code) => code.headCd === '204200') + .map((code) => ( + + ))}
+ {commonCodeList.filter((code) => code.refChar1 === inquiryRequest.qnaClsLrgCd).length > 0 && ( +
+ +
+ )} + {commonCodeList.filter((code) => code.refChar1 === inquiryRequest.qnaClsMidCd).length > 0 && ( +
+ +
+ )}
名前 *
- + setInquiryRequest({ ...inquiryRequest, regUserNm: e.target.value })} + value={inquiryRequest.regUserNm} + id="regUserNm" + />
電話番号
- + +
+
+
+
+ E-mail * +
+
+ setInquiryRequest({ ...inquiryRequest, qstMail: e.target.value })} + id="qstMail" + />
@@ -55,15 +247,30 @@ export default function RegistForm() { お問い合わせタイトル *
- + setInquiryRequest({ ...inquiryRequest, title: e.target.value })} + maxLength={100} + id="title" + />
- お問い合わせタイプ * + お問い合わせ内容 *
- +
@@ -72,29 +279,28 @@ 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 new file mode 100644 index 0000000..badcbcd --- /dev/null +++ b/src/components/inquiry/list/ListForm.tsx @@ -0,0 +1,49 @@ +'use client' +import { useInquiryFilterStore } from '@/store/inquiryFilterStore' +import { useRouter } from 'next/navigation' +import { useState } from 'react' + +export default function ListForm() { + const router = useRouter() + const { inquiryListRequest, setInquiryListRequest, reset } = useInquiryFilterStore() + const [searchKeyword, setSearchKeyword] = useState(inquiryListRequest.schTitle ?? '') + + const handleSearch = () => { + if (searchKeyword.length >= 2) { + reset() + setInquiryListRequest({ ...inquiryListRequest, schTitle: searchKeyword }) + } else { + alert('2文字以上入力してください') + } + } + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + handleSearch() + } + } + + return ( + <> +
+
+ +
+
+
+ setSearchKeyword(e.target.value)} + onKeyDown={handleKeyDown} + /> + +
+
+
+ + ) +} diff --git a/src/components/inquiry/list/ListTable.tsx b/src/components/inquiry/list/ListTable.tsx new file mode 100644 index 0000000..cbb88af --- /dev/null +++ b/src/components/inquiry/list/ListTable.tsx @@ -0,0 +1,143 @@ +'use client' + +import { useEffect, useState } from 'react' +import LoadMoreButton from '../../LoadMoreButton' +import { useInquiry } from '@/hooks/useInquiry' +import { InquiryList } from '@/types/Inquiry' +import { usePathname, useRouter } from 'next/navigation' +import { useInquiryFilterStore } from '@/store/inquiryFilterStore' +import { useSessionStore } from '@/store/session' +import ListForm from './ListForm' + +const badgeStyle = [ + { + id: 'Y', + label: '回答完了', + color: 'orange', + }, + { + id: 'N', + label: '回答待ち', + color: 'blue', + }, +] +export default function ListTable() { + const router = useRouter() + const pathname = usePathname() + + const { inquiryList, isLoadingInquiryList } = useInquiry() + const { inquiryListRequest, setInquiryListRequest, reset, offset, setOffset } = useInquiryFilterStore() + + const [hasMore, setHasMore] = useState(false) + + const [heldInquiryList, setHeldInquiryList] = useState([]) + + const { session } = useSessionStore() + + useEffect(() => { + setOffset(1) + setHeldInquiryList([]) + }, [pathname]) + + useEffect(() => { + if (!session.isLoggedIn || isLoadingInquiryList) return + if (session.isLoggedIn) { + setInquiryListRequest({ ...inquiryListRequest, storeId: session.storeId ?? '', loginId: session.userId ?? '' }) + } + if (inquiryList.length > 0 && inquiryList[0].totCnt > 0) { + if (offset > 1) { + setHeldInquiryList([...heldInquiryList, ...inquiryList]) + } else { + setHeldInquiryList(inquiryList) + } + setHasMore(inquiryList[0].totCnt > offset + 9) + } else { + setHeldInquiryList([]) + setHasMore(false) + } + }, [session, inquiryList]) + + const handleMyInquiry = () => { + setOffset(1) + setInquiryListRequest({ + ...inquiryListRequest, + schRegId: inquiryListRequest.schRegId ? null : session.userId, + }) + } + + const handleFilter = (e: React.ChangeEvent) => { + switch (e.target.value) { + case 'N': + setInquiryListRequest({ ...inquiryListRequest, schAnswerYn: 'N' }) + break + case 'Y': + setInquiryListRequest({ ...inquiryListRequest, schAnswerYn: 'Y' }) + break + default: + reset() + break + } + } + + return ( + <> + +
+
+
+
+ + +
+
+
+ +
+
+
+
+ 合計 {heldInquiryList.length > 0 ? heldInquiryList[0].totCnt : 0}個 +
+
    + {heldInquiryList.length === 0 || (heldInquiryList.length > 0 && heldInquiryList[0].totCnt === 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} +
    +
    +
  • + )) + )} +
+
+
+ { + setOffset(offset + 10) + }} + /> +
+
+ + ) +} diff --git a/src/hooks/useInquiry.ts b/src/hooks/useInquiry.ts new file mode 100644 index 0000000..a5ed2ab --- /dev/null +++ b/src/hooks/useInquiry.ts @@ -0,0 +1,116 @@ +import { InquiryList, Inquiry, InquirySaveResponse, CommonCode } from '@/types/Inquiry' +import { useAxios } from '@/hooks/useAxios' +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, + compCd?: string, +): { + inquiryList: InquiryList[] + isLoadingInquiryList: boolean + inquiryDetail: Inquiry | null + isLoadingInquiryDetail: boolean + isSavingInquiry: boolean + saveInquiry: (formData: FormData) => Promise + downloadFile: (encodeFileNo: number, srcFileNm: string) => Promise + commonCodeList: CommonCode[] +} { + const queryClient = useQueryClient() + const { inquiryListRequest, offset } = useInquiryFilterStore() + const { session } = useSessionStore() + const { axiosInstance } = useAxios() + + const { data: inquiryList, isLoading: isLoadingInquiryList } = useQuery({ + queryKey: ['inquiryList', inquiryListRequest, offset], + queryFn: async () => { + try { + const resp = await axiosInstance(null).get<{ data: InquiryList[] }>(`/api/qna/list`, { + params: { inquiryListRequest, startRow: offset, endRow: offset + 9 }, + }) + return resp.data.data + } catch (error: any) { + console.error(error.response.data) + return [] + } + }, + enabled: !!inquiryListRequest, + }) + + const inquriyListData = useMemo(() => { + if (isLoadingInquiryList) { + return { inquiryList: [] } + } + return { + inquiryList: inquiryList ?? [], + } + }, [inquiryList, isLoadingInquiryList]) + + const { data: inquiryDetail, isLoading: isLoadingInquiryDetail } = useQuery({ + 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: session?.userId ?? '' }, + }) + 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 (formData: FormData) => { + const resp = await axiosInstance(null).post<{ data: InquirySaveResponse }>('/api/qna/save', formData) + return resp.data.data + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['inquiryList'] }) + }, + }) + + const downloadFile = async (encodeFileNo: number, srcFileNm: string) => { + try { + const resp = await axiosInstance(null).get(`/api/qna/file`, { params: { encodeFileNo, srcFileNm } }) + const blob = new Blob([resp.data], { type: 'application/octet-stream;charset=UTF-8' }) + const url = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + a.download = `${srcFileNm}` + a.click() + URL.revokeObjectURL(url) + return blob + } catch (error: any) { + if (error.response.status === 404) { + alert('ファイルが見つかりません') + } + return null + } + } + + const { data: commonCodeList, isLoading: isLoadingCommonCodeList } = useQuery({ + queryKey: ['commonCodeList'], + queryFn: async () => { + const resp = await axiosInstance(null).get<{ data: CommonCode[] }>(`/api/qna`) + return resp.data + }, + staleTime: Infinity, + gcTime: Infinity, + }) + + return { + inquiryList: inquriyListData.inquiryList, + inquiryDetail: inquiryDetail ?? null, + isLoadingInquiryList, + isLoadingInquiryDetail, + isSavingInquiry, + saveInquiry, + downloadFile, + commonCodeList: commonCodeList?.data ?? [], + } +} diff --git a/src/store/inquiryFilterStore.ts b/src/store/inquiryFilterStore.ts index 0960c85..b3fb8d9 100644 --- a/src/store/inquiryFilterStore.ts +++ b/src/store/inquiryFilterStore.ts @@ -1,41 +1,44 @@ +import { InquiryListRequest } from '@/types/Inquiry' import { create } from 'zustand' -export const FILTER_OPTIONS = [ - { - id: 'all', - label: '全体', - }, - { - id: 'completed', - label: '回答完了', - }, - { - id: 'waiting', - label: '回答待ち', - }, -] -export type FILTER_OPTIONS_ENUM = (typeof FILTER_OPTIONS)[number]['id'] - type InquiryFilterState = { - keyword: string - filter: FILTER_OPTIONS_ENUM - isMySurvey: string | null - offset: number - setKeyword: (keyword: string) => void - setFilter: (filter: FILTER_OPTIONS_ENUM) => void - setIsMySurvey: (isMySurvey: string | null) => void - setOffset: (offset: number) => void + inquiryListRequest: InquiryListRequest + setInquiryListRequest: (inquiryListRequest: InquiryListRequest) => void reset: () => void + offset: number + setOffset: (offset: number) => void } export const useInquiryFilterStore = create((set) => ({ - keyword: '', - filter: 'all', - isMySurvey: null, - offset: 0, - setKeyword: (keyword) => set({ keyword }), - setFilter: (filter) => set({ filter }), - setIsMySurvey: (isMySurvey) => set({ isMySurvey }), + inquiryListRequest: { + compCd: '5200', + langCd: 'JA', + storeId: '', + siteTpCd: 'QC', + schTitle: null, + schRegId: null, + schFromDt: null, + schToDt: null, + schAnswerYn: null, + loginId: '', + }, + setInquiryListRequest: (inquiryListRequest) => set({ inquiryListRequest }), + reset: () => + set({ + inquiryListRequest: { + compCd: '5200', + langCd: 'JA', + storeId: '', + siteTpCd: 'QC', + schTitle: '', + schRegId: '', + schFromDt: '', + schToDt: '', + schAnswerYn: null, + loginId: '', + }, + offset: 1, + }), + offset: 1, setOffset: (offset) => set({ offset }), - reset: () => set({ keyword: '', filter: 'all', isMySurvey: null, offset: 0 }), })) diff --git a/src/styles/components/_sub.scss b/src/styles/components/_sub.scss index 73c5fc4..939c66e 100644 --- a/src/styles/components/_sub.scss +++ b/src/styles/components/_sub.scss @@ -1,38 +1,38 @@ -@use "../abstracts" as *; +@use '../abstracts' as *; // input form 공통 -.data-input-form-bx{ +.data-input-form-bx { margin-bottom: 18px; - &:last-child{ + &:last-child { margin-bottom: 0; } - .data-input-form-tit{ + .data-input-form-tit { @include defaultFont($font-s-13, $font-w-500, $font-c); margin-bottom: 10px; - .import{ - color: #F00; + .import { + color: #f00; } - span{ + span { display: block; - @include defaultFont($font-s-13, $font-w-400, #A8B6C7); + @include defaultFont($font-s-13, $font-w-400, #a8b6c7); } } - .data-input-guide{ + .data-input-guide { margin-top: 8px; - @include defaultFont($font-s-13, $font-w-400, #A8B6C7); + @include defaultFont($font-s-13, $font-w-400, #a8b6c7); } -} +} -.btn-flex-wrap{ +.btn-flex-wrap { @include flex(5px); margin-top: 24px; - .btn-bx{ + .btn-bx { flex: 1; } - &.com{ - .btn-bx{ + &.com { + .btn-bx { flex: 1 1 auto; - button{ + button { font-size: 12px; } } @@ -40,13 +40,13 @@ } // 매물 common -.top-btn{ +.top-btn { position: fixed; bottom: 96px; right: 15px; width: 38px; height: 38px; - background-color: rgba(0, 0, 0, 0.50); + background-color: rgba(0, 0, 0, 0.5); background-image: url(/assets/images/sub/top_btn_icon.svg); background-position: center; background-repeat: no-repeat; @@ -55,68 +55,68 @@ z-index: 90000; } -.sale-contents{ +.sale-contents { width: 100%; - background-color: #F5F5F5; - .sale-frame{ + background-color: #f5f5f5; + .sale-frame { padding: 0 20px; - border-top: 1px solid #ECECEC; - border-bottom: 1px solid #ECECEC; + border-top: 1px solid #ececec; + border-bottom: 1px solid #ececec; margin-bottom: 10px; padding-bottom: 24px; padding-top: 24px; background-color: $white-fff; - &:first-child{ + &:first-child { padding-top: 0; border-top: none; } - &:last-child{ + &:last-child { padding-bottom: 0; border-bottom: none; margin-bottom: 0; } } } -.sale-form-btn-wrap{ - padding: 20px 20px 0 ; +.sale-form-btn-wrap { + padding: 20px 20px 0; background-color: #fff; - .btn-flex-wrap{ + .btn-flex-wrap { margin-top: 0; } } // 매물 목록 -.sale-form-bx{ +.sale-form-bx { margin-bottom: 14px; - &:last-child{ + &:last-child { margin-bottom: 0; } -} -.sale-list-wrap{ - .sale-list-item{ +} +.sale-list-wrap { + .sale-list-item { padding-top: 14px; padding-bottom: 14px; - border-bottom: 1px solid #ECECEC; + border-bottom: 1px solid #ececec; cursor: pointer; - &:first-child{ + &:first-child { padding-top: 0; } - &:last-child{ + &:last-child { border-bottom: none; padding-bottom: 0; } } } -.sale-item-bx{ - .sale-item-date-bx{ +.sale-item-bx { + .sale-item-date-bx { @include flex(0px); align-items: center; margin-bottom: 9px; - .sale-item-num{ + .sale-item-num { position: relative; @include defaultFont($font-s-13, $font-w-400, $font-c); padding-right: 6px; - &::after{ + &::after { content: ''; position: absolute; top: 50%; @@ -124,31 +124,31 @@ transform: translateY(-50%); width: 1px; height: 10px; - background-color: #A2ABB8; + background-color: #a2abb8; } } - .sale-item-date{ - @include defaultFont($font-s-13, $font-w-400, #A2ABB8); + .sale-item-date { + @include defaultFont($font-s-13, $font-w-400, #a2abb8); padding-left: 6px; } } - .sale-item-tit{ + .sale-item-tit { @include defaultFont($font-s-15, $font-w-500, $font-c); @include ellipsis(1); margin-bottom: 9px; } - .sale-item-customer{ + .sale-item-customer { @include defaultFont($font-s-13, $font-w-400, $font-c); margin-bottom: 9px; } - .sale-item-update-bx{ + .sale-item-update-bx { @include flex(0px); align-items: center; - .sale-item-name{ + .sale-item-name { position: relative; - @include defaultFont($font-s-13, $font-w-400, #A2ABB8); + @include defaultFont($font-s-13, $font-w-400, #a2abb8); padding-right: 6px; - &::after{ + &::after { content: ''; position: absolute; top: 50%; @@ -156,176 +156,177 @@ transform: translateY(-50%); width: 1px; height: 10px; - background-color: #A2ABB8; + background-color: #a2abb8; } } - .sale-item-update{ - @include defaultFont($font-s-13, $font-w-400, #A2ABB8); + .sale-item-update { + @include defaultFont($font-s-13, $font-w-400, #a2abb8); padding-left: 6px; } } - &.nodata{ - .sale-item-nodata{ + &.nodata { + .sale-item-nodata { padding: 5px 0; text-align: center; @include defaultFont($font-s-15, $font-w-500, $font-c); } } } -.sale-edit-btn{ +.sale-edit-btn { margin-top: 24px; } // 매물 상세 -.sale-data-table-wrap{ +.sale-data-table-wrap { padding: 24px; background-color: #fff; - border-top: 1px solid #ECECEC; + border-top: 1px solid #ececec; } -.sale-data-table{ +.sale-data-table { width: 100%; table-layout: fixed; - tbody{ - tr{ - th{ + tbody { + tr { + th { @include defaultFont($font-s-13, $font-w-500, $font-c); vertical-align: top; padding: 5px 0; } - td{ + td { @include defaultFont($font-s-13, $font-w-400, $font-c); padding: 5px 0 8px 14px; - .data-down{ + .data-down { @include flex(8px); align-items: center; - color: #1259CB; - i{ + color: #1259cb; + i { display: block; width: 8px; height: 12px; - background: url(/assets/images/sub/down_icon.svg)no-repeat center; + background: url(/assets/images/sub/down_icon.svg) no-repeat center; background-size: cover; } } } - &:first-child{ - th,td{ + &:first-child { + th, + td { padding-top: 0; } } - &:last-child{ - th,td{ + &:last-child { + th, + td { padding-bottom: 0; } } } } -} +} -.sale-detail-toggle-wrap{ - border-top: 1px solid #ECECEC; +.sale-detail-toggle-wrap { + border-top: 1px solid #ececec; } -.sale-detail-toggle-bx{ - border-bottom: 1px solid #ECECEC; +.sale-detail-toggle-bx { + border-bottom: 1px solid #ececec; } -.sale-detail-toggle-head{ +.sale-detail-toggle-head { @include flex(5px); padding: 14px 18px; background-color: $white-fff; cursor: pointer; - .sale-detail-toggle-name{ + .sale-detail-toggle-name { @include defaultFont($font-s-13, $font-w-500, $font-c); } - .sale-detail-toggle-btn-wrap{ + .sale-detail-toggle-btn-wrap { margin-left: auto; - .sale-detail-toggle-btn{ + .sale-detail-toggle-btn { display: block; width: 22px; height: 22px; - background: url(/assets/images/sub/sale_toggle_btn.svg)no-repeat center; - background-size: cover + background: url(/assets/images/sub/sale_toggle_btn.svg) no-repeat center; + background-size: cover; } } } -.sale-detail-toggle-cont{ +.sale-detail-toggle-cont { display: none; - .sale-frame{ + .sale-frame { padding: 24px 20px; - &:first-child{ + &:first-child { padding-top: 24px; } - &:last-child{ + &:last-child { padding-bottom: 24px; } } } -.sale-detail-toggle-bx{ - &.act{ - .sale-detail-toggle-head{ - background-color: #5F738E; - .sale-detail-toggle-name{ - color: #fff +.sale-detail-toggle-bx { + &.act { + .sale-detail-toggle-head { + background-color: #5f738e; + .sale-detail-toggle-name { + color: #fff; } - .sale-detail-toggle-btn-wrap{ - .sale-detail-toggle-btn{ - background: url(/assets/images/sub/sale_toggle_btn_white.svg)no-repeat center; + .sale-detail-toggle-btn-wrap { + .sale-detail-toggle-btn { + background: url(/assets/images/sub/sale_toggle_btn_white.svg) no-repeat center; } } } - .sale-detail-toggle-cont{ + .sale-detail-toggle-cont { display: block; } } } // 매물 기본정보 -.form-flex{ +.form-flex { @include flex(5px); - .form-bx{ + .form-bx { flex: 1; } } -.form-btn{ +.form-btn { margin-top: 12px; } // 매물 전기 지붕정보 -.sale-roof-title{ +.sale-roof-title { @include defaultFont($font-s-15, $font-w-500, $font-c); padding-bottom: 10px; margin-bottom: 20px; - border-bottom: 1px solid #2E3A59; + border-bottom: 1px solid #2e3a59; } -.data-check-wrap{ +.data-check-wrap { @include flex(10px); flex-wrap: wrap; margin-bottom: 12px; .radio-form-box, - .check-form-box{ + .check-form-box { width: calc(50% - 5px); } - &.mb0{ + &.mb0 { margin-bottom: 0; } } -.data-input{ - &.flex{ +.data-input { + &.flex { @include flex(8px); align-items: center; - span{ + span { flex: none; @include defaultFont($font-s-13, $font-w-400, $font-c); } } } - // 1:1 문의 common -.inquiry-frame{ +.inquiry-frame { padding: 0 20px; } -.badge{ +.badge { min-width: 60px; height: 30px; line-height: 30px; @@ -334,65 +335,64 @@ text-align: center; font-size: $font-s-12; font-weight: $font-w-500; - &.blue{ - color: #5497E9; - background-color: #ECF5FF; + &.blue { + color: #5497e9; + background-color: #ecf5ff; } - &.orange{ - color: #F86A56; - background-color: #FFEFED; + &.orange { + color: #f86a56; + background-color: #ffefed; } - &.block{ + &.block { width: 100%; - } } // 1:1 문의 목록 -.inquiry-table-filter{ +.inquiry-table-filter { margin-bottom: 24px; - .filter-check{ + .filter-check { margin-bottom: 12px; } } -.inquiry-list-tit{ +.inquiry-list-tit { padding-bottom: 10px; - border-bottom: 1px solid #2E3A59; + border-bottom: 1px solid #2e3a59; @include defaultFont($font-s-13, $font-w-400, $font-c); - span{ + span { font-weight: $font-w-500; } } -.inquiry-list{ - .inquiry-item{ +.inquiry-list { + .inquiry-item { padding: 10px 0; cursor: pointer; - border-bottom: 1px solid #ECECEC; - &:last-child{ + border-bottom: 1px solid #ececec; + &:last-child { border-bottom: none; padding-bottom: 0; } - .inquiry-item-bx{ + .inquiry-item-bx { position: relative; padding-right: 70px; - .inquiry-item-category{ + .inquiry-item-category { display: flex; align-items: center; margin-bottom: 5px; - span{ + span { position: relative; display: block; @include defaultFont($font-s-13, $font-w-400, $font-c); padding: 0 6px; - &:first-child{ + &:first-child { padding-left: 0; } - &:last-child{ + &:last-child { padding-right: 0; - &::before{ + &::before { display: none; } } - &::before{ + &::before { content: ''; position: absolute; top: 50%; @@ -400,26 +400,31 @@ transform: translateY(-50%); width: 1px; height: 10px; - background-color: #A2ABB8; + background-color: #a2abb8; } } } - .inquiry-item-tit{ + .inquiry-item-tit { @include defaultFont($font-s-15, $font-w-500, $font-c); @include ellipsis(1); margin-bottom: 5px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + width: 100%; + display: block; } - .inquiry-item-date{ - @include defaultFont($font-s-13, $font-w-400, #A2ABB8); + .inquiry-item-date { + @include defaultFont($font-s-13, $font-w-400, #a2abb8); } - .inquiry-badge{ + .inquiry-badge { position: absolute; top: 0; right: 0; } - &.nodata{ + &.nodata { padding-right: 0; - .inquiry-item-nodata{ + .inquiry-item-nodata { padding: 10px 0; text-align: center; @include defaultFont($font-s-15, $font-w-500, $font-c); @@ -430,42 +435,45 @@ } // 1:1문의 작성 -.inquiry-file-wrap{ +.textarea-form { + white-space: pre-wrap; +} +.inquiry-file-wrap { margin-top: 20px; - .file-list-wrap{ + .file-list-wrap { margin-top: 14px; } } -.file-list-tit{ +.file-list-tit { @include defaultFont($font-s-13, $font-w-500, $font-c); } -.file-list{ +.file-list { margin-top: 14px; - .file-item{ - border-top: 1px solid #EDEDED; + .file-item { + border-top: 1px solid #ededed; cursor: default; - .file-item-bx{ + .file-item-bx { width: 100%; padding: 14px 0; @include flex(0px); align-items: center; - .file-item-name{ + .file-item-name { @include ellipsis(1); @include defaultFont($font-s-13, $font-w-400, $font-c); padding-right: 10px; } - .file-del{ + .file-del { flex: none; display: block; margin-left: auto; width: 16px; height: 16px; - background: url(/assets/images/common/id_delete_icon.svg)no-repeat center; + background: url(/assets/images/common/id_delete_icon.svg) no-repeat center; background-size: cover; } } - &:last-child{ - .file-item-bx{ + &:last-child { + .file-item-bx { padding-bottom: 0; } } @@ -473,33 +481,33 @@ } // 1:1 문의 상세 -.inquiry-detail-data-table{ +.inquiry-detail-data-table { padding: 20px 0; - border-bottom: 1px solid #ECECEC; + border-bottom: 1px solid #ececec; } -.inquiry-detail-data{ +.inquiry-detail-data { padding: 20px 0; - border-bottom: 1px solid #2E3A59; + border-bottom: 1px solid #2e3a59; margin-bottom: 24px; - .inquiry-detail-category{ + .inquiry-detail-category { display: flex; align-items: center; margin-bottom: 3px; - span{ + span { position: relative; display: block; @include defaultFont($font-s-13, $font-w-400, $font-c); padding: 0 6px; - &:first-child{ + &:first-child { padding-left: 0; } - &:last-child{ + &:last-child { padding-right: 0; - &::before{ + &::before { display: none; } } - &::before{ + &::before { content: ''; position: absolute; top: 50%; @@ -507,151 +515,154 @@ transform: translateY(-50%); width: 1px; height: 10px; - background-color: #A2ABB8; + background-color: #a2abb8; } } } - .inquiry-detail-tit{ + .inquiry-detail-tit { @include defaultFont($font-s-15, $font-w-500, $font-c); margin-bottom: 10px; + word-wrap: break-word; + white-space: normal; + overflow-wrap: break-word; } - .inquiry-detail-txt{ + .inquiry-detail-txt { @include defaultFont($font-s-13, $font-w-400, $font-c); + white-space: pre-line; } } // 1:1 문의 답변 -.inquiry-answer-wrap{ +.inquiry-answer-wrap { margin-top: 24px; - } -.inquiry-answer-header{ +.inquiry-answer-header { padding: 20px 0; - border-top: 1px solid #F86A56; - border-bottom: 1px solid #ECECEC; - .inquiry-answer-tit{ - @include defaultFont($font-s-14, $font-w-500, #F86A56); + border-top: 1px solid #f86a56; + border-bottom: 1px solid #ececec; + .inquiry-answer-tit { + @include defaultFont($font-s-14, $font-w-500, #f86a56); margin-bottom: 5px; } - .inquiry-answer-date{ - @include defaultFont($font-s-13, $font-w-400, #F86A56); + .inquiry-answer-date { + @include defaultFont($font-s-13, $font-w-400, #f86a56); } } -.inquiry-answer-tit{ +.inquiry-answer-tit { @include defaultFont($font-s-13, $font-w-400, $font-c); margin-bottom: 3px; } // 비밀번호 변경 -.border-frame{ +.border-frame { padding: 20px; - border-top: 1px solid #ECECEC; - border-bottom: 1px solid #ECECEC; + border-top: 1px solid #ececec; + border-bottom: 1px solid #ececec; background-color: #fff; margin-bottom: 10px; - &:last-child{ + &:last-child { border-bottom: none; padding-bottom: 0; margin-bottom: 0; } } -.pw-guide{ - .pw-guide-tit{ - @include defaultFont($font-s-16, $font-w-500, #1259CB); +.pw-guide { + .pw-guide-tit { + @include defaultFont($font-s-16, $font-w-500, #1259cb); } - .pw-guide-txt{ - @include defaultFont($font-s-13, $font-w-400, #417DDC); + .pw-guide-txt { + @include defaultFont($font-s-13, $font-w-400, #417ddc); } } // 지붕재 적합성 -.compliance-icon{ +.compliance-icon { display: flex; } -.compliance-check-wrap{ +.compliance-check-wrap { padding-top: 10px; } -.compliance-check-bx{ +.compliance-check-bx { position: relative; padding: 14px 18px; - border: 1px solid #EFEFEF; + border: 1px solid #efefef; border-radius: 4px; margin-bottom: 10px; - &:last-child{ + &:last-child { margin-bottom: 0; } - &.act{ - .bx-btn{ + &.act { + .bx-btn { transform: rotate(0) !important; } - .reference-list{ - display: block + .reference-list { + display: block; } } } -.check-name-wrap{ +.check-name-wrap { @include flex(0px); align-items: center; - .check-name{ + .check-name { @include defaultFont($font-s-13, $font-w-500, $font-c); } - .check-name-btn{ + .check-name-btn { padding-left: 5px; margin-left: auto; - .bx-btn{ + .bx-btn { display: block; width: 22px; height: 22px; - background: url(/assets/images/sub/compliance_bx_icon.svg)no-repeat center; + background: url(/assets/images/sub/compliance_bx_icon.svg) no-repeat center; transform: rotate(180deg); } } } -.reference-list{ +.reference-list { display: none; margin-top: 10px; padding-top: 14px; - border-top: 1px solid #ECECEC; - transition: all .15s ease-in-out; - .reference-item{ + border-top: 1px solid #ececec; + transition: all 0.15s ease-in-out; + .reference-item { margin-bottom: 8px; padding-left: 14px; - .reference-item-bx{ + .reference-item-bx { @include flex(10px); @include defaultFont($font-s-13, $font-w-400, $font-c); align-items: center; } - &:last-child{ + &:last-child { margin-bottom: 0; } } - &.check{ - .reference-item{ + &.check { + .reference-item { margin-bottom: 14px; } } } -.compliace-nosearch{ +.compliace-nosearch { padding: 30px 0; - span{ + span { display: block; @include defaultFont($font-s-13, $font-w-400, $font-c); text-align: center; } } -.check-item-wrap{ +.check-item-wrap { @include flex(0px); align-items: center; } -.compliance-icon-wrap{ +.compliance-icon-wrap { margin-left: auto; min-width: 44px; @include flex(0px); align-items: center; } -.float-btn-wrap{ +.float-btn-wrap { position: sticky; bottom: 10px; left: 0; @@ -659,14 +670,14 @@ background-color: #fff; z-index: 9; } -@media screen and (max-width: 360px){ - .btn-flex-wrap{ +@media screen and (max-width: 360px) { + .btn-flex-wrap { flex-direction: column; } - .data-check-wrap{ + .data-check-wrap { .radio-form-box, - .check-form-box{ + .check-form-box { width: 100%; } } -} \ No newline at end of file +} diff --git a/src/types/Inquiry.ts b/src/types/Inquiry.ts new file mode 100644 index 0000000..98a98c3 --- /dev/null +++ b/src/types/Inquiry.ts @@ -0,0 +1,97 @@ +export type InquiryListRequest = { + compCd: string //company code + langCd: string //language code + storeId: string //store id + siteTpCd: string //site type code (QC: QCast, QR: QRead) + schTitle: string | null //search title + schRegId: string | null //search regId + schFromDt: string | null //search start date + schToDt: string | null //search end date + schAnswerYn: string | null //search answer yn + loginId: string //login id +} + +export type InquiryList = { + totCnt: number //total count + rowNumber: number //row number + compCd: string //company code + qnaNo: number //qna number + qstTitle: string //title + regDt: string //registration date + regId: string //registration Userid + regNm: string //registration User name + answerYn: string //answer yn - Y / N + attachYn: string | null //attach yn - Y / N + qnaClsLrgCd: string //qna CLS large Code + qnaClsMidCd: string //qna CLS Mid Code + qnaClsSmlCd: string | null //qna CLS Small Code + regUserNm: string //registration User name +} + +export type InquiryDetailRequest = { + compCd: string //company code + langCd: string //language code + qnaNo: number //qna number + loginId: string //login id +} + +export type Inquiry = { + compCd: string //company code + qnaNo: number //qna number + qstTitle: string //title + qstContents: string //content + regDt: string //registration date + regId: string //registration Userid + regNm: string //registration User name + regEmail: string //registration User email + answerYn: string //answer yn - Y / N + ansContents: string | null //answer content + ansRegDt: string | null //answer registration date + ansRegNm: string | null //answer registration User name + storeId: string | null //store id + storeNm: string | null //store name + regUserNm: string //registration User name + regUserTelNo: string | null //registration User tel number + qnaClsLrgCd: string //qna CLS large Code + qnaClsMidCd: string //qna CLS Mid Code + qnaClsSmlCd: string | null //qna CLS Small Code + listFile: listFile[] | null //Question list file + ansListFile: listFile[] | null //Answer list file +} + +export type listFile = { + fileNo: number //file number + encodeFileNo: string //encode file number + srcFileNm: string //source file name + fileCours: string //file course + fileSize: number //file size(Byte) + regDt: string //registration date +} + +export type InquiryRequest = { + compCd: string //company code + siteTpCd: string //site type code(QC: QCast, QR: QRead) + qnaClsLrgCd: string //qna CLS large Code + qnaClsMidCd: string //qna CLS Mid Code + qnaClsSmlCd: string | null //qna CLS Small Code + title: string //title + contents: string //contents + regId: string //registration Userid + storeId: string //store id + regUserNm: string //registration User name + regUserTelNo: string | null //registration User tel number + qstMail: string //mail +} + +export type InquirySaveResponse = { + cnt: number | null //count + qnaNo: number //qna number + mailYn: string //mail yn - Y / N +} + +export type CommonCode = { + headCd: string + code: string + name: string + refChar1: string +}