From d718012f6f520ccccde792f38bd54706f9925a19 Mon Sep 17 00:00:00 2001 From: Daseul Kim Date: Fri, 23 May 2025 13:31:42 +0900 Subject: [PATCH 1/7] =?UTF-8?q?feat:=20=EC=A7=80=EB=B6=95=EC=9E=AC?= =?UTF-8?q?=EC=A0=81=ED=95=A9=EC=84=B1=20=EC=83=81=EC=84=B8=ED=8C=9D?= =?UTF-8?q?=EC=97=85=20=EB=A7=88=ED=81=AC=EC=97=85=20=EC=A0=81=EC=9A=A9=20?= =?UTF-8?q?=EB=B0=8F=20=ED=8C=9D=EC=97=85=20on/off=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/popup/SuitableDetailPopup.tsx | 215 ++++++++++++++++++- src/components/suitable/SuitableButton.tsx | 6 +- 2 files changed, 212 insertions(+), 9 deletions(-) diff --git a/src/components/popup/SuitableDetailPopup.tsx b/src/components/popup/SuitableDetailPopup.tsx index 59b7838..2d4095d 100644 --- a/src/components/popup/SuitableDetailPopup.tsx +++ b/src/components/popup/SuitableDetailPopup.tsx @@ -1,4 +1,11 @@ +'use client' + +import Image from 'next/image' +import { usePopupController } from '@/store/popupController' + export default function SuitableDetailPopup() { + const popupController = usePopupController() + return (
@@ -7,22 +14,214 @@ export default function SuitableDetailPopup() {
- +
屋根材適合性詳細
- +
-
-
-
アースティ40
-
- +
+
+
+
アースティ40
+
+ +
+
+
+
+
屋根技研 支持瓦
+
㈱ダイトー
+
+
+
屋根材
+
+
+
+
金具タイプ
+
木ねじ打ち込み式
+
+
+
+
+
屋根技研 支持瓦
+
+
+ +
+
+ +
+
+
+
Dで設置可
+
+
備考
+
+ 桟木なしの場合は支持金具平ー1で設置可能。その場合水返しが高い為、レベルプレート使用。桟木ありの場合は支持金具平ー2で設置可能 +
+
+
+
+
+
屋根技研支持金具
+
+
+ +
+
+ +
+
+
+
設置不可
+
+
備考
+
入手困難
+
+
+
+
+
屋根技研YGアンカー
+
+
+ +
+
+
+
お問い合わせください
+
+
備考
+
入手困難
+
+
+
+
+
ダイドーハント支持瓦Ⅱ
+
+
+ +
+
+
+
Ⅳ (D) で設置可
+
+
備考
+
入手困難
+
+
+
-
+
+
+
アースティ40
+
+ +
+
+
+
+
屋根技研 支持瓦
+
㈱ダイトー
+
+
+
屋根材
+
+
+
+
金具タイプ
+
木ねじ打ち込み式
+
+
+
+
+
屋根技研 支持瓦
+
+
+ +
+
+ +
+
+
+
Dで設置可
+
+
備考
+
入手困難
+
+
+
+
+
屋根技研支持金具
+
+
+ +
+
+ +
+
+
+
設置不可
+
+
備考
+
入手困難
+
+
+
+
+
屋根技研YGアンカー
+
+
+ +
+
+
+
お問い合わせください
+
+
備考
+
入手困難
+
+
+
+
+
ダイドーハント支持瓦Ⅱ
+
+
+ +
+
+
+
Ⅳ (D) で設置可
+
+
備考
+
入手困難
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+ +
diff --git a/src/components/suitable/SuitableButton.tsx b/src/components/suitable/SuitableButton.tsx index f412c89..1aa6be5 100644 --- a/src/components/suitable/SuitableButton.tsx +++ b/src/components/suitable/SuitableButton.tsx @@ -1,6 +1,10 @@ 'use client' +import { usePopupController } from '@/store/popupController' + export default function SuitableButton() { + const popupController = usePopupController() + return (
@@ -10,7 +14,7 @@ export default function SuitableButton() {
-
From 36bcdd00a1c14c72cd151e33c84f87f5970f6d97 Mon Sep 17 00:00:00 2001 From: Daseul Kim Date: Fri, 23 May 2025 17:14:07 +0900 Subject: [PATCH 2/7] =?UTF-8?q?feat:=20=EC=A7=80=EB=B6=95=EC=9E=AC=20?= =?UTF-8?q?=EC=A0=81=ED=95=A9=EC=84=B1=20=EC=A0=84=EC=B2=B4=EC=84=A0?= =?UTF-8?q?=ED=83=9D/=EC=A0=84=EC=B2=B4=ED=95=B4=EC=A0=9C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EB=B0=8F=20=EA=B4=80=EB=A0=A8=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 지붕재 적합성 전체선택/전체해제 기능 추가 - 검색조건에 따른 데이터 조회 api 추가 - 선택된 데이터 조회 api 추가 --- src/app/api/suitable/pick/route.ts | 47 ++++++ src/app/api/suitable/route.ts | 67 ++++++++- src/components/popup/SuitableDetailPopup.tsx | 141 ++++-------------- .../popup/SuitableDetailPopupButton.tsx | 23 +++ src/components/suitable/SuitableButton.tsx | 30 +++- src/hooks/useSuitable.ts | 48 +++++- src/store/useSuitableStore.ts | 16 +- src/types/Suitable.ts | 5 + 8 files changed, 250 insertions(+), 127 deletions(-) create mode 100644 src/app/api/suitable/pick/route.ts create mode 100644 src/components/popup/SuitableDetailPopupButton.tsx diff --git a/src/app/api/suitable/pick/route.ts b/src/app/api/suitable/pick/route.ts new file mode 100644 index 0000000..54db249 --- /dev/null +++ b/src/app/api/suitable/pick/route.ts @@ -0,0 +1,47 @@ +import { NextRequest, NextResponse } from 'next/server' +import { prisma } from '@/libs/prisma' +import { 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') + + let query = ` + SELECT + msm.id + , details.detail_id + FROM ms_suitable_main msm + LEFT JOIN ( + SELECT + msd.main_id + , STRING_AGG(msd.id, ',') AS detail_id + 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%' + ; + ` + + // 검색 조건 설정 + if (category) { + query = query.replace('--roofMtCd ', '') + query = query.replace(':roofMtCd', category) + } + if (keyword) { + query = query.replace('--productName ', '') + query = query.replace(':productName', keyword) + } + + const suitableIdSet = await prisma.$queryRawUnsafe(query) + + return NextResponse.json(suitableIdSet) + } catch (error) { + console.error('❌ 데이터 조회 중 오류가 발생했습니다:', error) + return NextResponse.json({ error: '데이터 조회 중 오류가 발생했습니다' }, { status: 500 }) + } +} diff --git a/src/app/api/suitable/route.ts b/src/app/api/suitable/route.ts index 7f7be9c..df42e1e 100644 --- a/src/app/api/suitable/route.ts +++ b/src/app/api/suitable/route.ts @@ -1,12 +1,63 @@ -import { NextResponse } from 'next/server' +import { NextRequest, NextResponse } from 'next/server' import { prisma } from '@/libs/prisma' +import { Suitable } from '@/types/Suitable' -export async function POST(request: Request) { - const body = await request.json() - // @ts-ignore - const suitables = await prisma.MS_SUITABLE.createMany({ - data: body, - }) +export async function GET(request: NextRequest) { + try { + const searchParams = request.nextUrl.searchParams - return NextResponse.json({ message: 'Suitable created successfully' }) + const ids = searchParams.get('ids') + const detailIds = searchParams.get('subIds') + + 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 + --ids AND details.main_id IN (:mainIds) + --detailIds AND details.id IN (:detailIds) + WHERE 1=1 + --ids AND msm.id IN (:mainIds) + ORDER BY msm.product_name; + ` + + // 검색 조건 설정 + if (ids) { + query = query.replaceAll('--ids ', '') + query = query.replaceAll(':mainIds', ids) + if (detailIds) { + query = query.replaceAll('--detailIds ', '') + query = query.replaceAll(':detailIds', detailIds) + } + } + + const suitable: Suitable[] = await prisma.$queryRawUnsafe(query) + + return NextResponse.json(suitable) + } catch (error) { + console.error('❌ 데이터 조회 중 오류가 발생했습니다:', error) + return NextResponse.json({ error: '데이터 조회 중 오류가 발생했습니다' }, { status: 500 }) + } } + diff --git a/src/components/popup/SuitableDetailPopup.tsx b/src/components/popup/SuitableDetailPopup.tsx index 2d4095d..80524d0 100644 --- a/src/components/popup/SuitableDetailPopup.tsx +++ b/src/components/popup/SuitableDetailPopup.tsx @@ -1,10 +1,39 @@ 'use client' import Image from 'next/image' +import { useCallback, useEffect, useState } from 'react' import { usePopupController } from '@/store/popupController' +import { useSuitableStore } from '@/store/useSuitableStore' +import SuitableDetailPopupButton from './SuitableDetailPopupButton' +import { useSuitable } from '@/hooks/useSuitable' +import { Suitable } from '@/types/Suitable' export default function SuitableDetailPopup() { const popupController = usePopupController() + const { getSuitableDetails, serializeSelectedItems } = useSuitable() + const { selectedItems } = useSuitableStore() + + const [openItems, setOpenItems] = useState>(new Set()) + const [suitableDetails, setSuitableDetails] = useState([]) + + // 아이템 열기/닫기 + const toggleItemOpen = useCallback((itemId: number) => { + setOpenItems((prev) => { + const newOpenItems = new Set(prev) + newOpenItems.has(itemId) ? newOpenItems.delete(itemId) : newOpenItems.add(itemId) + return newOpenItems + }) + }, []) + + // 선택된 아이템 상세 데이터 가져오기 + const getSelectedItemsData = async () => { + const serialized: Map = serializeSelectedItems() + setSuitableDetails(await getSuitableDetails(serialized.get('ids') ?? '', serialized.get('detailIds') ?? '')) + } + + useEffect(() => { + getSelectedItemsData() + }, []) return (
@@ -23,11 +52,11 @@ export default function SuitableDetailPopup() {
-
+
アースティ40
- +
@@ -115,114 +144,8 @@ export default function SuitableDetailPopup() {
-
-
-
アースティ40
-
- -
-
-
-
-
屋根技研 支持瓦
-
㈱ダイトー
-
-
-
屋根材
-
-
-
-
金具タイプ
-
木ねじ打ち込み式
-
-
-
-
-
屋根技研 支持瓦
-
-
- -
-
- -
-
-
-
Dで設置可
-
-
備考
-
入手困難
-
-
-
-
-
屋根技研支持金具
-
-
- -
-
- -
-
-
-
設置不可
-
-
備考
-
入手困難
-
-
-
-
-
屋根技研YGアンカー
-
-
- -
-
-
-
お問い合わせください
-
-
備考
-
入手困難
-
-
-
-
-
ダイドーハント支持瓦Ⅱ
-
-
- -
-
-
-
Ⅳ (D) で設置可
-
-
備考
-
入手困難
-
-
-
-
-
-
-
-
- -
-
- -
-
- -
+
diff --git a/src/components/popup/SuitableDetailPopupButton.tsx b/src/components/popup/SuitableDetailPopupButton.tsx new file mode 100644 index 0000000..52e1bae --- /dev/null +++ b/src/components/popup/SuitableDetailPopupButton.tsx @@ -0,0 +1,23 @@ +'use client' + +export default function SuitableDetailPopupButton() { + return ( +
+
+ +
+
+ +
+
+ +
+
+ ) +} diff --git a/src/components/suitable/SuitableButton.tsx b/src/components/suitable/SuitableButton.tsx index 1aa6be5..20191d2 100644 --- a/src/components/suitable/SuitableButton.tsx +++ b/src/components/suitable/SuitableButton.tsx @@ -1,20 +1,42 @@ 'use client' import { usePopupController } from '@/store/popupController' +import { useSuitable } from '@/hooks/useSuitable' +import { useSuitableStore } from '@/store/useSuitableStore' export default function SuitableButton() { const popupController = usePopupController() + const { getSuitableIds } = useSuitable() + const { selectedItems, addAllSelectedItem, clearSelectedItems } = useSuitableStore() + + const handleSelectAll = async () => { + addAllSelectedItem(await getSuitableIds()) + } + + const handleOpenPopup = () => { + if (selectedItems.size === 0) { + alert('屋根材を選択してください。') + return + } + popupController.setSuitableDetailPopup(true) + } return (
- + {selectedItems.size === 0 ? ( + + ) : ( + + )}
-
diff --git a/src/hooks/useSuitable.ts b/src/hooks/useSuitable.ts index f651b4d..94e61a0 100644 --- a/src/hooks/useSuitable.ts +++ b/src/hooks/useSuitable.ts @@ -3,21 +3,19 @@ import { transformObjectKeys } from '@/libs/axios' import { useSuitableStore } from '@/store/useSuitableStore' import { useAxios } from './useAxios' import { useCommCode } from './useCommCode' -import { SUITABLE_HEAD_CODE, type Suitable, type SuitableDetail } from '@/types/Suitable' +import { SUITABLE_HEAD_CODE, type Suitable, type SuitableDetail, type SuitableIds } from '@/types/Suitable' export function useSuitable() { const { axiosInstance } = useAxios() const { getCommCode } = useCommCode() - const { itemPerPage, selectedCategory, searchValue, suitableCommCode, setSuitableCommCode, isSearch } = useSuitableStore() + const { itemPerPage, selectedCategory, searchValue, suitableCommCode, setSuitableCommCode, isSearch, selectedItems } = useSuitableStore() const getSuitables = async ({ pageNumber, - ids, category, keyword, }: { pageNumber?: number - ids?: string category?: string keyword?: string }): Promise => { @@ -26,7 +24,6 @@ export function useSuitable() { pageNumber: pageNumber || 1, itemPerPage: itemPerPage, } - if (ids) params.ids = ids if (category) params.category = category if (keyword) params.keyword = keyword @@ -38,6 +35,31 @@ export function useSuitable() { } } + const getSuitableIds = async (): Promise => { + try { + const params: Record = {} + if (selectedCategory) params.category = selectedCategory + if (searchValue) params.keyword = searchValue + const response = await axiosInstance(null).get('/api/suitable/pick', { params }) + return response.data + } catch (error) { + console.error('지붕재 아이디 로드 실패:', error) + return [] + } + } + + const getSuitableDetails = async (ids: string, detailIds?: string): Promise => { + try { + const params: Record = { ids: ids } + if (detailIds) params.detailIds = detailIds + const response = await axiosInstance(null).get('/api/suitable', { params }) + 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) { @@ -100,8 +122,23 @@ export function useSuitable() { gcTime: 1000 * 60 * 10, }) + const serializeSelectedItems = (): Map => { + const ids: string[] = [] + const detailIds: string[] = [] + for (const [key, value] of selectedItems) { + ids.push(String(key)) + for (const id of value) detailIds.push(String(id)) + } + return new Map([ + ['ids', ids.join(',')], + ['detailIds', detailIds.join(',')], + ]) + } + return { getSuitables, + getSuitableIds, + getSuitableDetails, getSuitableCommCode, toCodeName, toSuitableDetail, @@ -111,5 +148,6 @@ export function useSuitable() { hasNextPage, isFetchingNextPage, isLoading, + serializeSelectedItems, } } diff --git a/src/store/useSuitableStore.ts b/src/store/useSuitableStore.ts index 74f4d0b..77ddfaa 100644 --- a/src/store/useSuitableStore.ts +++ b/src/store/useSuitableStore.ts @@ -1,5 +1,6 @@ import { create } from 'zustand' import type { CommCode } from '@/types/CommCode' +import type { SuitableIds } from '@/types/Suitable' interface SuitableState { /* 초기 데이터 로드 개수*/ @@ -27,8 +28,10 @@ interface SuitableState { /* 선택된 아이템 리스트 */ selectedItems: Map> - /* 선택된 아이템 추가 */ + /* 선택 아이템 추가 */ addSelectedItem: (mainId: number, detailId?: number, detailIds?: Set) => void + /* 아이템 전체 추가 */ + addAllSelectedItem: (suitableIds: SuitableIds[]) => void /* 선택된 아이템 제거 */ removeSelectedItem: (mainId: number, detailId?: number) => void /* 선택된 아이템 모두 제거 */ @@ -77,6 +80,17 @@ export const useSuitableStore = create((set) => ({ } }, + /* 아이템 전체 추가 */ + addAllSelectedItem: (suitableIds: SuitableIds[]) => { + set(() => { + const newSelectedItems = new Map() + suitableIds.forEach((suitableId) => { + newSelectedItems.set(suitableId.id, new Set(suitableId.detailId.split(',').map(Number))) + }) + return { selectedItems: newSelectedItems } + }) + }, + /* 선택된 아이템 제거 */ removeSelectedItem: (mainId: number, detailId?: number) => { set((state) => { diff --git a/src/types/Suitable.ts b/src/types/Suitable.ts index 270dd46..b8fbf72 100644 --- a/src/types/Suitable.ts +++ b/src/types/Suitable.ts @@ -34,3 +34,8 @@ export type Suitable = { detailCnt: number detail: string } + +export type SuitableIds = { + id: number + detailId: string +} From 8bcffd43bbe89770afca1739bc6be2172c8e89a8 Mon Sep 17 00:00:00 2001 From: Daseul Kim Date: Mon, 26 May 2025 16:31:21 +0900 Subject: [PATCH 3/7] =?UTF-8?q?feat:=20=EC=A7=80=EB=B6=95=EC=9E=AC?= =?UTF-8?q?=EC=A0=81=ED=95=A9=EC=84=B1=20=EC=B4=88=EA=B8=B0=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EC=A0=84=EC=B2=B4=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EB=AF=B8=ED=91=9C=EC=B6=9C,=20=EA=B2=80=EC=83=89=EC=A1=B0?= =?UTF-8?q?=EA=B1=B4=20=EA=B7=B8=EB=A3=B9=EC=BD=94=EB=93=9C=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/suitable/list/route.ts | 11 ++++++++-- src/components/suitable/Suitable.tsx | 15 +++++++------- src/components/suitable/SuitableList.tsx | 2 +- src/hooks/useSuitable.ts | 26 ++++++++++++++++++++---- src/store/useSuitableStore.ts | 25 +++++++++++------------ src/types/Suitable.ts | 4 +++- 6 files changed, 54 insertions(+), 29 deletions(-) diff --git a/src/app/api/suitable/list/route.ts b/src/app/api/suitable/list/route.ts index 005d556..61df570 100644 --- a/src/app/api/suitable/list/route.ts +++ b/src/app/api/suitable/list/route.ts @@ -41,7 +41,7 @@ export async function GET(request: NextRequest) { ) AS details ON msm.id = details.main_id WHERE 1=1 - --roofMtCd AND msm.roof_mt_cd = ':roofMtCd' + --roofMtCd AND msm.roof_mt_cd IN (:roofMtCd) --productName AND msm.product_name LIKE '%:productName%' ORDER BY msm.product_name OFFSET (@P1 - 1) * @P2 ROWS @@ -50,8 +50,13 @@ export async function GET(request: NextRequest) { // 검색 조건 설정 if (category) { + let roofMtQuery = ` + SELECT roof_mt_cd + FROM ms_suitable_roof_material_group + WHERE roof_matl_grp_cd = ':roofMtGrpCd' + ` query = query.replace('--roofMtCd ', '') - query = query.replace(':roofMtCd', category) + query = query.replace(':roofMtCd', roofMtQuery.replace(':roofMtGrpCd', category)) } if (keyword) { query = query.replace('--productName ', '') @@ -60,6 +65,8 @@ export async function GET(request: NextRequest) { const suitable: Suitable[] = await prisma.$queryRawUnsafe(query, pageNumber, itemPerPage) + // console.log(`검색 조건 :::: 카테고리: ${category}, 키워드: ${keyword}`) + return NextResponse.json(suitable) } catch (error) { console.error('❌ 데이터 조회 중 오류가 발생했습니다:', error) diff --git a/src/components/suitable/Suitable.tsx b/src/components/suitable/Suitable.tsx index f5f22b2..35fe94a 100644 --- a/src/components/suitable/Suitable.tsx +++ b/src/components/suitable/Suitable.tsx @@ -10,29 +10,28 @@ import { SUITABLE_HEAD_CODE } from '@/types/Suitable' export default function Suitable() { const [reference, setReference] = useState(true) + const [searchValue, setSearchValue] = useState('') - const { getSuitableCommCode } = useSuitable() - const { suitableCommCode, selectedCategory, setSelectedCategory, searchValue, setSearchValue, setIsSearch, clearSelectedItems } = useSuitableStore() + const { getSuitableCommCode, clearSuitableSearch } = useSuitable() + const { suitableCommCode, selectedCategory, setSelectedCategory, setSearchKeyword, clearSearchKeyword } = useSuitableStore() const handleInputSearch = async () => { if (!searchValue.trim()) { alert('屋根材の製品名を入力してください。') return } - setIsSearch(true) + setSearchKeyword(searchValue) } const handleInputClear = () => { setSearchValue('') - setIsSearch(false) + clearSearchKeyword() } useEffect(() => { getSuitableCommCode() return () => { - setSelectedCategory('') - setSearchValue('') - clearSelectedItems() + clearSuitableSearch() } }, []) @@ -41,7 +40,7 @@ export default function Suitable() {
setSelectedCategory(e.target.value)}> - - {suitableCommCode.get(SUITABLE_HEAD_CODE.ROOF_MATERIAL_GROUP)?.map((category: CommCode, index: number) => ( - - ))} - -
-
-
- setSearchValue(e.target.value)} - onKeyDown={(e) => { - if (e.key === 'Enter') { - handleInputSearch() - } - }} - /> - {searchValue &&
-
+
diff --git a/src/components/suitable/SuitableSearch.tsx b/src/components/suitable/SuitableSearch.tsx new file mode 100644 index 0000000..1337ced --- /dev/null +++ b/src/components/suitable/SuitableSearch.tsx @@ -0,0 +1,67 @@ +'use client' + +import { useEffect, useState } from 'react' +import { useSuitable } from '@/hooks/useSuitable' +import { useSuitableStore } from '@/store/useSuitableStore' +import type { CommCode } from '@/types/CommCode' +import { SUITABLE_HEAD_CODE } from '@/types/Suitable' + +export default function SuitableSearch() { + const [searchValue, setSearchValue] = useState('') + + const { getSuitableCommCode, clearSuitableSearch } = useSuitable() + const { suitableCommCode, selectedCategory, setSelectedCategory, setSearchKeyword } = useSuitableStore() + + const handleInputSearch = async () => { + if (!searchValue.trim()) { + alert('屋根材の製品名を入力してください。') + return + } + setSearchKeyword(searchValue) + } + + const handleInputClear = () => { + setSearchValue('') + clearSuitableSearch({ items: true, keyword: true }) + } + + useEffect(() => { + getSuitableCommCode() + return () => { + clearSuitableSearch({ items: true, category: true, keyword: true }) + } + }, []) + + return ( + <> +
+ +
+
+
+ setSearchValue(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') { + handleInputSearch() + } + }} + /> + {searchValue &&
+
+ + ) +} From 7f0d1cf87757e48cbfb64a5ee5d92f68734eb8d4 Mon Sep 17 00:00:00 2001 From: Daseul Kim Date: Tue, 27 May 2025 13:39:11 +0900 Subject: [PATCH 7/7] =?UTF-8?q?feat:=20=EC=A7=80=EB=B6=95=EC=9E=AC=20?= =?UTF-8?q?=EC=A0=81=ED=95=A9=EC=84=B1=20=EA=B2=80=EC=83=89=EC=96=B4=2030?= =?UTF-8?q?=EA=B8=80=EC=9E=90=20=EC=A0=9C=ED=95=9C=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/suitable/SuitableSearch.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/components/suitable/SuitableSearch.tsx b/src/components/suitable/SuitableSearch.tsx index 1337ced..8b4a883 100644 --- a/src/components/suitable/SuitableSearch.tsx +++ b/src/components/suitable/SuitableSearch.tsx @@ -12,6 +12,15 @@ export default function SuitableSearch() { const { getSuitableCommCode, clearSuitableSearch } = useSuitable() const { suitableCommCode, selectedCategory, setSelectedCategory, setSearchKeyword } = useSuitableStore() + const handleInputChange = (value: string) => { + if (Array.from(value).length > 30) { + alert('検索ワードは最大30文字まで入力できます。') + setSearchValue(value.slice(0, 30)) + return + } + setSearchValue(value) + } + const handleInputSearch = async () => { if (!searchValue.trim()) { alert('屋根材の製品名を入力してください。') @@ -51,7 +60,7 @@ export default function SuitableSearch() { className="search-frame" placeholder="屋根材 製品名を入力してください." value={searchValue} - onChange={(e) => setSearchValue(e.target.value)} + onChange={(e) => handleInputChange(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') { handleInputSearch()