From 1db17d6558490738e5e01d9e1005144f1f1580cd Mon Sep 17 00:00:00 2001 From: Daseul Kim Date: Thu, 5 Jun 2025 15:34:07 +0900 Subject: [PATCH 1/4] =?UTF-8?q?style:=20=EC=A7=80=EB=B6=95=EC=9E=AC=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=ED=8C=9D=EC=97=85=20=ED=95=98=EB=8B=A8=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20float=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../popup/SuitableDetailPopupButton.tsx | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/components/popup/SuitableDetailPopupButton.tsx b/src/components/popup/SuitableDetailPopupButton.tsx index 085dc92..8340f94 100644 --- a/src/components/popup/SuitableDetailPopupButton.tsx +++ b/src/components/popup/SuitableDetailPopupButton.tsx @@ -19,21 +19,23 @@ export default function SuitableDetailPopupButton() { } return ( -
-
- -
-
- -
-
- +
+
+
+ +
+
+ +
+
+ +
) From 85344afd431eaff3eb876ba580bcfc8b824f84ba Mon Sep 17 00:00:00 2001 From: Daseul Kim Date: Thu, 5 Jun 2025 16:15:07 +0900 Subject: [PATCH 2/4] =?UTF-8?q?remove:=20=EA=B5=AC=20=EC=A7=80=EB=B6=95?= =?UTF-8?q?=EC=9E=AC=20=EC=A0=81=ED=95=A9=EC=84=B1=20pdf=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/pdf/suitable/page.tsx | 9 -- src/components/pdf/SuitableDownloadPdf.tsx | 132 --------------------- 2 files changed, 141 deletions(-) delete mode 100644 src/app/pdf/suitable/page.tsx delete mode 100644 src/components/pdf/SuitableDownloadPdf.tsx diff --git a/src/app/pdf/suitable/page.tsx b/src/app/pdf/suitable/page.tsx deleted file mode 100644 index cdc520a..0000000 --- a/src/app/pdf/suitable/page.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import SuitableDownloadPdf from '@/components/pdf/SuitableDownloadPdf' - -export default function page() { - return ( - <> - - - ) -} diff --git a/src/components/pdf/SuitableDownloadPdf.tsx b/src/components/pdf/SuitableDownloadPdf.tsx deleted file mode 100644 index c034fe9..0000000 --- a/src/components/pdf/SuitableDownloadPdf.tsx +++ /dev/null @@ -1,132 +0,0 @@ -'use client' - -import { useEffect, useRef, useState } from 'react' -import generatePDF, { Margin, Options, Resolution, usePDF } from 'react-to-pdf' -import { useSuitable } from '@/hooks/useSuitable' -import { useSuitableStore } from '@/store/useSuitableStore' -import { SUITABLE_HEAD_CODE, type Suitable, type SuitableDetail } from '@/types/Suitable' - -export default function SuitableDownloadPdf() { - const [fileName, setFileName] = useState([]) - const [createTime, setCreateTime] = useState('') - const targetRef = useRef(null) - const { toCodeName, toSuitableDetail, selectedSuitables, isSelectedSuitablesLoading } = useSuitable() - const { selectedCategory, suitableCommCode, selectedItemsSearching, setSelectedItemsSearching } = useSuitableStore() - - const handleDownPdf = () => { - const options: Options = { - filename: `${fileName.join('_')}.pdf`, - method: 'open' as const, - resolution: Resolution.HIGH, - page: { - margin: Margin.SMALL, - format: 'A4', - orientation: 'landscape' as const, - }, - canvas: { - mimeType: 'image/png' as const, - qualityRatio: 1, - }, - overrides: { - pdf: { - compress: true, - }, - canvas: { - useCORS: true, - }, - }, - } - - generatePDF(targetRef, options) - // generatePDF(targetRef, { filename: 'page.pdf' }) - } - - const formatDateString = () => { - const now = new Date() - const year = now.getFullYear() - const month = now.getMonth() + 1 - const day = now.getDate() - const hours = now.getHours() - const minutes = now.getMinutes() - - return `${year}年${month}月${day}日 ${hours}:${minutes.toString().padStart(2, '0')}` - } - - useEffect(() => { - setCreateTime(formatDateString()) - setFileName([ - `(${suitableCommCode.get(SUITABLE_HEAD_CODE.ROOF_MATL_GRP_CD)?.find((category) => category.code === selectedCategory)?.codeJp})`, - '屋根材適合表', - ]) - if (!selectedItemsSearching) { - setSelectedItemsSearching(true) - } - }, []) - - if (!selectedCategory) return
잘못된 접근입니다.
- if (isSelectedSuitablesLoading) return
Loading...
- - return ( - <> - {/* */} -
-
-
-
ハンファジャパン株式会社
-
{fileName.join(' ')}
-
{createTime}
-
-
-

本適合表は参考資料としてご使用下さい。

-

屋根材製品の形状・仕様はメーカーより変更される場合が御座います。

-

又、現場環境(働き、勾配、瓦桟木条件等)により本適合表と異なる適合結果となる場合が御座います。予めご了承下さい。

-

屋根材以外の設置条件(垂木、野地板等の設置基準)も必ずご確認下さい。

-
-
{createTime}
-
-
-
- {selectedSuitables?.map((item: Suitable) => ( -
-
- {item.productName} - {toCodeName(SUITABLE_HEAD_CODE.MANU_FT_CD, item.manuFtCd)} - {toCodeName(SUITABLE_HEAD_CODE.ROOF_MT_CD, item.roofMtCd)} -
-
- - - - - - - - - - - - - - - - - {toSuitableDetail(item.detail).map((subItem: SuitableDetail) => ( - - - - - - - ))} - -
金具タイプ金具名設置可否備考
{toCodeName(SUITABLE_HEAD_CODE.ROOF_SH_CD, item.roofShCd)}{toCodeName(SUITABLE_HEAD_CODE.TRESTLE_MFPC_CD, subItem.trestleMfpcCd)}{subItem.trestleManufacturerProductName}{subItem.memo}
-
-
- ))} -
-
{createTime}
-
-
- - ) -} From c62414e61942aed5189433fbc49e82e6c5049901 Mon Sep 17 00:00:00 2001 From: Daseul Kim Date: Thu, 5 Jun 2025 16:45:57 +0900 Subject: [PATCH 3/4] =?UTF-8?q?refactor:=20=EC=A7=80=EB=B6=95=EC=9E=AC=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=ED=8C=9D=EC=97=85=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=A1=B0=ED=9A=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 구 지붕재 적합성 pdf 페이지와 연계된 내용 제거 및 수정 - crypto-js 라이브러리 제거 --- package-lock.json | 9 --- package.json | 2 - src/components/popup/SuitableDetailPopup.tsx | 15 ++--- src/components/suitable/SuitableList.tsx | 3 +- src/hooks/useSuitable.ts | 59 ++++++-------------- src/store/useSuitableStore.ts | 18 ++---- 6 files changed, 28 insertions(+), 78 deletions(-) diff --git a/package-lock.json b/package-lock.json index 98a452c..50673cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,6 @@ "@tanstack/react-query-devtools": "^5.71.0", "@types/nodemailer": "^6.4.17", "axios": "^1.8.4", - "crypto-js": "^4.2.0", "env-cmd": "^10.1.0", "iron-session": "^8.0.4", "lucide": "^0.503.0", @@ -33,7 +32,6 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4", - "@types/crypto-js": "^4.2.2", "@types/mysql": "^2.15.27", "@types/node": "^20", "@types/react": "^19", @@ -2142,13 +2140,6 @@ "integrity": "sha512-7qSgZbincDDDFyRweCIEvZULFAw5iz/DeunhvuxpL31nfntX3P4Yd4HkHBRg9H8CdqY1e5WFN1PZIz/REL9MVQ==", "license": "MIT" }, - "node_modules/@types/crypto-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz", - "integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/mysql": { "version": "2.15.27", "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.27.tgz", diff --git a/package.json b/package.json index 9ed3d18..064f055 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,6 @@ "@tanstack/react-query-devtools": "^5.71.0", "@types/nodemailer": "^6.4.17", "axios": "^1.8.4", - "crypto-js": "^4.2.0", "env-cmd": "^10.1.0", "iron-session": "^8.0.4", "lucide": "^0.503.0", @@ -40,7 +39,6 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4", - "@types/crypto-js": "^4.2.2", "@types/mysql": "^2.15.27", "@types/node": "^20", "@types/react": "^19", diff --git a/src/components/popup/SuitableDetailPopup.tsx b/src/components/popup/SuitableDetailPopup.tsx index e4635c0..461ac1b 100644 --- a/src/components/popup/SuitableDetailPopup.tsx +++ b/src/components/popup/SuitableDetailPopup.tsx @@ -5,16 +5,14 @@ import { useCallback, useEffect, useState } from 'react' import SuitableDetailPopupButton from './SuitableDetailPopupButton' import { useSuitable } from '@/hooks/useSuitable' import { usePopupController } from '@/store/popupController' -import { useSuitableStore } from '@/store/useSuitableStore' -import { useSpinnerStore } from '@/store/spinnerStore' import { SUITABLE_HEAD_CODE, type Suitable, type SuitableDetail } from '@/types/Suitable' export default function SuitableDetailPopup() { const popupController = usePopupController() - const { toCodeName, toSuitableDetail, suitableCheckIcon, suitableCheckMemo, selectedSuitables, isSelectedSuitablesLoading } = useSuitable() - const { setSelectedItemsSearching } = useSuitableStore() + const { toCodeName, toSuitableDetail, suitableCheckIcon, suitableCheckMemo, getSelectedSuitables } = useSuitable() const [openItems, setOpenItems] = useState>(new Set()) + const [selectedSuitables, setSelectedSuitables] = useState([]) /* 아이템 열기/닫기 */ const toggleItemOpen = useCallback((itemId: number) => { @@ -25,13 +23,10 @@ export default function SuitableDetailPopup() { }) }, []) - /* 데이터 로딩 상태 처리 */ useEffect(() => { - useSpinnerStore.getState().setIsShow(isSelectedSuitablesLoading) - }, [isSelectedSuitablesLoading]) - - useEffect(() => { - setSelectedItemsSearching(true) + getSelectedSuitables().then((res) => { + setSelectedSuitables(res) + }) }, []) return ( diff --git a/src/components/suitable/SuitableList.tsx b/src/components/suitable/SuitableList.tsx index 918776a..a0dc5be 100644 --- a/src/components/suitable/SuitableList.tsx +++ b/src/components/suitable/SuitableList.tsx @@ -21,7 +21,7 @@ export default function SuitableList() { isLoading, suitableCheckIcon, } = useSuitable() - const { selectedItems, addSelectedItem, removeSelectedItem, setSelectedItemsSearching } = useSuitableStore() + const { selectedItems, addSelectedItem, removeSelectedItem } = useSuitableStore() const [openItems, setOpenItems] = useState>(new Set()) const observerTarget = useRef(null) @@ -49,7 +49,6 @@ export default function SuitableList() { /* 아이템 클릭 */ const handleItemClick = useCallback( (mainId: number, detailId?: number, detailIds?: Set): void => { - setSelectedItemsSearching(false) isItemSelected(mainId, detailId) ? removeSelectedItem(mainId, detailId) : addSelectedItem(mainId, detailId, detailIds) }, [isItemSelected, addSelectedItem, removeSelectedItem], diff --git a/src/hooks/useSuitable.ts b/src/hooks/useSuitable.ts index f525d61..ce6c638 100644 --- a/src/hooks/useSuitable.ts +++ b/src/hooks/useSuitable.ts @@ -1,5 +1,4 @@ -import { useInfiniteQuery, useQuery } from '@tanstack/react-query' -import { SHA256 } from 'crypto-js' +import { useInfiniteQuery } from '@tanstack/react-query' import { transformObjectKeys } from '@/libs/axios' import { useSuitableStore } from '@/store/useSuitableStore' import { useAxios } from './useAxios' @@ -19,7 +18,6 @@ export function useSuitable() { clearSearchKeyword, selectedItems, clearSelectedItems, - selectedItemsSearching, } = useSuitableStore() const getSuitables = async ({ @@ -137,42 +135,6 @@ export function useSuitable() { enabled: selectedCategory !== '' || searchKeyword !== '', }) - const serializeSelectedItems = (): { ids: string; detailIds: string } => { - 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 { ids: ids.join(','), detailIds: detailIds.length > 0 ? detailIds.join(',') : '' } - } - - const getSelectedItemsHash = (): string => { - const entries = Array.from(selectedItems.entries()) - .map(([key, value]) => `${key}:${Array.from(value).sort().join(',')}`) - .sort() - .join('|') - return SHA256(entries).toString() - } - - const { - data: selectedSuitables, - isLoading: isSelectedSuitablesLoading, - // refetch: refetchSelectedSuitables, - } = useQuery({ - queryKey: ['suitables', 'selectedItems', getSelectedItemsHash(), selectedItemsSearching], - queryFn: async () => { - const { ids, detailIds } = serializeSelectedItems() - return await getSuitableDetails(ids, detailIds) - }, - staleTime: Infinity, - gcTime: Infinity, - enabled: selectedItemsSearching, - refetchOnMount: false, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - }) - const clearSuitableStore = ({ items = false, category = false, keyword = false }: { items?: boolean; category?: boolean; keyword?: boolean }) => { if (items) clearSelectedItems() if (category) clearSelectedCategory() @@ -198,6 +160,21 @@ export function useSuitable() { return `${value}で設置可` } + const serializeSelectedItems = (): { ids: string; detailIds: string } => { + 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 { ids: ids.join(','), detailIds: detailIds.length > 0 ? detailIds.join(',') : '' } + } + + const getSelectedSuitables = async (): Promise => { + const { ids, detailIds } = serializeSelectedItems() + return await getSuitableDetails(ids, detailIds) + } + const downloadSuitablePdf = async (): Promise => { try { const { ids, detailIds } = serializeSelectedItems() @@ -240,7 +217,6 @@ export function useSuitable() { return { getSuitables, getSuitableIds, - getSuitableDetails, getSuitableCommCode, toCodeName, toSuitableDetail, @@ -250,8 +226,7 @@ export function useSuitable() { hasNextPage, isFetchingNextPage, isLoading, - selectedSuitables, - isSelectedSuitablesLoading, + getSelectedSuitables, clearSuitableStore, suitableCheckIcon, suitableCheckMemo, diff --git a/src/store/useSuitableStore.ts b/src/store/useSuitableStore.ts index c0b6ca9..8ecd343 100644 --- a/src/store/useSuitableStore.ts +++ b/src/store/useSuitableStore.ts @@ -35,10 +35,6 @@ interface SuitableState { removeSelectedItem: (mainId: number, detailId?: number) => void /* 선택된 아이템 모두 제거 */ clearSelectedItems: () => void - /* 선택된 아이템 검색 상태 */ - selectedItemsSearching: boolean - /* 선택된 아이템 검색 상태 설정 */ - setSelectedItemsSearching: (value: boolean) => void } export const useSuitableStore = create((set) => ({ @@ -47,7 +43,6 @@ export const useSuitableStore = create((set) => ({ selectedCategory: '' as string, searchKeyword: '' as string, selectedItems: new Map() as Map>, - selectedItemsSearching: false as boolean, /* 공통코드 설정 */ setSuitableCommCode: (headCode: string, commCode: CommCode[]) => @@ -68,7 +63,7 @@ export const useSuitableStore = create((set) => ({ /* 선택된 아이템 추가 */ addSelectedItem: (mainId: number, detailId?: number, detailIds?: Set) => { if (detailId) { - // 디테일(하위) 아이템 추가 + /* 디테일(하위) 아이템 추가 */ set((state) => { const detailSet = state.selectedItems.get(mainId) || new Set() detailSet.add(detailId) @@ -76,7 +71,7 @@ export const useSuitableStore = create((set) => ({ return { selectedItems: state.selectedItems } }) } else { - // 메인(상위) 아이템 추가 + /* 메인(상위) 아이템 추가 */ set((state) => { state.selectedItems.set(mainId, detailIds || new Set()) return { selectedItems: state.selectedItems } @@ -101,16 +96,16 @@ export const useSuitableStore = create((set) => ({ 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 } @@ -119,7 +114,4 @@ export const useSuitableStore = create((set) => ({ /* 선택된 아이템 모두 제거 */ clearSelectedItems: () => set({ selectedItems: new Map() as Map> }), - - /* 선택된 아이템 검색 상태 설정 */ - setSelectedItemsSearching: (value: boolean) => set({ selectedItemsSearching: value }), })) From 762ce60e135d685a4f1eff3d424ccd5fd45aa3a6 Mon Sep 17 00:00:00 2001 From: Daseul Kim Date: Thu, 5 Jun 2025 17:47:08 +0900 Subject: [PATCH 4/4] =?UTF-8?q?refactor:=20suitable=20store=20=EB=B3=80?= =?UTF-8?q?=EC=88=98=EB=AA=85=20=EB=B3=80=EA=B2=BD=20selectedCategory=20->?= =?UTF-8?q?=20searchCategory?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/suitable/SuitableSearch.tsx | 9 +++-- src/hooks/useSuitable.ts | 16 ++++---- src/store/useSuitableStore.ts | 46 +++++++++++----------- 3 files changed, 37 insertions(+), 34 deletions(-) diff --git a/src/components/suitable/SuitableSearch.tsx b/src/components/suitable/SuitableSearch.tsx index ccc6aed..7a62fe2 100644 --- a/src/components/suitable/SuitableSearch.tsx +++ b/src/components/suitable/SuitableSearch.tsx @@ -7,11 +7,12 @@ import type { CommCode } from '@/types/CommCode' import { SUITABLE_HEAD_CODE } from '@/types/Suitable' export default function SuitableSearch() { - const [searchValue, setSearchValue] = useState('') + const [searchValue, setSearchValue] = useState('') const { getSuitableCommCode, clearSuitableStore } = useSuitable() - const { suitableCommCode, selectedCategory, setSelectedCategory, setSearchKeyword } = useSuitableStore() + const { suitableCommCode, searchCategory, setSearchCategory, setSearchKeyword } = useSuitableStore() + /* 키워드 입력 글자 제한 */ const handleInputChange = (value: string) => { if (Array.from(value).length > 30) { alert('検索ワードは最大30文字まで入力できます。') @@ -21,6 +22,7 @@ export default function SuitableSearch() { setSearchValue(value) } + /* 키워드 검색 */ const handleInputSearch = async () => { if (!searchValue.trim()) { alert('屋根材の製品名を入力してください。') @@ -29,6 +31,7 @@ export default function SuitableSearch() { setSearchKeyword(searchValue) } + /* 키워드 초기화 */ const handleInputClear = () => { setSearchValue('') clearSuitableStore({ items: true, keyword: true }) @@ -42,7 +45,7 @@ export default function SuitableSearch() { return ( <>
- setSearchCategory(e.target.value)}> {suitableCommCode.get(SUITABLE_HEAD_CODE.ROOF_MATL_GRP_CD)?.map((category: CommCode, index: number) => (