diff --git a/src/app/api/auth/route.ts b/src/app/api/auth/route.ts index d5149a9..8205132 100644 --- a/src/app/api/auth/route.ts +++ b/src/app/api/auth/route.ts @@ -15,7 +15,9 @@ export async function POST(request: Request) { }) console.log('๐Ÿš€ ~ result ~ result:', result.data) - if (result.data.result.code === 200) { + let finalResult = {} + + if (result.data.result.resultCode === 'S') { tracking({ url: `/api/auth/login`, data: JSON.stringify({ @@ -72,52 +74,64 @@ export async function POST(request: Request) { console.log('end session edit!') await session.save() - } - const resultForSession = { - LANG_CD: result.data.data.langCd, - CURR_PAGE: result.data.data.currPage, - ROW_COUNT: result.data.data.rowCount, - START_ROW: result.data.data.startRow, - END_ROW: result.data.data.endRow, - COMP_CD: result.data.data.compCd, - AGENCY_STORE_ID: result.data.data.agencyStoreId, - STORE_ID: result.data.data.storeId, - STORE_NM: result.data.data.storeNm, - USER_ID: result.data.data.userId, - CATEGORY: result.data.data.category, - USER_NM: result.data.data.userNm, - USER_NM_KANA: result.data.data.userNmKana, - TEL_NO: result.data.data.telNo, - FAX: result.data.data.fax, - EMAIL: result.data.data.email, - LAST_EDIT_USER: result.data.data.lastEditUser, - STORE_GUBUN: result.data.data.storeGubun, - PW_CURR: result.data.data.pwCurr, - PWD_INIT_YN: result.data.data.pwdInitYn, - APPR_STAT_CD: result.data.data.apprStatCd, - LOGIN_FAIL_CNT: result.data.data.loginFailCnt, - LOGIN_FAIL_MIN_YN: result.data.data.loginFailMinYn, - PRICE_VIEW_STAT_CD: result.data.data.priceViewStatCd, - GROUP_ID: result.data.data.groupId, - STORE_LVL: result.data.data.storeLvl, - CUST_CD: result.data.data.custCd, - BUILDER_NO: result.data.data.builderNo, - IS_LOGGED_IN: true, - ROLE: '', - } + const resultForSession = { + LANG_CD: result.data.data.langCd, + CURR_PAGE: result.data.data.currPage, + ROW_COUNT: result.data.data.rowCount, + START_ROW: result.data.data.startRow, + END_ROW: result.data.data.endRow, + COMP_CD: result.data.data.compCd, + AGENCY_STORE_ID: result.data.data.agencyStoreId, + STORE_ID: result.data.data.storeId, + STORE_NM: result.data.data.storeNm, + USER_ID: result.data.data.userId, + CATEGORY: result.data.data.category, + USER_NM: result.data.data.userNm, + USER_NM_KANA: result.data.data.userNmKana, + TEL_NO: result.data.data.telNo, + FAX: result.data.data.fax, + EMAIL: result.data.data.email, + LAST_EDIT_USER: result.data.data.lastEditUser, + STORE_GUBUN: result.data.data.storeGubun, + PW_CURR: result.data.data.pwCurr, + PWD_INIT_YN: result.data.data.pwdInitYn, + APPR_STAT_CD: result.data.data.apprStatCd, + LOGIN_FAIL_CNT: result.data.data.loginFailCnt, + LOGIN_FAIL_MIN_YN: result.data.data.loginFailMinYn, + PRICE_VIEW_STAT_CD: result.data.data.priceViewStatCd, + GROUP_ID: result.data.data.groupId, + STORE_LVL: result.data.data.storeLvl, + CUST_CD: result.data.data.custCd, + BUILDER_NO: result.data.data.builderNo, + IS_LOGGED_IN: true, + ROLE: '', + } - if (result.data.data.userId === 'T01') { - resultForSession.ROLE = 'T01' - } else if (result.data.data.groupId === '60000') { - resultForSession.ROLE = 'Admin' - } else if (result.data.data.groupId === '70000' && result.data.data.builderNo === null) { - resultForSession.ROLE = 'Admin_Sub' - } else if (result.data.data.groupId === '70000' && result.data.data.builderNo !== null) { - resultForSession.ROLE = 'Builder' + if (result.data.data.userId === 'T01') { + resultForSession.ROLE = 'T01' + } else if (result.data.data.groupId === '60000') { + resultForSession.ROLE = 'Admin' + } else if (result.data.data.groupId === '70000' && result.data.data.builderNo === null) { + resultForSession.ROLE = 'Admin_Sub' + } else if (result.data.data.groupId === '70000' && result.data.data.builderNo !== null) { + resultForSession.ROLE = 'Builder' + } else { + resultForSession.ROLE = 'User' + } + + finalResult = { + code: 200, + message: 'Login is Succecss!!', + result: resultForSession, + } } else { - resultForSession.ROLE = 'User' + finalResult = { + code: 400, + message: 'Login is Failed!!', + result: {}, + } } - return NextResponse.json({ code: 200, message: 'Login is Succecss!!', result: resultForSession }) + return NextResponse.json(finalResult) } diff --git a/src/app/api/suitable/list/route.ts b/src/app/api/suitable/list/route.ts index 32eac33..005d556 100644 --- a/src/app/api/suitable/list/route.ts +++ b/src/app/api/suitable/list/route.ts @@ -1,82 +1,66 @@ import { NextRequest, NextResponse } from 'next/server' import { prisma } from '@/libs/prisma' -import { SUITABLE_HEAD_CODE, type SuitableMain } from '@/types/Suitable' +import { type Suitable } from '@/types/Suitable' export async function GET(request: NextRequest) { try { const searchParams = request.nextUrl.searchParams + + const pageNumber = parseInt(searchParams.get('pageNumber') || '0') + const itemPerPage = parseInt(searchParams.get('itemPerPage') || '0') + if (pageNumber === 0 || itemPerPage === 0) { + return NextResponse.json({ error: 'ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ์™€ ํŽ˜์ด์ง€๋‹น ์•„์ดํ…œ ์ˆ˜๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค' }, { status: 400 }) + } + const category = searchParams.get('category') const keyword = searchParams.get('keyword') - let MainWhereCondition: any = {} - const whereCondition: string[] = [] - const params: string[] = [] + let query = ` + SELECT + msm.id + , msm.product_name + , details.detail_cnt + , details.detail + FROM ms_suitable_main msm + LEFT JOIN ( + SELECT + msd.main_id + , COUNT(msd.id) AS detail_cnt + , ( + SELECT + msd_json.id + , msd_json.trestle_mfpc_cd + , msd_json.trestle_manufacturer_product_name + , msd_json.memo + FROM ms_suitable_detail msd_json + WHERE msd.main_id = msd_json.main_id + FOR JSON PATH + ) AS detail + FROM ms_suitable_detail msd + GROUP BY msd.main_id + ) AS details + ON msm.id = details.main_id + WHERE 1=1 + --roofMtCd AND msm.roof_mt_cd = ':roofMtCd' + --productName AND msm.product_name LIKE '%:productName%' + ORDER BY msm.product_name + OFFSET (@P1 - 1) * @P2 ROWS + FETCH NEXT @P2 ROWS ONLY; + ` + + // ๊ฒ€์ƒ‰ ์กฐ๊ฑด ์„ค์ • if (category) { - whereCondition.push(`${SUITABLE_HEAD_CODE.ROOF_MT_CD} = @P1`) - params.push(category) - MainWhereCondition[SUITABLE_HEAD_CODE.ROOF_MT_CD] = category + query = query.replace('--roofMtCd ', '') + query = query.replace(':roofMtCd', category) } if (keyword) { - whereCondition.push('PRODUCT_NAME LIKE @P2') - params.push(`%${keyword}%`) - MainWhereCondition['PRODUCT_NAME'] = { - contains: keyword, - } + query = query.replace('--productName ', '') + query = query.replace(':productName', keyword) } - const startTime = performance.now() - console.log(`์ฟผ๋ฆฌ (main table) ์‹œ์ž‘ ์‹œ๊ฐ„: ${startTime}ms`) - // @ts-ignore - const suitable = await prisma.MS_SUITABLE_MAIN.findMany({ - select: { - ID: true, - PRODUCT_NAME: true, - ROOF_MT_CD: true, - }, - where: MainWhereCondition, - orderBy: { - PRODUCT_NAME: 'asc', - }, - }) + const suitable: Suitable[] = await prisma.$queryRawUnsafe(query, pageNumber, itemPerPage) - const endTime = performance.now() - console.log(`์ฟผ๋ฆฌ (main table) ์ข…๋ฃŒ ์‹œ๊ฐ„: ${endTime - startTime}ms`) - - const mainIds: number[] = suitable.map((item: SuitableMain) => item.id) - - - const startTime2 = performance.now() - console.log(`์ฟผ๋ฆฌ (detail table) ์‹œ์ž‘ ์‹œ๊ฐ„: ${startTime2}ms`) - let detailQuery = ` - SELECT - msd.main_id - , ( - SELECT - msd_json.id - , msd_json.trestle_mfpc_cd - , msd_json.trestle_manufacturer_product_name - , msd_json.memo - FROM ms_suitable_detail msd_json - WHERE msd.main_id = msd_json.main_id - FOR JSON PATH - ) AS detail - FROM ms_suitable_detail msd - -- WHERE 1=1 - GROUP BY msd.main_id - ` - if (whereCondition.length > 0) { - detailQuery = detailQuery.replace('-- WHERE 1=1', `WHERE msd.main_id IN @P1`) - } - // @ts-ignore - const detail = await prisma.$queryRawUnsafe(detailQuery, ...mainIds) - - const endTime2 = performance.now() - console.log(`์ฟผ๋ฆฌ (detail table) ์ข…๋ฃŒ ์‹œ๊ฐ„: ${endTime2 - startTime2}ms`) - - const endTime3 = performance.now() - console.log(`์ฟผ๋ฆฌ ์ด ์‹คํ–‰ ์‹œ๊ฐ„: ${endTime3 - startTime}ms`) - - return NextResponse.json({ suitable, detail }) + return NextResponse.json(suitable) } catch (error) { console.error('โŒ ๋ฐ์ดํ„ฐ ์กฐํšŒ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค:', error) return NextResponse.json({ error: '๋ฐ์ดํ„ฐ ์กฐํšŒ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค' }, { status: 500 }) diff --git a/src/app/api/suitable/list/test/route.ts b/src/app/api/suitable/list/test/route.ts deleted file mode 100644 index e4688bd..0000000 --- a/src/app/api/suitable/list/test/route.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server' -import { prisma } from '@/libs/prisma' -import { SUITABLE_HEAD_CODE, type Suitable } from '@/types/Suitable' - -export async function GET(request: NextRequest) { - try { - const searchParams = request.nextUrl.searchParams - const category = searchParams.get('category') - const keyword = searchParams.get('keyword') - - const whereCondition: string[] = [] - const params: string[] = [] - if (category) { - whereCondition.push(`${SUITABLE_HEAD_CODE.ROOF_MT_CD} = @P1`) - params.push(category) - } - if (keyword) { - whereCondition.push('PRODUCT_NAME LIKE @P2') - params.push(`%${keyword}%`) - } - - const startTime = performance.now() - console.log(`์ฟผ๋ฆฌ ์‹œ์ž‘ ์‹œ๊ฐ„: ${startTime}ms`) - - let query = ` - SELECT - msm.id - , msm.product_name - , msm.manu_ft_cd - , msm.roof_mt_cd - , msm.roof_sh_cd - , details.detail - FROM ms_suitable_main msm - LEFT JOIN ( - SELECT - msd.main_id - , ( - SELECT - msd_json.id - , msd_json.trestle_mfpc_cd - , msd_json.trestle_manufacturer_product_name - , msd_json.memo - FROM ms_suitable_detail msd_json - WHERE msd.main_id = msd_json.main_id - FOR JSON PATH - ) AS detail - FROM ms_suitable_detail msd - GROUP BY msd.main_id - ) AS details - ON msm.id = details.main_id - -- AND details.main_id IN (#mainIds) - -- WHERE 1=1 - ORDER BY msm.product_name` - - // ๊ฒ€์ƒ‰ ์กฐ๊ฑด ์ถ”๊ฐ€ - if (whereCondition.length > 0) { - query = query.replace('-- WHERE 1=1', `WHERE ${whereCondition.join(' AND ')}`) - } - - // @ts-ignore - const suitable: Suitable[] = await prisma.$queryRawUnsafe(query, ...params) - - const endTime = performance.now() - console.log(`์ฟผ๋ฆฌ ์‹คํ–‰ ์‹œ๊ฐ„: ${endTime - startTime}ms`) - - return NextResponse.json(suitable) - } catch (error) { - console.error('โŒ ๋ฐ์ดํ„ฐ ์กฐํšŒ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค:', error) - return NextResponse.json({ error: '๋ฐ์ดํ„ฐ ์กฐํšŒ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค' }, { status: 500 }) - } -} diff --git a/src/app/api/survey-sales/[id]/route.ts b/src/app/api/survey-sales/[id]/route.ts index db5c9df..07ecd6a 100644 --- a/src/app/api/survey-sales/[id]/route.ts +++ b/src/app/api/survey-sales/[id]/route.ts @@ -21,7 +21,6 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ const getNewSrlNo = async (srlNo: string, storeId: string) => { let newSrlNo = srlNo - console.log('srlNo:: ', srlNo) if (srlNo.startsWith('ไธ€ๆ™‚ไฟๅญ˜')) { //@ts-ignore const lastSurvey = await prisma.SD_SURVEY_SALES_BASIC_INFO.findFirst({ @@ -128,7 +127,6 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise< SRL_NO: newSrlNo, }, }) - console.log(survey) return NextResponse.json({ message: 'Survey confirmed successfully' }) } } catch (error) { diff --git a/src/app/api/survey-sales/route.ts b/src/app/api/survey-sales/route.ts index 7d51802..a1d818a 100644 --- a/src/app/api/survey-sales/route.ts +++ b/src/app/api/survey-sales/route.ts @@ -1,7 +1,6 @@ import { NextResponse } from 'next/server' import { prisma } from '@/libs/prisma' import { convertToSnakeCase } from '@/utils/common-utils' -import { equal } from 'assert' /** * ๊ฒ€์ƒ‰ ํŒŒ๋ผ๋ฏธํ„ฐ */ @@ -32,6 +31,7 @@ const SEARCH_OPTIONS = [ 'POST_CODE', // ์šฐํŽธ๋ฒˆํ˜ธ 'ADDRESS', // ์ฃผ์†Œ 'ADDRESS_DETAIL', // ์ƒ์„ธ์ฃผ์†Œ + 'SRL_NO', // ๋“ฑ๋ก๋ฒˆํ˜ธ ] as const // ํŽ˜์ด์ง€๋‹น ํ•ญ๋ชฉ ์ˆ˜ @@ -50,13 +50,6 @@ const createKeywordSearchCondition = (keyword: string, searchOption: string): Wh // ๋ชจ๋“  ํ•„๋“œ ๊ฒ€์ƒ‰ ์‹œ OR ์กฐ๊ฑด ์‚ฌ์šฉ where.OR = [] - // ID๊ฐ€ ์ˆซ์ž์ธ ๊ฒฝ์šฐ ID ๊ฒ€์ƒ‰ ์กฐ๊ฑด ์ถ”๊ฐ€ - if (keyword.match(/^\d+$/) || !isNaN(Number(keyword))) { - where.OR.push({ - ID: { equals: Number(keyword) }, - }) - } - where.OR.push( ...SEARCH_OPTIONS.map((field) => ({ [field]: { contains: keyword }, @@ -65,15 +58,6 @@ const createKeywordSearchCondition = (keyword: string, searchOption: string): Wh } else if (SEARCH_OPTIONS.includes(searchOption.toUpperCase() as any)) { // ํŠน์ • ํ•„๋“œ ๊ฒ€์ƒ‰ where[searchOption.toUpperCase()] = { contains: keyword } - } else if (searchOption === 'id') { - // ID ๊ฒ€์ƒ‰ (์ˆซ์ž ๋ณ€ํ™˜ ํ•„์š”) - const number = Number(keyword) - if (!isNaN(number)) { - where.ID = { equals: number } - } else { - // ์œ ํšจํ•˜์ง€ ์•Š์€ ID ๊ฒ€์ƒ‰ ์‹œ ๋นˆ ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜ - where.ID = { equals: null } - } } return where } diff --git a/src/app/suitable-test/layout.tsx b/src/app/suitable-test/layout.tsx deleted file mode 100644 index e5e7c3f..0000000 --- a/src/app/suitable-test/layout.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import type { ReactNode } from 'react' - -interface SuitableLayoutProps { - children: ReactNode -} - -export default function layout({ children }: SuitableLayoutProps) { - return ( - <> -
-
-
-
-
ใ“ใฎ้ฉๅˆ่กจใฏๅ‚่€ƒ่ณ‡ๆ–™ใจใ—ใฆไฝฟ็”จใ—ใฆใใ ใ•ใ„.
-
่ฉณ็ดฐใ‚„ใŠๅ•ใ„ๅˆใ‚ใ›ใฏ1๏ผš1ใŠๅ•ใ„ๅˆใ‚ใ›ใ‚’ใ”ๅˆฉ็”จใใ ใ•ใ„.
-
ๅฑ‹ๆ นๆใฎ้ธๆŠžorๅฑ‹ๆ นๆๅใ‚’็›ดๆŽฅๅ…ฅๅŠ›ใ—ใฆใใ ใ•ใ„.
-
-
- {children} -
-
- - ) -} diff --git a/src/app/suitable-test/page.tsx b/src/app/suitable-test/page.tsx deleted file mode 100644 index a5299fe..0000000 --- a/src/app/suitable-test/page.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import SuitableRaw from '@/components/suitable/SuitableRaw' - -export default function page() { - return ( - <> - - - ) -} diff --git a/src/components/Login.tsx b/src/components/Login.tsx index 043fee6..4a77ad6 100644 --- a/src/components/Login.tsx +++ b/src/components/Login.tsx @@ -39,6 +39,26 @@ export default function Login() { return emailRegex.test(email) } + const handleLogin = () => { + if (validateLogin()) { + setIsLogin(true) + } + } + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + handleLogin() + } + } + + const validateLogin = () => { + if (account.loginId === '' || account.pwd === '') { + alert('Please enter your ID and password.') + return false + } + return true + } + interface LoginData { code: number message: string | null @@ -85,6 +105,9 @@ export default function Login() { ...loginData?.result, }) router.push('/') + } else if (loginData?.code === 400) { + alert(loginData?.message) + setAccount({ loginId: '', pwd: '' }) } }, [loginData]) @@ -106,6 +129,7 @@ export default function Login() { className="login-frame" placeholder="Input Frame ID" value={account.loginId} + onKeyDown={(e) => handleKeyDown(e)} onChange={(e) => setAccount({ loginId: e.target.value })} /> diff --git a/src/components/suitable/Suitable.tsx b/src/components/suitable/Suitable.tsx index 36a397f..f5f22b2 100644 --- a/src/components/suitable/Suitable.tsx +++ b/src/components/suitable/Suitable.tsx @@ -11,7 +11,7 @@ import { SUITABLE_HEAD_CODE } from '@/types/Suitable' export default function Suitable() { const [reference, setReference] = useState(true) - const { getSuitableCommCode, refetchBySearch } = useSuitable() + const { getSuitableCommCode } = useSuitable() const { suitableCommCode, selectedCategory, setSelectedCategory, searchValue, setSearchValue, setIsSearch, clearSelectedItems } = useSuitableStore() const handleInputSearch = async () => { @@ -20,19 +20,13 @@ export default function Suitable() { return } setIsSearch(true) - refetchBySearch() } const handleInputClear = () => { setSearchValue('') setIsSearch(false) - refetchBySearch() } - useEffect(() => { - refetchBySearch() - }, [selectedCategory]) - useEffect(() => { getSuitableCommCode() return () => { @@ -62,6 +56,11 @@ export default function Suitable() { placeholder="ๅฑ‹ๆ นๆ ่ฃฝๅ“ๅใ‚’ๅ…ฅๅŠ›ใ—ใฆใใ ใ•ใ„." value={searchValue} onChange={(e) => setSearchValue(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') { + handleInputSearch() + } + }} /> {searchValue && - - - - - ) - }, - [isItemSelected, openItems, handleItemClick, toggleItemOpen, suitableCheck, toCodeName, toSuitableDetail], - ) - - // ๋ฉ”๋ชจ์ด์ œ์ด์…˜๋œ ์•„์ดํ…œ ๋ฆฌ์ŠคํŠธ - const renderedItems = useMemo(() => { - return visibleItems.map(renderItem) - }, [visibleItems, renderItem]) - - if (isSearchLoading) { - return
Loading...
- } - - if (!suitableSearchResults?.length) { - return - } - - return ( - <> - {renderedItems} -
- {isLoadingMore &&
๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...
} -
- - - ) -} diff --git a/src/components/suitable/SuitableNoData.tsx b/src/components/suitable/SuitableNoData.tsx index 1245fbf..f427b15 100644 --- a/src/components/suitable/SuitableNoData.tsx +++ b/src/components/suitable/SuitableNoData.tsx @@ -1,11 +1,16 @@ +'use client' + +import { useRouter } from 'next/navigation' + export default function SuitableNoData() { + const router = useRouter() return ( <>
ๆคœ็ดข็ตๆžœใฏใ‚ใ‚Šใพใ›ใ‚“ใ€‚ ๅฑ‹ๆ นๆ้ฉๅˆๆ€ง่กจใซใชใ„่ฃฝๅ“ใฎๆƒ…ๅ ฑใ‚’ๅ…ฅๅŠ›ใ—ใฆใใ ใ•ใ„ใ€‚ ไปŠๅพŒ่ฟ”ไฟกใ„ใŸใ—ใพใ™ใ€‚ - diff --git a/src/components/suitable/SuitableRaw.tsx b/src/components/suitable/SuitableRaw.tsx deleted file mode 100644 index d48dfea..0000000 --- a/src/components/suitable/SuitableRaw.tsx +++ /dev/null @@ -1,118 +0,0 @@ -'use client' - -import Image from 'next/image' -import { useEffect, useState } from 'react' -import SuitableListRaw from './SuitableListRaw' -import { useSuitableRaw } from '@/hooks/useSuitableRaw' -import { useSuitableStore } from '@/store/useSuitableStore' -import type { CommCode } from '@/types/CommCode' -import { SUITABLE_HEAD_CODE } from '@/types/Suitable' - -export default function SuitableRaw() { - const [reference, setReference] = useState(true) - - const { getSuitableCommCode, refetchBySearch } = useSuitableRaw() - const { suitableCommCode, selectedCategory, setSelectedCategory, searchValue, setSearchValue, setIsSearch, clearSelectedItems } = useSuitableStore() - - const handleInputSearch = async () => { - if (!searchValue.trim()) { - alert('ๅฑ‹ๆ นๆใฎ่ฃฝๅ“ๅใ‚’ๅ…ฅๅŠ›ใ—ใฆใใ ใ•ใ„ใ€‚') - return - } - setIsSearch(true) - refetchBySearch() - } - - const handleInputClear = () => { - setSearchValue('') - setIsSearch(false) - refetchBySearch() - } - - useEffect(() => { - refetchBySearch() - }, [selectedCategory]) - - useEffect(() => { - getSuitableCommCode() - return () => { - setSelectedCategory('') - setSearchValue('') - clearSelectedItems() - } - }, []) - - return ( -
- ํ…Œ์ŠคํŠธ1 ํŽ˜์ด์ง€ -
- -
-
-
- setSearchValue(e.target.value)} - /> - {searchValue &&
-
-
-
-
-
ๅ‡กไพ‹
-
- -
-
-
    -
  • -
    -
    - -
    - ่จญ็ฝฎๅฏ่ƒฝ -
    -
  • -
  • -
    -
    - -
    - ่จญ็ฝฎไธๅฏ -
    -
  • -
  • -
    -
    - -
    - ใŠๅ•ใ„ๅˆใ‚ใ› -
    -
  • -
  • -
    -
    - -
    - ๅ‚™่€ƒ -
    -
  • -
-
- -
-
- ) -} diff --git a/src/components/survey-sale/list/ListTable.tsx b/src/components/survey-sale/list/ListTable.tsx index 0bfeafe..8ff5eaf 100644 --- a/src/components/survey-sale/list/ListTable.tsx +++ b/src/components/survey-sale/list/ListTable.tsx @@ -27,7 +27,7 @@ export default function ListTable() { }, [pathname]) useEffect(() => { - if (!session.isLoggedIn || !('data' in surveyList)) return + if (!session.isLoggedIn || isLoadingSurveyList) return if ('count' in surveyList && surveyList.count > 0) { if (offset > 0) { setHeldSurveyList((prev) => [...prev, ...surveyList.data]) @@ -45,14 +45,12 @@ export default function ListTable() { router.push(`/survey-sale/${id}`) } - // TODO: ๋กœ๋”ฉ ์ฒ˜๋ฆฌ ํ•„์š” - return ( <> -
- {heldSurveyList.length > 0 ? ( -
    +
    + {heldSurveyList.length > 0 ? ( +
      {heldSurveyList.map((survey) => (
    • handleDetailClick(survey.id)}>
      @@ -67,18 +65,18 @@ export default function ListTable() {
      {new Date(survey.uptDt).toLocaleString()}
    - - ))} -
- ) : ( -
- ไฝœๆˆใ•ใ‚ŒใŸ็‰ฉไปถใฏใ‚ใ‚Šใพใ›ใ‚“ใ€‚ -
- )} -
- setOffset(offset + 10)} /> + + ))} + + ) : ( +
+ ไฝœๆˆใ•ใ‚ŒใŸ็‰ฉไปถใฏใ‚ใ‚Šใพใ›ใ‚“ใ€‚
+ )} +
+ setOffset(offset + 10)} />
+
) } diff --git a/src/components/survey-sale/list/SearchForm.tsx b/src/components/survey-sale/list/SearchForm.tsx index e0de061..82147ba 100644 --- a/src/components/survey-sale/list/SearchForm.tsx +++ b/src/components/survey-sale/list/SearchForm.tsx @@ -6,7 +6,7 @@ import { useState } from 'react' export default function SearchForm({ memberRole, userNm }: { memberRole: string; userNm: string }) { const router = useRouter() - const { setSearchOption, setSort, setIsMySurvey, setKeyword, isMySurvey, keyword, searchOption, sort } = useSurveyFilterStore() + const { setSearchOption, setSort, setIsMySurvey, setKeyword, reset, isMySurvey, keyword, searchOption, sort } = useSurveyFilterStore() const [searchKeyword, setSearchKeyword] = useState(keyword) const [option, setOption] = useState(searchOption) @@ -15,6 +15,7 @@ export default function SearchForm({ memberRole, userNm }: { memberRole: string; alert('2ๆ–‡ๅญ—ไปฅไธŠๅ…ฅๅŠ›ใ—ใฆใใ ใ•ใ„') return } + reset() setKeyword(searchKeyword) setSearchOption(option) } diff --git a/src/hooks/useCommCode.ts b/src/hooks/useCommCode.ts index bb50240..6c78183 100644 --- a/src/hooks/useCommCode.ts +++ b/src/hooks/useCommCode.ts @@ -1,7 +1,8 @@ -import { axiosInstance } from '@/libs/axios' +import { useAxios } from './useAxios' import type { CommCode } from '@/types/CommCode' export function useCommCode() { + const { axiosInstance } = useAxios() const getCommCode = async (headCode: string): Promise => { try { const response = await axiosInstance(null).get('/api/comm-code', { params: { headCode: headCode } }) diff --git a/src/hooks/useSuitable.ts b/src/hooks/useSuitable.ts index 4bdd9b2..f651b4d 100644 --- a/src/hooks/useSuitable.ts +++ b/src/hooks/useSuitable.ts @@ -1,33 +1,43 @@ -import { useQuery } from '@tanstack/react-query' -import { axiosInstance, transformObjectKeys } from '@/libs/axios' +import { useInfiniteQuery } from '@tanstack/react-query' +import { transformObjectKeys } from '@/libs/axios' import { useSuitableStore } from '@/store/useSuitableStore' +import { useAxios } from './useAxios' import { useCommCode } from './useCommCode' -import { SUITABLE_HEAD_CODE, type SuitableDetailGroup, type SuitableMain, type Suitable, type SuitableDetail } from '@/types/Suitable' +import { SUITABLE_HEAD_CODE, type Suitable, type SuitableDetail } from '@/types/Suitable' export function useSuitable() { + const { axiosInstance } = useAxios() const { getCommCode } = useCommCode() - const { selectedCategory, searchValue, suitableCommCode, setSuitableCommCode, isSearch } = useSuitableStore() + const { itemPerPage, selectedCategory, searchValue, suitableCommCode, setSuitableCommCode, isSearch } = useSuitableStore() - const getSuitables = async (): Promise => { + const getSuitables = async ({ + pageNumber, + ids, + category, + keyword, + }: { + pageNumber?: number + ids?: string + category?: string + keyword?: string + }): Promise => { try { - const response = await axiosInstance(null).get('/api/suitable/list') + const params: Record = { + pageNumber: pageNumber || 1, + itemPerPage: itemPerPage, + } + if (ids) params.ids = ids + if (category) params.category = category + if (keyword) params.keyword = keyword + + const response = await axiosInstance(null).get('/api/suitable/list', { params }) return response.data } catch (error) { console.error('์ง€๋ถ•์žฌ ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹คํŒจ:', error) - return { suitable: [], detail: [] } + return [] } } - // const updateSearchResults = async (selectedCategory: string | undefined, searchValue: string | undefined): Promise => { - // try { - // const response = await axiosInstance(null).get('/api/suitable/list', { params: { selectedCategory, searchValue } }) - // return response.data - // } catch (error) { - // console.error('์ง€๋ถ•์žฌ ๋ฐ์ดํ„ฐ ๊ฒ€์ƒ‰ ์‹คํŒจ:', error) - // return [] - // } - // } - const getSuitableCommCode = () => { const headCodes = Object.values(SUITABLE_HEAD_CODE) as SUITABLE_HEAD_CODE[] for (const code of headCodes) { @@ -42,12 +52,8 @@ export function useSuitable() { return commCode?.find((item) => item.code === code)?.codeJp || '' } - const toSuitableDetail = (mainId: number): SuitableDetail[] => { + const toSuitableDetail = (suitableDetailString: string): SuitableDetail[] => { try { - const suitableDetailString = suitableList?.detail.find((item) => item.mainId === mainId)?.detail - if (!suitableDetailString) { - return [] - } const suitableDetailArray = transformObjectKeys(JSON.parse(suitableDetailString)) as SuitableDetail[] if (!Array.isArray(suitableDetailArray)) { throw new Error('suitableDetailArray is not an array') @@ -59,39 +65,39 @@ export function useSuitable() { } } - const { data: suitableList, isLoading: isInitialLoading } = useQuery({ - queryKey: ['suitables', 'list'], - queryFn: async () => await getSuitables(), - staleTime: 1000 * 60 * 10, // 10๋ถ„ - gcTime: 1000 * 60 * 10, // 10๋ถ„ - }) + const toSuitableDetailIds = (suitableDetailString: string): Set => { + try { + return new Set(JSON.parse(suitableDetailString).map(({ id }: { id: number }) => id)) + } catch (error) { + console.error('์ง€๋ถ•์žฌ ๋ฐ์ดํ„ฐ ํŒŒ์‹ฑ ์‹คํŒจ:', error) + return new Set() + } + } const { - data: suitableSearchResults, - refetch: refetchBySearch, - isLoading: isSearchLoading, - } = useQuery({ - queryKey: ['suitables', 'search', selectedCategory, isSearch], - queryFn: async () => { - if (!isSearch && !selectedCategory) { - // ๊ฒ€์ƒ‰ ์ƒํƒœ๊ฐ€ ์•„๋‹ˆ๋ฉด ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ ๋ฐ˜ํ™˜ ์ž„์‹œ์ฒ˜๋ฆฌ - return isInitialLoading ? await getSuitables() : suitableList ?? { suitable: [], detail: [] } - } else { - const filteredSuitable = suitableList?.suitable.filter((item: SuitableMain) => { - const categoryMatch = !selectedCategory || item.roofMtCd === selectedCategory - const searchMatch = !searchValue || item.productName.includes(searchValue) - return categoryMatch && searchMatch - }) ?? [] - const mainIds = filteredSuitable.map((item: SuitableMain) => item.id) - const filteredDetail = suitableList?.detail.filter((item: SuitableDetailGroup) => { - return mainIds.includes(item.mainId) - }) ?? [] - return { suitable: filteredSuitable, detail: filteredDetail } - } + data: suitables, + fetchNextPage, + hasNextPage, + isFetchingNextPage, + isLoading, + isError, + error, + } = useInfiniteQuery({ + queryKey: ['suitables', 'list', selectedCategory, isSearch], + queryFn: async (context) => { + const pageParam = context.pageParam as number + return await getSuitables({ + pageNumber: pageParam, + ...(selectedCategory && { category: selectedCategory }), + ...(isSearch && { keyword: searchValue }), + }) }, + getNextPageParam: (lastPage: Suitable[], allPages: Suitable[][]) => { + return lastPage.length === itemPerPage ? allPages.length + 1 : undefined + }, + initialPageParam: 1, staleTime: 1000 * 60 * 10, gcTime: 1000 * 60 * 10, - enabled: true, }) return { @@ -99,9 +105,11 @@ export function useSuitable() { getSuitableCommCode, toCodeName, toSuitableDetail, - suitableList, - suitableSearchResults, - refetchBySearch, - isSearchLoading, + toSuitableDetailIds, + suitables, + fetchNextPage, + hasNextPage, + isFetchingNextPage, + isLoading, } } diff --git a/src/hooks/useSuitableRaw.ts b/src/hooks/useSuitableRaw.ts deleted file mode 100644 index a962244..0000000 --- a/src/hooks/useSuitableRaw.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { useQuery } from '@tanstack/react-query' -import { axiosInstance, transformObjectKeys } from '@/libs/axios' -import { useSuitableStore } from '@/store/useSuitableStore' -import { useCommCode } from './useCommCode' -import { SUITABLE_HEAD_CODE, type SuitableDetail } from '@/types/Suitable' - -export type Suitable = { - id: number - productName: string - manuFtCd: string - roofMtCd: string - roofShCd: string - detail: string -} - -export function useSuitableRaw() { - const { getCommCode } = useCommCode() - const { selectedCategory, searchValue, suitableCommCode, setSuitableCommCode, isSearch } = useSuitableStore() - - const getSuitables = async (): Promise => { - try { - const response = await axiosInstance(null).get('/api/suitable/list/test') - return response.data - } catch (error) { - console.error('์ง€๋ถ•์žฌ ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹คํŒจ:', error) - return [] - } - } - - // const updateSearchResults = async (selectedCategory: string | undefined, searchValue: string | undefined): Promise => { - // try { - // const response = await axiosInstance(null).get('/api/suitable/list', { params: { selectedCategory, searchValue } }) - // return response.data - // } catch (error) { - // console.error('์ง€๋ถ•์žฌ ๋ฐ์ดํ„ฐ ๊ฒ€์ƒ‰ ์‹คํŒจ:', error) - // return [] - // } - // } - - const getSuitableCommCode = () => { - const headCodes = Object.values(SUITABLE_HEAD_CODE) as SUITABLE_HEAD_CODE[] - for (const code of headCodes) { - getCommCode(code).then((res) => { - setSuitableCommCode(code, res) - }) - } - } - - const toCodeName = (headCode: string, code: string): string => { - const commCode = suitableCommCode.get(headCode) - return commCode?.find((item) => item.code === code)?.codeJp || '' - } - - const toSuitableDetail = (suitableDetailString: string): SuitableDetail[] => { - try { - const suitableDetailArray = transformObjectKeys(JSON.parse(suitableDetailString)) as SuitableDetail[] - if (!Array.isArray(suitableDetailArray)) { - throw new Error('suitableDetailArray is not an array') - } - return suitableDetailArray - } catch (error) { - console.error('์ง€๋ถ•์žฌ ๋ฐ์ดํ„ฐ ํŒŒ์‹ฑ ์‹คํŒจ:', error) - return [] - } - } - - const { data: suitableList, isLoading: isInitialLoading } = useQuery({ - queryKey: ['suitables', 'list'], - queryFn: async () => await getSuitables(), - staleTime: 1000 * 60 * 10, // 10๋ถ„ - gcTime: 1000 * 60 * 10, // 10๋ถ„ - }) - - const { - data: suitableSearchResults, - refetch: refetchBySearch, - isLoading: isSearchLoading, - // } = useQuery({ - } = useQuery({ - queryKey: ['suitables', 'search', selectedCategory, isSearch], - queryFn: async () => { - if (!isSearch && !selectedCategory) { - return isInitialLoading ? await getSuitables() : suitableList ?? [] // ๊ฒ€์ƒ‰ ์ƒํƒœ๊ฐ€ ์•„๋‹ˆ๋ฉด ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ ๋ฐ˜ํ™˜ ์ž„์‹œ์ฒ˜๋ฆฌ - } else { - return ( - suitableList?.filter((item: Suitable) => { - const categoryMatch = !selectedCategory || item.roofMtCd === selectedCategory - const searchMatch = !searchValue || item.productName.includes(searchValue) - return categoryMatch && searchMatch - }) ?? [] - ) - } - }, - staleTime: 1000 * 60 * 10, - gcTime: 1000 * 60 * 10, - enabled: true, - }) - - return { - getSuitables, - getSuitableCommCode, - toCodeName, - toSuitableDetail, - suitableList, - suitableSearchResults, - refetchBySearch, - isSearchLoading, - } -} diff --git a/src/store/surveyFilterStore.ts b/src/store/surveyFilterStore.ts index 95514e8..a27f2fc 100644 --- a/src/store/surveyFilterStore.ts +++ b/src/store/surveyFilterStore.ts @@ -6,7 +6,7 @@ export const SEARCH_OPTIONS = [ label: 'ๅ…จไฝ“', }, { - id: 'id', + id: 'srl_no', label: '็™ป้Œฒ็•ชๅท', }, { @@ -41,7 +41,7 @@ export const SEARCH_OPTIONS_PARTNERS = [ label: 'ๅ…จไฝ“', }, { - id: 'id', + id: 'srl_no', label: '็™ป้Œฒ็•ชๅท', }, { diff --git a/src/store/useSuitableStore.ts b/src/store/useSuitableStore.ts index 5fa4cd0..74f4d0b 100644 --- a/src/store/useSuitableStore.ts +++ b/src/store/useSuitableStore.ts @@ -2,6 +2,9 @@ import { create } from 'zustand' import type { CommCode } from '@/types/CommCode' interface SuitableState { + /* ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ ๋กœ๋“œ ๊ฐœ์ˆ˜*/ + itemPerPage: number + /* ๊ณตํ†ต์ฝ”๋“œ */ suitableCommCode: Map /* ๊ณตํ†ต์ฝ”๋“œ ์„ค์ • */ @@ -23,21 +26,22 @@ interface SuitableState { setSearchValue: (value: string) => void /* ์„ ํƒ๋œ ์•„์ดํ…œ ๋ฆฌ์ŠคํŠธ */ - selectedItems: number[] + selectedItems: Map> /* ์„ ํƒ๋œ ์•„์ดํ…œ ์ถ”๊ฐ€ */ - addSelectedItem: (itemId: number) => void + addSelectedItem: (mainId: number, detailId?: number, detailIds?: Set) => void /* ์„ ํƒ๋œ ์•„์ดํ…œ ์ œ๊ฑฐ */ - removeSelectedItem: (itemId: number) => void + removeSelectedItem: (mainId: number, detailId?: number) => void /* ์„ ํƒ๋œ ์•„์ดํ…œ ๋ชจ๋‘ ์ œ๊ฑฐ */ clearSelectedItems: () => void } export const useSuitableStore = create((set) => ({ + itemPerPage: 100 as number, suitableCommCode: new Map() as Map, isSearch: false as boolean, selectedCategory: '' as string, searchValue: '' as string, - selectedItems: [] as number[], + selectedItems: new Map() as Map>, /* ๊ณตํ†ต์ฝ”๋“œ ์„ค์ • */ setSuitableCommCode: (headCode: string, commCode: CommCode[]) => @@ -55,17 +59,46 @@ export const useSuitableStore = create((set) => ({ setSearchValue: (value: string) => set({ searchValue: value }), /* ์„ ํƒ๋œ ์•„์ดํ…œ ์ถ”๊ฐ€ */ - addSelectedItem: (itemId: number) => - set((state) => ({ - selectedItems: state.selectedItems.some((i) => i === itemId) ? state.selectedItems : [...state.selectedItems, itemId], - })), + addSelectedItem: (mainId: number, detailId?: number, detailIds?: Set) => { + if (detailId) { + // ๋””ํ…Œ์ผ(ํ•˜์œ„) ์•„์ดํ…œ ์ถ”๊ฐ€ + set((state) => { + const detailSet = state.selectedItems.get(mainId) || new Set() + detailSet.add(detailId) + state.selectedItems.set(mainId, detailSet) + return { selectedItems: state.selectedItems } + }) + } else { + // ๋ฉ”์ธ(์ƒ์œ„) ์•„์ดํ…œ ์ถ”๊ฐ€ + set((state) => { + state.selectedItems.set(mainId, detailIds || new Set()) + return { selectedItems: state.selectedItems } + }) + } + }, /* ์„ ํƒ๋œ ์•„์ดํ…œ ์ œ๊ฑฐ */ - removeSelectedItem: (itemId: number) => - set((state) => ({ - selectedItems: state.selectedItems.filter((i) => i !== itemId), - })), + removeSelectedItem: (mainId: number, detailId?: number) => { + set((state) => { + const newSelectedItems = new Map(state.selectedItems) + + if (!detailId) { + // ๋ฉ”์ธ(์ƒ์œ„) ์•„์ดํ…œ ์ œ๊ฑฐ + newSelectedItems.delete(mainId) + return { selectedItems: newSelectedItems } + } + + // ๋””ํ…Œ์ผ(ํ•˜์œ„) ์•„์ดํ…œ ์ œ๊ฑฐ + const detailSet = state.selectedItems.get(mainId) || new Set() + detailSet.delete(detailId) + + // ๋””ํ…Œ์ผ(ํ•˜์œ„)ํ•˜์œ„ ์•„์ดํ…œ์ด ๋ชจ๋‘ ์ œ๊ฑฐ๋˜๋ฉด ๋ฉ”์ธ ์•„์ดํ…œ๋„ ์ œ๊ฑฐ + detailSet.size === 0 ? newSelectedItems.delete(mainId) : newSelectedItems.set(mainId, detailSet) + + return { selectedItems: newSelectedItems } + }) + }, /* ์„ ํƒ๋œ ์•„์ดํ…œ ๋ชจ๋‘ ์ œ๊ฑฐ */ - clearSelectedItems: () => set({ selectedItems: [] }), + clearSelectedItems: () => set({ selectedItems: new Map() as Map> }), })) diff --git a/src/types/Suitable.ts b/src/types/Suitable.ts index 2e3563b..270dd46 100644 --- a/src/types/Suitable.ts +++ b/src/types/Suitable.ts @@ -25,20 +25,12 @@ export type SuitableDetail = { memo: string } -// export type Suitable = { -// id: number -// productName: string -// manuFtCd: string -// roofMtCd: string -// roofShCd: string -// detail: string -// } - -export type SuitableDetailGroup = { - mainId: number +export type Suitable = { + id: number + productName: string + manuFtCd: string + roofMtCd: string + roofShCd: string + detailCnt: number detail: string } -export type Suitable = { - suitable: SuitableMain[] - detail: SuitableDetailGroup[] -} \ No newline at end of file