diff --git a/src/app/api/comm-code/route.ts b/src/app/api/comm-code/route.ts
new file mode 100644
index 0000000..6b4852c
--- /dev/null
+++ b/src/app/api/comm-code/route.ts
@@ -0,0 +1,44 @@
+import { NextRequest, NextResponse } from 'next/server'
+import { prisma } from '@/libs/prisma'
+import type { CommCode } from '@/types/CommCode'
+
+export async function GET(request: NextRequest) {
+ try {
+ const searchParams = request.nextUrl.searchParams
+ const headCode = searchParams.get('headCode')
+
+ // @ts-ignore
+ const headCd = await prisma.BC_COMM_H.findFirst({
+ where: {
+ HEAD_ID: headCode,
+ },
+ select: {
+ HEAD_CD: true,
+ },
+ })
+
+ if (!headCd) {
+ return NextResponse.json({ error: `${headCode}를 찾을 수 없습니다` }, { status: 404 })
+ }
+
+ // @ts-ignore
+ const roofMaterials: CommCode[] = await prisma.BC_COMM_L.findMany({
+ where: {
+ HEAD_CD: headCd.HEAD_CD,
+ },
+ select: {
+ HEAD_CD: true,
+ CODE: true,
+ CODE_JP: true,
+ },
+ orderBy: {
+ CODE: 'asc',
+ },
+ })
+
+ return NextResponse.json(roofMaterials)
+ } catch (error) {
+ console.error('❌ 데이터 조회 중 오류가 발생했습니다:', error)
+ return NextResponse.json({ error: '데이터 조회 중 오류가 발생했습니다' }, { status: 500 })
+ }
+}
diff --git a/src/app/api/suitable/category/route.ts b/src/app/api/suitable/category/route.ts
deleted file mode 100644
index 288a74a..0000000
--- a/src/app/api/suitable/category/route.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { NextResponse } from 'next/server'
-import { prisma } from '@/libs/prisma'
-
-export async function GET() {
- // @ts-ignore
- const roofMaterialCategory = await prisma.MS_SUITABLE.findMany({
- select: {
- roof_material: true,
- },
- distinct: ['roof_material'],
- orderBy: {
- roof_material: 'asc',
- },
- })
- return NextResponse.json(roofMaterialCategory)
-}
diff --git a/src/app/api/suitable/detail/route.ts b/src/app/api/suitable/detail/route.ts
deleted file mode 100644
index d29f666..0000000
--- a/src/app/api/suitable/detail/route.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { NextResponse } from 'next/server'
-import { prisma } from '@/libs/prisma'
-
-export async function GET(request: Request) {
- const { searchParams } = new URL(request.url)
- const roofMaterial = searchParams.get('roof-material')
- console.log('🚀 ~ GET ~ roof-material:', roofMaterial)
-
- // @ts-ignore
- const suitables = await prisma.MS_SUITABLE.findMany({
- where: {
- roof_material: roofMaterial,
- },
- })
-
- return NextResponse.json(suitables)
-}
diff --git a/src/app/api/suitable/list/route.ts b/src/app/api/suitable/list/route.ts
index d789275..d6d8f37 100644
--- a/src/app/api/suitable/list/route.ts
+++ b/src/app/api/suitable/list/route.ts
@@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/libs/prisma'
+import { SUITABLE_HEAD_CODE } from '@/types/Suitable'
export async function GET(request: NextRequest) {
try {
@@ -9,24 +10,59 @@ export async function GET(request: NextRequest) {
let whereCondition: any = {}
if (category) {
- whereCondition['roof_material'] = category
+ whereCondition[SUITABLE_HEAD_CODE.ROOF_MT_CD] = category
}
if (keyword) {
- whereCondition['product_name'] = {
+ whereCondition['PRODUCT_NAME'] = {
contains: keyword,
}
}
- console.log('🚀 ~ /api/suitable/list: ~ prisma where condition:', whereCondition)
+ // // 1 include 사용
+ // // @ts-ignore
+ // const suitable = await prisma.MS_SUITABLE_MAIN.findMany({
+ // where: whereCondition,
+ // include: {
+ // MS_SUITABLE_DETAIL: true,
+ // },
+ // orderBy: {
+ // PRODUCT_NAME: 'asc',
+ // },
+ // })
+
+ // 2 include 안쓰고 따로 쿼리, 쓸거만 골라서 조회
// @ts-ignore
- const suitables = await prisma.MS_SUITABLE.findMany({
+ const suitable = await prisma.MS_SUITABLE_MAIN.findMany({
+ select: {
+ ID: true,
+ PRODUCT_NAME: true,
+ ROOF_MT_CD: true,
+ },
where: whereCondition,
orderBy: {
- product_name: 'asc',
+ PRODUCT_NAME: 'asc',
},
})
+ // @ts-ignore
+ const suitableDetail = await prisma.MS_SUITABLE_DETAIL.findMany({
+ select: {
+ ID: true,
+ MAIN_ID: true,
+ TRESTLE_MFPC_CD: true,
+ TRESTLE_MANUFACTURER_PRODUCT_NAME: true,
+ MEMO: true,
+ },
+ where: whereCondition
+ ? {
+ MAIN_ID: {
+ in: suitable.map((suitable: { ID: number }) => suitable.ID),
+ },
+ }
+ : undefined,
+ orderBy: [{ MAIN_ID: 'asc' }, { TRESTLE_MANUFACTURER_PRODUCT_NAME: 'asc' }],
+ })
- return NextResponse.json(suitables)
+ return NextResponse.json({ suitable, suitableDetail })
} catch (error) {
console.error('❌ 데이터 조회 중 오류가 발생했습니다:', error)
return NextResponse.json({ error: '데이터 조회 중 오류가 발생했습니다' }, { status: 500 })
diff --git a/src/components/suitable/Suitable.tsx b/src/components/suitable/Suitable.tsx
index 5120aa3..f8ebb3e 100644
--- a/src/components/suitable/Suitable.tsx
+++ b/src/components/suitable/Suitable.tsx
@@ -1,27 +1,69 @@
'use client'
-import { useState } from 'react'
-import SuitableCheckData from './SuitableCheckData'
-import SuitableNoData from './SuitableNoData'
+import { useEffect, useState } from 'react'
+import SuitableList from './SuitableList'
+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 Suitable() {
const [reference, setReference] = useState(false)
+ const { getSuitableCommCode, refetchBySearch } = useSuitable()
+ 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 (
-
@@ -55,33 +97,8 @@ export default function Suitable() {
- {/* checkData */}
- {/* 데이터 없을경우 버튼 영역 안보여야함 */}
-
-
-
-
+
- {/* 데이터 없을경우 버튼 영역 안보여야함 */}
-
-
-
-
-
-
-
-
-
-
-
- {/* 검색기록 없을떄 위에 두 영역 안보이고 이 부분만 보여야 함*/}
- {/* */}
)
}
diff --git a/src/components/suitable/SuitableCheckData.tsx b/src/components/suitable/SuitableCheckData.tsx
deleted file mode 100644
index 2cef53b..0000000
--- a/src/components/suitable/SuitableCheckData.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-'use client'
-
-export default function SuitableCheckData() {
- return (
- <>
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
- >
- )
-}
diff --git a/src/components/suitable/SuitableList.tsx b/src/components/suitable/SuitableList.tsx
new file mode 100644
index 0000000..e5a21c6
--- /dev/null
+++ b/src/components/suitable/SuitableList.tsx
@@ -0,0 +1,76 @@
+'use client'
+
+import SuitableNoData from './SuitableNoData'
+import { useSuitable } from '@/hooks/useSuitable'
+import { useSuitableStore } from '@/store/useSuitableStore'
+import { SUITABLE_HEAD_CODE, type SuitableMain, type SuitableDetail } from '@/types/Suitable'
+
+export default function SuitableList() {
+ const { toCodeName, suitableSearchResults, isSearchLoading } = useSuitable()
+ const { selectedItems, addSelectedItem, removeSelectedItem } = useSuitableStore()
+
+ const handleItemClick = (itemId: number) => {
+ selectedItems.some((selected) => selected === itemId) ? removeSelectedItem(itemId) : addSelectedItem(itemId)
+ }
+
+ const suitableCheck = (value: string) => {
+ if (value === '×') {
+ return
+ } else if (value === 'ー') {
+ return
+ } else {
+ return
+ }
+ }
+
+ const filterSuitableDetail = (mainId: number): SuitableDetail[] | undefined => {
+ const result: SuitableDetail[] = []
+ for (const subItem of suitableSearchResults?.suitableDetail ?? []) {
+ if (subItem.MAIN_ID > mainId) break
+ if (subItem.MAIN_ID === mainId) {
+ result.push(subItem)
+ }
+ }
+ return result
+ }
+
+ return (
+ <>
+ {isSearchLoading ? (
+ Loading...
+ ) : suitableSearchResults && suitableSearchResults.suitable.length > 0 ? (
+ suitableSearchResults.suitable.map((item: SuitableMain) => (
+
+
+
+
+
+
+
+
+
+
+
+ -
+ {filterSuitableDetail(item.ID)?.map((subItem: SuitableDetail) => (
+
+
+
+
+
+
+ {suitableCheck(subItem.TRESTLE_MANUFACTURER_PRODUCT_NAME)}
+ {subItem.MEMO && }
+
+
+ ))}
+
+
+
+ ))
+ ) : (
+
+ )}
+ >
+ )
+}
diff --git a/src/hooks/useCommCode.ts b/src/hooks/useCommCode.ts
new file mode 100644
index 0000000..bb50240
--- /dev/null
+++ b/src/hooks/useCommCode.ts
@@ -0,0 +1,18 @@
+import { axiosInstance } from '@/libs/axios'
+import type { CommCode } from '@/types/CommCode'
+
+export function useCommCode() {
+ const getCommCode = async (headCode: string): Promise => {
+ try {
+ const response = await axiosInstance(null).get('/api/comm-code', { params: { headCode: headCode } })
+ return response.data
+ } catch (error) {
+ console.error(`common code (${headCode}) load failed:`, error)
+ return []
+ }
+ }
+
+ return {
+ getCommCode,
+ }
+}
diff --git a/src/hooks/useSuitable.ts b/src/hooks/useSuitable.ts
index 62f00e2..0be29a4 100644
--- a/src/hooks/useSuitable.ts
+++ b/src/hooks/useSuitable.ts
@@ -1,30 +1,95 @@
-import { suitableApi } from '@/api/suitable'
+import { useQuery } from '@tanstack/react-query'
+import { axiosInstance } from '@/libs/axios'
+import { useSuitableStore } from '@/store/useSuitableStore'
+import { useCommCode } from './useCommCode'
+import { SUITABLE_HEAD_CODE, type SuitableData, type SuitableMain, type SuitableDetail } from '@/types/Suitable'
export function useSuitable() {
- const getCategories = async () => {
- try {
- return await suitableApi.getCategory()
- } catch (error) {
- console.error('카테고리 데이터 로드 실패:', error)
- return []
- }
- }
+ const { getCommCode } = useCommCode()
+ const { selectedCategory, searchValue, suitableCommCode, setSuitableCommCode, isSearch } = useSuitableStore()
- const getSuitables = async () => {
+ const getSuitables = async (): Promise => {
try {
- return await suitableApi.getList()
+ const response = await axiosInstance(null).get('/api/suitable/list')
+ return response.data
} catch (error) {
console.error('지붕재 데이터 로드 실패:', error)
+ return {
+ suitable: [],
+ suitableDetail: [],
+ }
}
}
- const updateSearchResults = async (selectedCategory: string | undefined, searchValue: string | undefined) => {
- try {
- return await suitableApi.getList(selectedCategory, searchValue)
- } catch (error) {
- console.error('지붕재 데이터 검색 실패:', error)
+ // 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)
+ })
}
}
- return { getCategories, getSuitables, updateSearchResults }
+ const toCodeName = (headCode: string, code: string): string => {
+ const commCode = suitableCommCode.get(headCode)
+ return commCode?.find((item) => item.CODE === code)?.CODE_JP || ''
+ }
+
+ 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({
+ queryKey: ['suitables', 'search', selectedCategory, searchValue],
+ queryFn: async () => {
+ if (!isSearch && !selectedCategory) {
+ return suitableList ?? (await getSuitables()) // 검색 상태가 아니면 초기 데이터 반환 임시처리
+ } else {
+ const suitable = suitableList?.suitable.filter((item: SuitableMain) => {
+ const categoryMatch = !selectedCategory || item.ROOF_MT_CD === selectedCategory
+ const searchMatch = !searchValue || item.PRODUCT_NAME.includes(searchValue)
+ return categoryMatch && searchMatch
+ })
+ const mainIds = suitable?.map((item: SuitableMain) => item.ID)
+ const suitableDetail = suitableList?.suitableDetail.filter((item: SuitableDetail) => {
+ return mainIds?.includes(item.MAIN_ID)
+ })
+ return {
+ suitable: suitable ?? [],
+ suitableDetail: suitableDetail ?? [],
+ }
+ }
+ },
+ staleTime: 1000 * 60 * 10,
+ gcTime: 1000 * 60 * 10,
+ enabled: true,
+ })
+
+ return {
+ getSuitables,
+ getSuitableCommCode,
+ toCodeName,
+ suitableList,
+ isInitialLoading,
+ suitableSearchResults,
+ refetchBySearch,
+ isSearchLoading,
+ }
}
diff --git a/src/store/useSuitableStore.ts b/src/store/useSuitableStore.ts
index 17b88c5..5fa4cd0 100644
--- a/src/store/useSuitableStore.ts
+++ b/src/store/useSuitableStore.ts
@@ -1,68 +1,71 @@
import { create } from 'zustand'
-import { Suitable, suitableApi } from '@/api/suitable'
+import type { CommCode } from '@/types/CommCode'
interface SuitableState {
- // // 검색 결과 리스트
- // searchResults: Suitable[]
- // // 초기 데이터 로드
- // fetchInitializeData: () => Promise
- // // 검색 결과 설정
- // setSearchResults: (results: Suitable[]) => void
- // // 검색 결과 초기화
- // resetSearchResults: () => void
+ /* 공통코드 */
+ suitableCommCode: Map
+ /* 공통코드 설정 */
+ setSuitableCommCode: (headCode: string, commCode: CommCode[]) => void
- // 선택된 아이템 리스트
- selectedItems: Suitable[]
- // 선택된 아이템 추가
- addSelectedItem: (item: Suitable) => void
- // 선택된 아이템 제거
+ /* 검색 상태 */
+ isSearch: boolean
+ /* 검색 상태 설정 */
+ setIsSearch: (isSearch: boolean) => void
+
+ /* 선택된 카테고리 */
+ selectedCategory: string
+ /* 선택된 카테고리 설정 */
+ setSelectedCategory: (category: string) => void
+
+ /* 검색 값 */
+ searchValue: string
+ /* 검색 값 설정 */
+ setSearchValue: (value: string) => void
+
+ /* 선택된 아이템 리스트 */
+ selectedItems: number[]
+ /* 선택된 아이템 추가 */
+ addSelectedItem: (itemId: number) => void
+ /* 선택된 아이템 제거 */
removeSelectedItem: (itemId: number) => void
- // 선택된 아이템 모두 제거
+ /* 선택된 아이템 모두 제거 */
clearSelectedItems: () => void
}
export const useSuitableStore = create((set) => ({
- // // 초기 상태
- // searchResults: [],
+ suitableCommCode: new Map() as Map,
+ isSearch: false as boolean,
+ selectedCategory: '' as string,
+ searchValue: '' as string,
+ selectedItems: [] as number[],
- // // 초기 데이터 로드
- // fetchInitializeData: async () => {
- // const suitables = await fetchInitialSuitablee()
- // set({ searchResults: suitables })
- // },
-
- // // 검색 결과 설정
- // setSearchResults: (results) => set({ searchResults: results }),
-
- // // 검색 결과 초기화
- // resetSearchResults: () => set({ searchResults: [] }),
-
- // 초기 상태
- selectedItems: [],
-
- // 선택된 아이템 추가 (중복 방지)
- addSelectedItem: (item) =>
+ /* 공통코드 설정 */
+ setSuitableCommCode: (headCode: string, commCode: CommCode[]) =>
set((state) => ({
- selectedItems: state.selectedItems.some((i) => i.id === item.id) ? state.selectedItems : [...state.selectedItems, item],
+ suitableCommCode: new Map(state.suitableCommCode).set(headCode, commCode),
})),
- // 선택된 아이템 제거
- removeSelectedItem: (itemId) =>
+ /* 검색 상태 설정 */
+ setIsSearch: (isSearch: boolean) => set({ isSearch }),
+
+ /* 선택된 카테고리 설정 */
+ setSelectedCategory: (category: string) => set({ selectedCategory: category }),
+
+ /* 검색 값 설정 */
+ setSearchValue: (value: string) => set({ searchValue: value }),
+
+ /* 선택된 아이템 추가 */
+ addSelectedItem: (itemId: number) =>
set((state) => ({
- selectedItems: state.selectedItems.filter((item) => item.id !== itemId),
+ selectedItems: state.selectedItems.some((i) => i === itemId) ? state.selectedItems : [...state.selectedItems, itemId],
})),
- // 선택된 아이템 모두 제거
+ /* 선택된 아이템 제거 */
+ removeSelectedItem: (itemId: number) =>
+ set((state) => ({
+ selectedItems: state.selectedItems.filter((i) => i !== itemId),
+ })),
+
+ /* 선택된 아이템 모두 제거 */
clearSelectedItems: () => set({ selectedItems: [] }),
}))
-
-// // 전체 데이터 초기화 함수
-// async function fetchInitialSuitablee() {
-// try {
-// const suitable = await suitableApi.getList()
-// return suitable
-// } catch (error) {
-// console.error('초기 데이터 로드 실패:', error)
-// return []
-// }
-// }
diff --git a/src/types/CommCode.ts b/src/types/CommCode.ts
new file mode 100644
index 0000000..19daecd
--- /dev/null
+++ b/src/types/CommCode.ts
@@ -0,0 +1,5 @@
+export type CommCode = {
+ HEAD_CD: string
+ CODE: string
+ CODE_JP: string
+}
diff --git a/src/types/Suitable.ts b/src/types/Suitable.ts
new file mode 100644
index 0000000..5df0add
--- /dev/null
+++ b/src/types/Suitable.ts
@@ -0,0 +1,40 @@
+export enum SUITABLE_HEAD_CODE {
+ /* 지붕재 제조사명 */
+ MANU_FT_CD = 'MANU_FT_CD',
+ /* 지붕재 종류 */
+ ROOF_MT_CD = 'ROOF_MT_CD',
+ /* 마운팅 브래킷 종류 */
+ ROOF_SH_CD = 'ROOF_SH_CD',
+ /* 마운팅 브래킷 제조사명 및 제품코드드 */
+ TRESTLE_MFPC_CD = 'TRESTLE_MFPC_CD',
+}
+
+export type SuitableIncludeDetail = {
+ ID: number
+ PRODUCT_NAME: string
+ MANU_FT_CD: string
+ ROOF_MT_CD: string
+ ROOF_SH_CD: string
+ MS_SUITABLE_DETAIL: SuitableDetail[]
+}
+
+export type SuitableData = {
+ suitable: SuitableMain[]
+ suitableDetail: SuitableDetail[]
+}
+
+export type SuitableMain = {
+ ID: number
+ PRODUCT_NAME: string
+ MANU_FT_CD: string
+ ROOF_MT_CD: string
+ ROOF_SH_CD: string
+}
+
+export type SuitableDetail = {
+ ID: number
+ MAIN_ID: number
+ TRESTLE_MFPC_CD: string
+ TRESTLE_MANUFACTURER_PRODUCT_NAME: string
+ MEMO: string
+}