From adfe6d7a3281f476adf7f3fcf37b9e94cccacd87 Mon Sep 17 00:00:00 2001 From: Daseul Kim Date: Wed, 30 Apr 2025 16:20:36 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=A7=80=EB=B6=95=EC=9E=AC=20=EC=A0=81?= =?UTF-8?q?=ED=95=A9=EC=84=B1=20pdf=20=EA=B8=B0=EB=B3=B8=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 --- src/app/api/suitable/details/route.ts | 1 + src/components/Suitable.tsx | 3 +- src/components/SuitableDetails.tsx | 74 +++++++++++++--- src/components/SuitablePdf.tsx | 116 ++++++++++++++++++++++++++ src/components/SuitableSearch.tsx | 5 +- 5 files changed, 184 insertions(+), 15 deletions(-) create mode 100644 src/components/SuitablePdf.tsx diff --git a/src/app/api/suitable/details/route.ts b/src/app/api/suitable/details/route.ts index 2645411..d29f666 100644 --- a/src/app/api/suitable/details/route.ts +++ b/src/app/api/suitable/details/route.ts @@ -1,4 +1,5 @@ import { NextResponse } from 'next/server' +import { prisma } from '@/libs/prisma' export async function GET(request: Request) { const { searchParams } = new URL(request.url) diff --git a/src/components/Suitable.tsx b/src/components/Suitable.tsx index eb725a2..e21b4d4 100644 --- a/src/components/Suitable.tsx +++ b/src/components/Suitable.tsx @@ -9,6 +9,7 @@ export default function Suitable() { const router = useRouter() const queryClient = useQueryClient() const { selectedItems, addSelectedItem, removeSelectedItem } = useSuitableStore() + const handleItemClick = (item: SuitableType) => { if (!item.id) return // 초기 데이터 import 때문에 Suitable.id가 optional 타입이라서 방어 처리 추가 selectedItems.some((selected) => selected.id === item.id) ? removeSelectedItem(item.id) : addSelectedItem(item) @@ -23,7 +24,7 @@ export default function Suitable() { enabled: false, }) - if (isLoading || !suitableList) { + if (isLoading || !suitableList || suitableList.length === 0) { return
Loading...
} diff --git a/src/components/SuitableDetails.tsx b/src/components/SuitableDetails.tsx index 795fcdc..748c28f 100644 --- a/src/components/SuitableDetails.tsx +++ b/src/components/SuitableDetails.tsx @@ -1,23 +1,71 @@ 'use client' -import { Suitable, suitableApi } from '@/api/suitable' -import { useQuery, useQueryClient } from '@tanstack/react-query' +import { suitableApi } from '@/api/suitable' +import { useQuery } from '@tanstack/react-query' +import generatePDF, { usePDF, Options } from 'react-to-pdf' +import PDFContent from './SuitablePdf' + +const pdfOptions: Options = { + filename: 'page.pdf', + method: 'save', + page: { format: 'a4', margin: 10 }, + overrides: { + pdf: { + compress: true, + }, + }, +} export default function SuitableDetails({ roofMaterial }: { roofMaterial: string }) { - console.log('🚀 ~ SuitableDetails ~ roofMaterial:', roofMaterial) - // const { data, error, isPending } = useQuery({ - // queryKey: ['suitable-details'], - // queryFn: () => suitableApi.getDetails(roofMaterial), - // staleTime: 0, - // }) - const cache = useQueryClient() - const listData = cache.getQueryData(['suitable-list']) as Suitable[] - const data = listData.filter((item) => item.roof_material === roofMaterial) + const { data, isLoading } = useQuery({ + queryKey: ['suitable-details'], + queryFn: () => suitableApi.getDetails(roofMaterial), + staleTime: 0, + enabled: !!roofMaterial, + }) + + const { toPDF, targetRef } = usePDF({ ...pdfOptions, method: 'open' }) + + if (isLoading) { + return
Loading...
+ } return ( <> -

Searched Roof Material: {roofMaterial}

- {data && data.map((item) =>
{item.product_name}
)} + + + {/* */} + {/*
*/} +
+

Searched Roof Material: {decodeURIComponent(roofMaterial as string)}

+ {data && + data.map((item) => ( +
+
+
product_name: {item.product_name}
+
manufacturer: {item.manufacturer}
+
roof_material: {item.roof_material}
+
shape: {item.shape}
+
support_roof_tile: {item.support_roof_tile}
+
support_roof_bracket: {item.support_roof_bracket}
+
yg_anchor: {item.yg_anchor}
+
+ ))} +
+ {/*
*/} +
+
+

PDF 내용 나와라

+ +
+
) } diff --git a/src/components/SuitablePdf.tsx b/src/components/SuitablePdf.tsx new file mode 100644 index 0000000..f059744 --- /dev/null +++ b/src/components/SuitablePdf.tsx @@ -0,0 +1,116 @@ +import { Suitable } from '@/api/suitable' + +interface StatusInfo { + statusIcon: string + statusText: string +} + +interface FittingItem { + name: string + value: string + memo: string +} + +function getStatusInfo(value: string): StatusInfo | null { + const val = value?.trim()?.replace(/ー|-/g, '-') || '' + + if (['○', '〇'].includes(val)) { + return { + statusIcon: '✅', + statusText: '設置可', + } + } + + if (['×', '✕'].includes(val)) { + return { + statusIcon: '❌', + statusText: '設置不可', + } + } + + if (['-', '-', 'ー'].includes(val)) { + return { + statusIcon: '❓', + statusText: 'お問い合わせください', + } + } + + if (val === '') return null + + return { + statusIcon: '✅', + statusText: `${val} で設置可`, + } +} + +function getFittingItems(item: Suitable): FittingItem[] { + return [ + { name: '屋根瓦用支持金具', value: item.support_roof_tile, memo: item.support_roof_tile_memo }, + { name: '屋根ブラケット用支持金具', value: item.support_roof_bracket, memo: item.support_roof_bracket_memo }, + { name: 'YGアンカー', value: item.yg_anchor, memo: item.yg_anchor_memo }, + { name: 'RG屋根瓦パーツ', value: item.rg_roof_tile_part, memo: item.rg_roof_tile_part_memo }, + { name: 'DIDOハント支持瓦2', value: item.dido_hunt_support_tile_2, memo: item.dido_hunt_support_tile_2_memo }, + { name: '高島パワーベース', value: item.takashima_power_base, memo: item.takashima_power_base_memo }, + { name: '高島瓦ブラケット', value: item.takashima_tile_bracket, memo: item.takashima_tile_bracket_memo }, + { name: 'スレートブラケット4', value: item.slate_bracket_4, memo: item.slate_bracket_4_memo }, + { name: 'スレートシングルメタルブラケット', value: item.slate_single_metal_bracket, memo: item.slate_single_metal_bracket_memo }, + { name: 'DIDOハントショートラック4', value: item.dido_hunt_short_rack_4, memo: item.dido_hunt_short_rack_4_memo }, + { + name: '高島スレートブラケットスレートシングル', + value: item.takashima_slate_bracket_slate_single, + memo: item.takashima_slate_bracket_slate_single_memo, + }, + { name: 'DFメタルブラケット', value: item.df_metal_bracket, memo: item.df_metal_bracket_memo }, + { name: 'スレートメタルブラケット', value: item.slate_metal_bracket, memo: item.slate_metal_bracket_memo }, + { name: '高島スレートブラケットメタル屋根', value: item.takashima_slate_bracket_metal_roof, memo: item.takashima_slate_bracket_metal_roof_memo }, + ] +} + +function FittingItem({ fitting }: { fitting: FittingItem }) { + const statusInfo = getStatusInfo(fitting.value) + if (!statusInfo) return null + + return ( +
  • + {statusInfo.statusIcon} + + {fitting.name}:{statusInfo.statusText} + + {fitting.memo && ( + + 備考: + {fitting.memo} + + )} +
  • + ) +} + +export default function PDFContent({ data }: { data: Suitable[] }) { + return ( +
    + +

    適合結果 (적합결과)

    + {data.map((item) => ( +
    +
    +

    + {item.product_name}({item.manufacturer} / {item.roof_material} / {item.shape}) +

    +
      + {getFittingItems(item).map((fitting, index) => ( + + ))} +
    +
    + ))} +
    + ) +} diff --git a/src/components/SuitableSearch.tsx b/src/components/SuitableSearch.tsx index e339f80..4e3aa13 100644 --- a/src/components/SuitableSearch.tsx +++ b/src/components/SuitableSearch.tsx @@ -4,6 +4,7 @@ import { useEffect, useState } from 'react' import { useRouter } from 'next/navigation' import { useQuery } from '@tanstack/react-query' import { useSuitable } from '@/hooks/useSuitable' +import Link from 'next/link' export default function SuitableSearch() { const router = useRouter() @@ -74,7 +75,9 @@ export default function SuitableSearch() { ))} - + + 지붕재 종류 상세보기 페이지 이동 +
    setSearchValue(e.target.value)} />