diff --git a/.env.development b/.env.development
index cfc50e71..f35f6208 100644
--- a/.env.development
+++ b/.env.development
@@ -1,6 +1,8 @@
-NEXT_PUBLIC_API_SERVER_PATH="http://1.248.227.176:38080"
+NEXT_PUBLIC_API_SERVER_PATH="https://dev-api.hanasys.jp"
-NEXT_PUBLIC_HOST_URL="http://1.248.227.176:4000"
+NEXT_PUBLIC_HOST_URL="//1.248.227.176:4000"
+
+NEXT_PUBLIC_API_HOST_URL="http://1.248.227.176:5000"
SESSION_SECRET="i3iHH1yp2/2SpQSIySQ4bpyc4g0D+zCF9FAn5xUG0+Y="
@@ -8,4 +10,18 @@ SESSION_SECRET="i3iHH1yp2/2SpQSIySQ4bpyc4g0D+zCF9FAn5xUG0+Y="
NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_yAS4QDalL9jgQ7vS"
NEXT_PUBLIC_Q_ORDER_AUTO_LOGIN_URL="http://q-order-stg.q-cells.jp:8120/eos/login/autoLogin"
-NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL="http://q-musubi-stg.q-cells.jp:8120/qm/login/autoLogin"
\ No newline at end of file
+NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL="http://q-musubi-stg.q-cells.jp:8120/qm/login/autoLogin"
+
+# 테스트용
+# AWS_REGION="ap-northeast-2"
+# AMPLIFY_BUCKET="interplug"
+# AWS_ACCESS_KEY_ID="AKIAVWMWJCUXFHEAZ4FR"
+# AWS_SECRET_ACCESS_KEY="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"
+# NEXT_PUBLIC_AWS_S3_BASE_URL="https://interplug.s3.ap-northeast-2.amazonaws.com"
+
+# 실제 일본 서버
+AWS_REGION="ap-northeast-1"
+AMPLIFY_BUCKET="files.hanasys.jp"
+AWS_ACCESS_KEY_ID="AKIA3K4QWLZHFZRJOM2E"
+AWS_SECRET_ACCESS_KEY="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"
+NEXT_PUBLIC_AWS_S3_BASE_URL="//files.hanasys.jp"
\ No newline at end of file
diff --git a/.env.production b/.env.production
index 09bb27d3..b2d50cdd 100644
--- a/.env.production
+++ b/.env.production
@@ -1,6 +1,8 @@
NEXT_PUBLIC_API_SERVER_PATH="https://api.hanasys.jp/"
-NEXT_PUBLIC_HOST_URL="http://1.248.227.176:4000"
+NEXT_PUBLIC_HOST_URL="//1.248.227.176:4000"
+
+NEXT_PUBLIC_API_HOST_URL="https://www.hanasys.jp/"
SESSION_SECRET="i3iHH1yp2/2SpQSIySQ4bpyc4g0D+zCF9FAn5xUG0+Y="
@@ -10,4 +12,10 @@ NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secr
# NEXT_PUBLIC_Q_ORDER_AUTO_LOGIN_URL="https://q-order.q-cells.jp/eos/login/autoLogin"
# NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL="https://q-musubi.q-cells.jp/qm/login/autoLogin"
NEXT_PUBLIC_Q_ORDER_AUTO_LOGIN_URL="http://q-order-stg.q-cells.jp:8120/eos/login/autoLogin"
-NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL="http://q-musubi-stg.q-cells.jp:8120/qm/login/autoLogin"
\ No newline at end of file
+NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL="http://q-musubi-stg.q-cells.jp:8120/qm/login/autoLogin"
+
+AWS_REGION="ap-northeast-2"
+AMPLIFY_BUCKET="interplug"
+AWS_ACCESS_KEY_ID="AKIAVWMWJCUXFHEAZ4FR"
+AWS_SECRET_ACCESS_KEY="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"
+NEXT_PUBLIC_AWS_S3_BASE_URL="//files.hanasys.jp"
\ No newline at end of file
diff --git a/package.json b/package.json
index d9c1af06..bbddf9ad 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
"serve": "node server.js"
},
"dependencies": {
+ "@aws-sdk/client-s3": "^3.772.0",
"ag-grid-react": "^32.0.2",
"axios": "^1.7.8",
"big.js": "^6.2.2",
@@ -24,7 +25,7 @@
"js-cookie": "^3.0.5",
"mathjs": "^13.0.2",
"mssql": "^11.0.1",
- "next": "14.2.26",
+ "next": "14.2.28",
"next-international": "^1.2.4",
"react": "^18",
"react-chartjs-2": "^5.2.0",
@@ -38,6 +39,7 @@
"react-responsive-modal": "^6.4.2",
"react-select": "^5.8.1",
"recoil": "^0.7.7",
+ "sharp": "^0.33.5",
"sqlite": "^5.1.1",
"sqlite3": "^5.1.7",
"sweetalert2": "^11.14.1",
diff --git a/qcast3.database.sqlite b/qcast3.database.sqlite
index c0c43e6f..6a7a809a 100644
Binary files a/qcast3.database.sqlite and b/qcast3.database.sqlite differ
diff --git a/src/app/api/image/cad/route.js b/src/app/api/image/cad/route.js
new file mode 100644
index 00000000..b7d64a50
--- /dev/null
+++ b/src/app/api/image/cad/route.js
@@ -0,0 +1,69 @@
+import { NextResponse } from 'next/server'
+import { S3Client, PutObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3'
+
+const Bucket = process.env.AMPLIFY_BUCKET
+const s3 = new S3Client({
+ region: process.env.AWS_REGION,
+ credentials: {
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID,
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
+ },
+})
+
+const uploadImage = async (file) => {
+ const Body = Buffer.from(await file.arrayBuffer())
+ const Key = `cads/${file.name}`
+ const ContentType = file.ContentType
+
+ await s3.send(
+ new PutObjectCommand({
+ Bucket,
+ Key,
+ Body,
+ ContentType,
+ }),
+ )
+
+ return {
+ filePath: `https://${process.env.AMPLIFY_BUCKET}.s3.${process.env.AWS_REGION}.amazonaws.com/${Key}`,
+ fileName: Key,
+ }
+}
+
+export async function POST(req) {
+ try {
+ const formData = await req.formData()
+ const file = formData.get('file')
+
+ const result = await uploadImage(file)
+
+ return NextResponse.json(result)
+ } catch (error) {
+ console.error(error)
+ return NextResponse.json({ error: 'Failed to upload image' }, { status: 500 })
+ }
+}
+
+export async function DELETE(req) {
+ try {
+ const searchParams = req.nextUrl.searchParams
+ const Key = `cads/${searchParams.get('fileName')}`
+ console.log('🚀 ~ DELETE ~ Key:', Key)
+
+ if (!Key) {
+ return NextResponse.json({ error: 'fileName parameter is required' }, { status: 400 })
+ }
+
+ await s3.send(
+ new DeleteObjectCommand({
+ Bucket,
+ Key,
+ }),
+ )
+
+ return NextResponse.json({ message: '이미지 삭제 성공' }, { status: 200 })
+ } catch (error) {
+ console.error('S3 Delete Error:', error)
+ return NextResponse.json({ error: 'Failed to delete image' }, { status: 500 })
+ }
+}
diff --git a/src/app/api/image/canvas/route.js b/src/app/api/image/canvas/route.js
new file mode 100644
index 00000000..0a4d0be2
--- /dev/null
+++ b/src/app/api/image/canvas/route.js
@@ -0,0 +1,133 @@
+import { NextResponse } from 'next/server'
+import { S3Client, PutObjectCommand, DeleteObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3'
+import sharp from 'sharp'
+import { v4 as uuidv4 } from 'uuid'
+const Bucket = process.env.AMPLIFY_BUCKET
+const s3 = new S3Client({
+ region: process.env.AWS_REGION,
+ credentials: {
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID,
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
+ },
+})
+
+const checkArea = (obj) => {
+ const { width, height, left, top } = obj
+
+ if (left < 0 || top < 0 || width > 1600 || height > 1000) {
+ return false
+ }
+
+ return true
+}
+
+const cropImage = async (Key, width, height, left, top) => {
+ try {
+ const checkResult = checkArea({ width, height, left, top })
+
+ // Get the image from S3
+ const { Body } = await s3.send(
+ new GetObjectCommand({
+ Bucket,
+ Key,
+ }),
+ )
+
+ // Convert stream to buffer
+ const chunks = []
+ for await (const chunk of Body) {
+ chunks.push(chunk)
+ }
+ const imageBuffer = Buffer.concat(chunks)
+
+ let processedImage
+ if (!checkResult) {
+ processedImage = await sharp(imageBuffer).toBuffer()
+ } else {
+ processedImage = await sharp(imageBuffer)
+ .extract({
+ width: parseInt(width),
+ height: parseInt(height),
+ left: parseInt(left),
+ top: parseInt(top),
+ })
+ .png()
+ .toBuffer()
+ }
+ return processedImage
+ } catch (error) {
+ console.error('Error processing image:', error)
+ throw error
+ }
+}
+
+export async function POST(req) {
+ try {
+ const formData = await req.formData()
+ const file = formData.get('file')
+ const objectNo = formData.get('objectNo')
+ const planNo = formData.get('planNo')
+ const type = formData.get('type')
+ const width = formData.get('width')
+ const height = formData.get('height')
+ const left = formData.get('left')
+ const top = formData.get('top')
+
+ const OriginalKey = `Drawing/${uuidv4()}`
+
+ /**
+ * 원본 이미지를 우선 저장한다.
+ * 이미지 이름이 겹지는 현상을 방지하기 위해 uuid 를 사용한다.
+ */
+ await s3.send(
+ new PutObjectCommand({
+ Bucket,
+ Key: OriginalKey,
+ Body: Buffer.from(await file.arrayBuffer()),
+ ContentType: 'image/png',
+ }),
+ )
+
+ /**
+ * 저장된 원본 이미지를 기준으로 크롭여부를 결정하여 크롭 이미지를 저장한다.
+ */
+ const bufferImage = await cropImage(OriginalKey, width, height, left, top)
+
+ /**
+ * 크롭 이미지 이름을 결정한다.
+ */
+ const Key = `Drawing/${objectNo}_${planNo}_${type}.png`
+
+ /**
+ * 크롭이 완료된 이미지를 업로드한다.
+ */
+ await s3.send(
+ new PutObjectCommand({
+ Bucket,
+ Key,
+ Body: bufferImage,
+ ContentType: 'image/png',
+ }),
+ )
+
+ /**
+ * 크롭이미지 저장이 완료되면 원본 이미지를 삭제한다.
+ */
+ await s3.send(
+ new DeleteObjectCommand({
+ Bucket,
+ Key: OriginalKey,
+ }),
+ )
+
+ const result = {
+ filePath: `https://${process.env.AMPLIFY_BUCKET}.s3.${process.env.AWS_REGION}.amazonaws.com/${Key}`,
+ fileName: Key,
+ }
+
+ return NextResponse.json(result)
+ } catch (error) {
+ console.error('Error in POST:', error)
+ return NextResponse.json({ error: 'Failed to process and upload image' }, { status: 500 })
+ }
+}
diff --git a/src/app/api/image/map/route.js b/src/app/api/image/map/route.js
new file mode 100644
index 00000000..0cc76c02
--- /dev/null
+++ b/src/app/api/image/map/route.js
@@ -0,0 +1,78 @@
+import { NextResponse } from 'next/server'
+import { S3Client, PutObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3'
+
+const Bucket = process.env.AMPLIFY_BUCKET
+const s3 = new S3Client({
+ region: process.env.AWS_REGION,
+ credentials: {
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID,
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
+ },
+})
+
+export async function GET(req) {
+ try {
+ const searchParams = req.nextUrl.searchParams
+ const q = searchParams.get('q')
+ const fileNm = searchParams.get('fileNm')
+ const zoom = searchParams.get('zoom')
+
+ /** 구글 맵을 이미지로 변경하기 위한 API */
+ const API_KEY = 'AIzaSyDO7nVR1N_D2tKy60hgGFavpLaXkHpiHpc'
+ const targetUrl = `https://maps.googleapis.com/maps/api/staticmap?center=${q}&zoom=${zoom}&maptype=satellite&size=640x640&scale=1&key=${API_KEY}`
+ const decodeUrl = decodeURIComponent(targetUrl)
+
+ /** 구글 맵을 이미지로 변경하기 위한 API 호출 */
+ const response = await fetch(decodeUrl)
+ const data = await response.arrayBuffer()
+ // const buffer = Buffer.from(data)
+
+ /** 변경된 이미지를 S3에 업로드 */
+ const Body = Buffer.from(data)
+ const Key = `maps/${fileNm}`
+ const ContentType = 'image/png'
+
+ await s3.send(
+ new PutObjectCommand({
+ Bucket,
+ Key,
+ Body,
+ ContentType,
+ }),
+ )
+
+ const result = {
+ filePath: `https://${process.env.AMPLIFY_BUCKET}.s3.${process.env.AWS_REGION}.amazonaws.com/${Key}`,
+ fileName: Key,
+ }
+
+ return NextResponse.json(result)
+ } catch (error) {
+ console.error(error)
+ return NextResponse.json({ error: 'Failed to upload image' }, { status: 500 })
+ }
+}
+
+export async function DELETE(req) {
+ try {
+ const searchParams = req.nextUrl.searchParams
+ const Key = `maps/${searchParams.get('fileName')}`
+ console.log('🚀 ~ DELETE ~ Key:', Key)
+
+ if (!Key) {
+ return NextResponse.json({ error: 'fileName parameter is required' }, { status: 400 })
+ }
+
+ await s3.send(
+ new DeleteObjectCommand({
+ Bucket,
+ Key,
+ }),
+ )
+
+ return NextResponse.json({ message: '이미지 삭제 성공' }, { status: 200 })
+ } catch (error) {
+ console.error('S3 Delete Error:', error)
+ return NextResponse.json({ error: 'Failed to delete image' }, { status: 500 })
+ }
+}
diff --git a/src/app/api/image/upload/route.js b/src/app/api/image/upload/route.js
new file mode 100644
index 00000000..4d875257
--- /dev/null
+++ b/src/app/api/image/upload/route.js
@@ -0,0 +1,72 @@
+import { NextResponse } from 'next/server'
+import { S3Client, PutObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3'
+
+const Bucket = process.env.AMPLIFY_BUCKET
+const s3 = new S3Client({
+ region: process.env.AWS_REGION,
+ credentials: {
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID,
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
+ },
+})
+
+const uploadImage = async (file) => {
+ const Body = Buffer.from(await file.arrayBuffer())
+ const Key = `upload/${file.name}`
+ const ContentType = file.ContentType
+
+ await s3.send(
+ new PutObjectCommand({
+ Bucket,
+ Key,
+ Body,
+ ContentType,
+ }),
+ )
+
+ return {
+ filePath: `https://${process.env.AMPLIFY_BUCKET}.s3.${process.env.AWS_REGION}.amazonaws.com/${Key}`,
+ fileName: Key,
+ }
+}
+
+export async function POST(req) {
+ try {
+ const formData = await req.formData()
+ const file = formData.get('file')
+
+ const result = await uploadImage(file)
+ result.message = '이미지 업로드 성공'
+
+ return NextResponse.json(result)
+ } catch (error) {
+ console.error(error)
+ return NextResponse.json({ error: 'Failed to upload image' }, { status: 500 })
+ }
+}
+
+export async function DELETE(req) {
+ try {
+ const searchParams = req.nextUrl.searchParams
+ const fileName = searchParams.get('fileName')
+
+ if (!fileName) {
+ return NextResponse.json({ error: 'fileName parameter is required' }, { status: 400 })
+ }
+
+ const Key = `upload/${fileName}`
+ console.log('🚀 ~ DELETE ~ Key:', Key)
+
+ await s3.send(
+ new DeleteObjectCommand({
+ Bucket,
+ Key,
+ }),
+ )
+
+ return NextResponse.json({ message: '이미지 삭제 성공' }, { status: 200 })
+ } catch (error) {
+ console.error('S3 Delete Error:', error)
+ return NextResponse.json({ error: 'Failed to delete image' }, { status: 500 })
+ }
+}
diff --git a/src/components/estimate/Estimate.jsx b/src/components/estimate/Estimate.jsx
index 8ce54402..7237e056 100644
--- a/src/components/estimate/Estimate.jsx
+++ b/src/components/estimate/Estimate.jsx
@@ -60,7 +60,7 @@ export default function Estimate({}) {
const [cableItemList, setCableItemList] = useState([]) //케이블 리스트
const [cableItem, setCableItem] = useState('') //케이블 선택값
-
+ const [cableDbItem, setCableDbItem] = useState('') //케이블 선택값
const [startDate, setStartDate] = useState(new Date())
const singleDatePickerProps = {
startDate,
@@ -98,7 +98,7 @@ export default function Estimate({}) {
}
const initEstimate = (currPid = currentPid) => {
- console.log('🚀 ~ initEstimate ~ currPid:', currPid)
+ // console.log('🚀 ~ initEstimate ~ currPid:', currPid)
closeAll()
setObjectNo(objectRecoil.floorPlanObjectNo)
@@ -117,6 +117,7 @@ export default function Estimate({}) {
item.value = item.clRefChr1
item.label = item.clRefChr2
})
+ // console.log(code2)
setCableItemList(code2)
}
@@ -152,7 +153,7 @@ export default function Estimate({}) {
}
useEffect(() => {
- console.log('🚀 ~ Estimate ~ selectedPlan:', selectedPlan)
+ // console.log('🚀 ~ Estimate ~ selectedPlan:', selectedPlan)
if (selectedPlan) initEstimate(selectedPlan.planNo)
}, [selectedPlan])
@@ -739,6 +740,18 @@ export default function Estimate({}) {
setCableItem(value)
}
+ /* 케이블 select 변경시 */
+ const onChangeDisplayDoubleCableItem = (value, itemList) => {
+ itemList.map((item, index) => {
+ if (item.dispCableFlg === '1' && item.itemTpCd === 'M12') {
+ if (value !== '') {
+ onChangeDisplayItem(value, item.dispOrder, index, true)
+ }
+ }
+ })
+ setCableDbItem(value)
+ }
+
// 아이템 자동완성 검색시 아이템 추가/변경시
const onChangeDisplayItem = (itemId, dispOrder, index, flag) => {
const param = {
@@ -1088,15 +1101,20 @@ export default function Estimate({}) {
item.showSaleTotPrice = '0'
}
- if (item.dispCableFlg === '1' && item.itemTpCd !== 'M12') {
+ if (item.dispCableFlg === '1' ) {
dispCableFlgCnt++
- setCableItem(item.itemId)
+ if(item.itemTpCd === 'M12') {
+ setCableDbItem(item.itemId)
+ }else{
+ setCableItem(item.itemId)
+ }
}
}
})
if (dispCableFlgCnt === 0) {
setCableItem('100038')
+ setCableDbItem('100037')
}
let pkgAsp = estimateContextState.pkgAsp ? Number(estimateContextState.pkgAsp.replaceAll(',', '')) : 0
@@ -1159,14 +1177,20 @@ export default function Estimate({}) {
dispCableFlgCnt++
}
- if (item.dispCableFlg === '1' && item.itemTpCd !== 'M12') {
- setCableItem(item.itemId)
+ if (item.dispCableFlg === '1'){
+
+ if(item.itemTpCd === 'M12') {
+ setCableDbItem(item.itemId)
+ }else{
+ setCableItem(item.itemId)
+ }
}
}
})
if (dispCableFlgCnt === 0) {
setCableItem('100038')
+ setCableDbItem('100037')
}
totals.vatPrice = totals.supplyPrice * 0.1
@@ -1227,6 +1251,7 @@ export default function Estimate({}) {
if (dispCableFlgCnt === 0) {
setCableItem('100038')
+ setCableDbItem('100037')
}
}
}, [estimateContextState?.itemList, cableItemList])
@@ -1831,6 +1856,7 @@ export default function Estimate({}) {
+
{getMessage('estimate.detail.header.singleCable')}
+
+
+
+
{getMessage('estimate.detail.header.doubleCable')}
+
+
@@ -1927,7 +1976,7 @@ export default function Estimate({}) {
onChangeSelect(item.dispOrder)}
checked={!!selection.has(item.dispOrder)}
/>
diff --git a/src/components/floor-plan/modal/ImgLoad.jsx b/src/components/floor-plan/modal/ImgLoad.jsx
index dda395a9..f0a86938 100644
--- a/src/components/floor-plan/modal/ImgLoad.jsx
+++ b/src/components/floor-plan/modal/ImgLoad.jsx
@@ -120,7 +120,7 @@ export default function ImgLoad() {
value={refImage ? (refImage?.name ?? '') : (currentCanvasPlan?.bgImageName ?? '')}
readOnly
/>
- {refImage && }
+ {currentCanvasPlan?.bgImageName && }
diff --git a/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx b/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx
index 4a4d72a2..281ae5a8 100644
--- a/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx
+++ b/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx
@@ -86,6 +86,26 @@ export default function PassivityCircuitAllocation(props) {
.map((obj) => obj.circuitNumber),
),
]
+
+ const surfaceList = targetModules.map((module) => {
+ return canvas.getObjects().filter((obj) => obj.id === canvas.getObjects().filter((obj) => obj.id === module)[0].surfaceId)[0]
+ })
+
+ if (surfaceList.length > 1) {
+ let surfaceType = {}
+
+ surfaceList.forEach((surface) => {
+ surfaceType[`${surface.direction}-${surface.roofMaterial.pitch}`] = surface
+ })
+ if (Object.keys(surfaceType).length > 1) {
+ swalFire({
+ text: getMessage('module.circuit.fix.not.same.roof.error'),
+ type: 'alert',
+ icon: 'warning',
+ })
+ return
+ }
+ }
if (!circuitNumber || circuitNumber === 0) {
swalFire({
text: getMessage('module.circuit.minimun.error'),
diff --git a/src/hooks/common/useCanvasPopupStatusController.js b/src/hooks/common/useCanvasPopupStatusController.js
index b6499099..e7bd8457 100644
--- a/src/hooks/common/useCanvasPopupStatusController.js
+++ b/src/hooks/common/useCanvasPopupStatusController.js
@@ -37,6 +37,7 @@ export function useCanvasPopupStatusController(param = 1) {
* @returns
*/
const getModuleSelection = async (popupTypeParam) => {
+ if (!currentCanvasPlan.objectNo || !currentCanvasPlan.planNo) return
const result = await promiseGet({
url: `/api/v1/canvas-popup-status?objectNo=${currentCanvasPlan.objectNo}&planNo=${currentCanvasPlan.planNo}&popupType=${popupTypeParam}`,
})
@@ -48,7 +49,7 @@ export function useCanvasPopupStatusController(param = 1) {
return null
})
- return result.data
+ return result?.data
}
/**
@@ -59,7 +60,7 @@ export function useCanvasPopupStatusController(param = 1) {
let resultData = {}
for (let i = 1; i < 3; i++) {
const result = await getModuleSelection(i)
- if (!result.objectNo) return
+ if (!result?.objectNo) return
if (i === 1) {
if (result.popupStatus && unescapeString(result.popupStatus)) {
const data = JSON.parse(unescapeString(result.popupStatus))
diff --git a/src/hooks/common/useRefFiles.js b/src/hooks/common/useRefFiles.js
index b21cc954..d13e7213 100644
--- a/src/hooks/common/useRefFiles.js
+++ b/src/hooks/common/useRefFiles.js
@@ -1,5 +1,5 @@
import { useEffect, useRef, useState } from 'react'
-import { useRecoilState } from 'recoil'
+import { useRecoilState, useSetRecoilState } from 'recoil'
import { useSwal } from '@/hooks/useSwal'
import { useAxios } from '../useAxios'
@@ -7,6 +7,7 @@ import { currentCanvasPlanState } from '@/store/canvasAtom'
import { useCanvas } from '@/hooks/useCanvas'
import { deleteBackGroundImage, setBackGroundImage } from '@/lib/imageActions'
import { settingModalFirstOptionsState } from '@/store/settingAtom'
+import { popSpinnerState } from '@/store/popupAtom'
/**
* 배경 이미지 관리
@@ -15,6 +16,9 @@ import { settingModalFirstOptionsState } from '@/store/settingAtom'
* 이미지 -> 캔버스 배경에 이미지 로드
* 주소 -> 구글 맵에서 주소 검색 후 이미지로 다운로드 받아서 캔버스 배경에 이미지 로드
* .dwg -> api를 통해서 .png로 변환 후 캔버스 배경에 이미지 로드
+ *
+ * setCurrentBgImage 에 이미지를 세팅하면 도면에 배경으로 로딩된다.
+ * 다만 S3 Response에 aws 고유 주소가 나오는데 여기서는 cloudfront 사용을 위해서 NEXT_PUBLIC_AWS_S3_BASE_URL 도메인을 사용한다.
* @returns {object}
*/
export function useRefFiles() {
@@ -28,7 +32,8 @@ export function useRefFiles() {
const [settingModalFirstOptions, setSettingModalFirstOptions] = useRecoilState(settingModalFirstOptionsState)
const { handleBackImageLoadToCanvas } = useCanvas()
const { swalFire } = useSwal()
- const { get, post } = useAxios()
+ const { get, post, del } = useAxios()
+ const setPopSpinnerStore = useSetRecoilState(popSpinnerState)
useEffect(() => {
if (refFileMethod === '1') {
@@ -39,6 +44,7 @@ export function useRefFiles() {
}, [refFileMethod])
/**
+ * 최초 input type="file" 에 대한 이벤트
* 파일 불러오기 버튼 컨트롤
* @param {*} file
*/
@@ -59,6 +65,10 @@ export function useRefFiles() {
}
}
+ /**
+ * 허용하는 파일인지 체크한다
+ * @param {File} file
+ */
const refFileSetting = (file) => {
console.log('🚀 ~ refFileSetting ~ file:', file)
if (file.name.split('.').pop() === 'dwg') {
@@ -77,34 +87,40 @@ export function useRefFiles() {
}
/**
- * 파일 삭제
+ * 이미지 파일 삭제
*/
const handleFileDelete = async () => {
swalFire({
text: '삭제하시겠습니까?',
type: 'confirm',
confirmFn: async () => {
- setRefImage(null)
- setCurrentCanvasPlan((prev) => ({ ...prev, bgImageName: null }))
+ setPopSpinnerStore(true)
+ console.log('🚀 ~ handleFileDelete ~ handleFileDelete:', refImage)
+ console.log('🚀 ~ handleFileDelete ~ currentCanvasPlan.bgImageName:', currentCanvasPlan.bgImageName)
+ await del({ url: `${process.env.NEXT_PUBLIC_API_HOST_URL}/api/image/upload?fileName=${currentCanvasPlan.bgImageName}` })
+ setCurrentBgImage(null)
await deleteBackGroundImage({
objectId: currentCanvasPlan.id,
planNo: currentCanvasPlan.planNo,
})
+ setPopSpinnerStore(false)
},
})
}
/**
- * 주소 삭제
+ * 주소 맵 이미지 삭제
*/
const handleAddressDelete = async () => {
swalFire({
text: '삭제하시겠습니까?',
type: 'confirm',
confirmFn: async () => {
+ console.log('🚀 ~ handleAddressDelete ~ handleAddressDelete:', refImage)
+ console.log('🚀 ~ handleAddressDelete ~ currentCanvasPlan.bgImageName:', currentCanvasPlan.bgImageName)
+ await del({ url: `${process.env.NEXT_PUBLIC_API_HOST_URL}/api/image/map?fileName=${currentCanvasPlan.bgImageName}` })
setMapPositionAddress('')
setCurrentBgImage(null)
- setCurrentCanvasPlan((prev) => ({ ...prev, mapPositionAddress: null }))
await deleteBackGroundImage({
objectId: currentCanvasPlan.id,
planNo: currentCanvasPlan.planNo,
@@ -114,7 +130,7 @@ export function useRefFiles() {
}
/**
- * 주소로 구글 맵 이미지 다운로드
+ * 주소로 구글 맵 이미지 다운로드하여 캔버스 배경으로 로드
*/
const handleMapImageDown = async () => {
console.log('🚀 ~ handleMapImageDown ~ handleMapImageDown:')
@@ -133,15 +149,16 @@ export function useRefFiles() {
}))
const res = await get({
- url: `${process.env.NEXT_PUBLIC_HOST_URL}/map/convert?q=${queryRef.current.value}&fileNm=${currentCanvasPlan.id}&zoom=20`,
+ url: `${process.env.NEXT_PUBLIC_API_HOST_URL}/api/image/map?q=${queryRef.current.value}&fileNm=${currentCanvasPlan.id}&zoom=20`,
})
console.log('🚀 ~ handleMapImageDown ~ res:', res)
- setCurrentBgImage(`${process.env.NEXT_PUBLIC_HOST_URL}${res.filePath}`)
+ setCurrentBgImage(`${process.env.NEXT_PUBLIC_AWS_S3_BASE_URL}/${res.fileName}`)
await setBackGroundImage({
objectId: currentCanvasPlan.id,
planNo: currentCanvasPlan.planNo,
- imagePath: `${process.env.NEXT_PUBLIC_HOST_URL}${res.filePath}`,
+ // imagePath: `${process.env.NEXT_PUBLIC_HOST_URL}${res.filePath}`,
+ imagePath: `${res.filePath}`,
})
}
@@ -149,16 +166,26 @@ export function useRefFiles() {
* 배경 이미지 로드를 위한 세팅
*/
useEffect(() => {
- if (!currentBgImage) {
- return
- }
+ // if (!currentBgImage) {
+ // return
+ // }
console.log('🚀 ~ useEffect ~ currentBgImage:', currentBgImage)
- handleBackImageLoadToCanvas(currentBgImage)
- setCurrentCanvasPlan((prev) => ({
- ...prev,
- bgImageName: refImage?.name ?? null,
- mapPositionAddress: queryRef.current.value,
- }))
+ if (currentBgImage) {
+ handleBackImageLoadToCanvas(currentBgImage)
+ setCurrentCanvasPlan((prev) => ({
+ ...prev,
+ // bgImageName: refImage?.name ?? null,
+ bgImageName: currentBgImage.split('/').pop(),
+ mapPositionAddress: queryRef.current.value,
+ }))
+ } else {
+ setRefImage(null)
+ setCurrentCanvasPlan((prev) => ({
+ ...prev,
+ bgImageName: null,
+ mapPositionAddress: null,
+ }))
+ }
}, [currentBgImage])
/**
@@ -166,6 +193,7 @@ export function useRefFiles() {
* @param {*} file
*/
const handleUploadImageRefFile = async (file) => {
+ setPopSpinnerStore(true)
const newOption1 = settingModalFirstOptions.option1.map((option) => ({
...option,
selected: option.column === 'imageDisplay' ? true : option.selected,
@@ -180,20 +208,22 @@ export function useRefFiles() {
formData.append('file', file)
const res = await post({
- url: `${process.env.NEXT_PUBLIC_HOST_URL}/image/upload`,
+ url: `${process.env.NEXT_PUBLIC_API_HOST_URL}/api/image/upload`,
data: formData,
})
console.log('🚀 ~ handleUploadImageRefFile ~ res:', res)
- setCurrentBgImage(`${process.env.NEXT_PUBLIC_HOST_URL}${res.filePath}`)
+ setCurrentBgImage(`${process.env.NEXT_PUBLIC_AWS_S3_BASE_URL}/${res.fileName}`)
setRefImage(file)
const params = {
objectId: currentCanvasPlan.id,
planNo: currentCanvasPlan.planNo,
- imagePath: `${process.env.NEXT_PUBLIC_HOST_URL}${res.filePath}`,
+ // imagePath: `${process.env.NEXT_PUBLIC_HOST_URL}${res.filePath}`,
+ imagePath: `${res.filePath}`,
}
console.log('🚀 ~ handleUploadImageRefFile ~ params:', params)
await setBackGroundImage(params)
+ setPopSpinnerStore(false)
}
/**
@@ -204,15 +234,28 @@ export function useRefFiles() {
const formData = new FormData()
formData.append('file', file)
+ /** 캐드 도면 파일 변환 */
const res = await post({ url: converterUrl, data: formData })
console.log('🚀 ~ handleUploadConvertRefFile ~ res:', res)
+
+ /** 캐드 도면 파일 업로드 */
const result = await post({
- url: `${process.env.NEXT_PUBLIC_HOST_URL}/cad/convert`,
+ url: `${process.env.NEXT_PUBLIC_API_HOST_URL}/api/image/cad`,
data: res,
})
console.log('🚀 ~ handleUploadConvertRefFile ~ result:', result)
- setCurrentBgImage(`${process.env.NEXT_PUBLIC_HOST_URL}${result.filePath}`)
- setRefImage(res.Files[0].FileData)
+
+ setCurrentBgImage(`${process.env.NEXT_PUBLIC_AWS_S3_BASE_URL}/${res.fileName}`)
+ setRefImage(file)
+
+ const params = {
+ objectId: currentCanvasPlan.id,
+ planNo: currentCanvasPlan.planNo,
+ // imagePath: `${process.env.NEXT_PUBLIC_HOST_URL}${res.filePath}`,
+ imagePath: `${result.filePath}`,
+ }
+ console.log('🚀 ~ handleUploadImageRefFile ~ params:', params)
+ await setBackGroundImage(params)
}
/**
diff --git a/src/hooks/floorPlan/useImgLoader.js b/src/hooks/floorPlan/useImgLoader.js
index e51a4a2b..ca5fef79 100644
--- a/src/hooks/floorPlan/useImgLoader.js
+++ b/src/hooks/floorPlan/useImgLoader.js
@@ -79,7 +79,8 @@ export function useImgLoader() {
/** 이미지 크롭 요청 */
const result = await post({
- url: `${process.env.NEXT_PUBLIC_HOST_URL}/image/canvas`,
+ // url: `${process.env.NEXT_PUBLIC_API_HOST_URL}/image/canvas`,
+ url: `${process.env.NEXT_PUBLIC_API_HOST_URL}/api/image/canvas`,
data: formData,
})
console.log('🚀 ~ handleCanvasToPng ~ result:', result)
diff --git a/src/hooks/roofcover/useAuxiliaryDrawing.js b/src/hooks/roofcover/useAuxiliaryDrawing.js
index 78d0b883..c854ae77 100644
--- a/src/hooks/roofcover/useAuxiliaryDrawing.js
+++ b/src/hooks/roofcover/useAuxiliaryDrawing.js
@@ -704,6 +704,7 @@ export function useAuxiliaryDrawing(id, isUseEffect = true) {
removeLine(line1)
}
intersectionPoints.current.push(interSectionPointsWithRoofLines[0])
+ return
}
}
diff --git a/src/hooks/roofcover/useOuterLineWall.js b/src/hooks/roofcover/useOuterLineWall.js
index e0418a75..00929a6b 100644
--- a/src/hooks/roofcover/useOuterLineWall.js
+++ b/src/hooks/roofcover/useOuterLineWall.js
@@ -96,6 +96,10 @@ export function useOuterLineWall(id, propertiesId) {
}
addCanvasMouseEventListener('mouse:down', mouseDown)
+ addDocumentEventListener('contextmenu', document, (e) => {
+ handleRollback()
+ })
+
clear()
return () => {
initEvent()
@@ -682,6 +686,7 @@ export function useOuterLineWall(id, propertiesId) {
if (points.length === 0) {
return
}
+ enterCheck(e)
// 포커스가 length1에 있지 않으면 length1에 포커스를 줌
const activeElem = document.activeElement
if (activeElem !== length1Ref.current) {
@@ -746,6 +751,7 @@ export function useOuterLineWall(id, propertiesId) {
if (points.length === 0) {
return
}
+ enterCheck(e)
const key = e.key
const activeElem = document.activeElement
@@ -779,6 +785,7 @@ export function useOuterLineWall(id, propertiesId) {
if (points.length === 0) {
return
}
+ enterCheck(e)
const key = e.key
switch (key) {
case 'Down': // IE/Edge에서 사용되는 값
@@ -804,6 +811,7 @@ export function useOuterLineWall(id, propertiesId) {
if (points.length === 0) {
return
}
+ enterCheck(e)
const key = e.key
switch (key) {
case 'Enter': {
@@ -828,7 +836,7 @@ export function useOuterLineWall(id, propertiesId) {
if (points.length === 0) {
return
}
-
+ enterCheck(e)
const key = e.key
switch (key) {
case 'Down': // IE/Edge에서 사용되는 값
@@ -892,6 +900,12 @@ export function useOuterLineWall(id, propertiesId) {
isFix.current = true
}
+ const enterCheck = (e) => {
+ if (e.key === 'Enter') {
+ handleFix()
+ }
+ }
+
return {
points,
setPoints,
diff --git a/src/hooks/surface/usePlacementShapeDrawing.js b/src/hooks/surface/usePlacementShapeDrawing.js
index d53faa39..be23cd7c 100644
--- a/src/hooks/surface/usePlacementShapeDrawing.js
+++ b/src/hooks/surface/usePlacementShapeDrawing.js
@@ -92,6 +92,9 @@ export function usePlacementShapeDrawing(id) {
}
addCanvasMouseEventListener('mouse:down', mouseDown)
+ addDocumentEventListener('contextmenu', document, (e) => {
+ handleRollback()
+ })
clear()
}, [verticalHorizontalMode, points, adsorptionPointAddMode, adsorptionPointMode, adsorptionRange, interval, tempGridMode])
@@ -175,7 +178,7 @@ export function usePlacementShapeDrawing(id) {
}
}
-/*
+ /*
mouseMove
*/
const roofs = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
@@ -184,7 +187,7 @@ mouseMove
const { getAdsorptionPoints } = useAdsorptionPoint()
const mouseMove = (e) => {
- removeMouseLine();
+ removeMouseLine()
const pointer = canvas.getPointer(e.e)
const roofsPoints = roofs.map((roof) => roof.points).flat()
roofAdsorptionPoints.current = [...roofsPoints]
@@ -246,7 +249,6 @@ mouseMove
})
canvas?.add(horizontalLine, verticalLine)
canvas?.renderAll()
-
}
useEffect(() => {
@@ -768,6 +770,7 @@ mouseMove
if (points.length === 0) {
return
}
+ enterCheck(e)
// 포커스가 length1에 있지 않으면 length1에 포커스를 줌
const activeElem = document.activeElement
@@ -833,6 +836,7 @@ mouseMove
if (points.length === 0) {
return
}
+ enterCheck(e)
const key = e.key
const activeElem = document.activeElement
@@ -866,6 +870,7 @@ mouseMove
if (points.length === 0) {
return
}
+ enterCheck(e)
const key = e.key
switch (key) {
case 'Down': // IE/Edge에서 사용되는 값
@@ -891,6 +896,7 @@ mouseMove
if (points.length === 0) {
return
}
+ enterCheck(e)
const key = e.key
switch (key) {
case 'Enter': {
@@ -915,6 +921,7 @@ mouseMove
if (points.length === 0) {
return
}
+ enterCheck(e)
const key = e.key
switch (key) {
@@ -979,6 +986,12 @@ mouseMove
isFix.current = true
}
+ const enterCheck = (e) => {
+ if (e.key === 'Enter') {
+ handleFix()
+ }
+ }
+
return {
points,
setPoints,
diff --git a/src/hooks/surface/useSurfaceShapeBatch.js b/src/hooks/surface/useSurfaceShapeBatch.js
index 16f68668..863de4bf 100644
--- a/src/hooks/surface/useSurfaceShapeBatch.js
+++ b/src/hooks/surface/useSurfaceShapeBatch.js
@@ -72,16 +72,12 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
length5 = surfaceRefs.length5.current.value
}
- console.log(' before length : ', length1, length2, length3, length4, length5)
-
length1 = parseFloat(length1 === undefined ? 0 : Number(length1 / 10).toFixed(1))
length2 = parseFloat(length2 === undefined ? 0 : Number(length2 / 10).toFixed(1))
length3 = parseFloat(length3 === undefined ? 0 : Number(length3 / 10).toFixed(1))
length4 = parseFloat(length4 === undefined ? 0 : Number(length4 / 10).toFixed(1))
length5 = parseFloat(length5 === undefined ? 0 : Number(length5 / 10).toFixed(1))
- console.log(' after length : ', length1, length2, length3, length4, length5)
-
let isDrawing = true
let obj = null
let points = []
diff --git a/src/hooks/usePolygon.js b/src/hooks/usePolygon.js
index 577274ad..91562948 100644
--- a/src/hooks/usePolygon.js
+++ b/src/hooks/usePolygon.js
@@ -1127,6 +1127,9 @@ export const usePolygon = () => {
case 'left':
defense = 'north'
break
+ default:
+ defense = 'south'
+ break
}
pitch = polygon.lines[index]?.attributes?.pitch ?? 0
diff --git a/src/locales/ja.json b/src/locales/ja.json
index 35f0b757..7593b6b6 100644
--- a/src/locales/ja.json
+++ b/src/locales/ja.json
@@ -125,9 +125,9 @@
"modal.module.basic.settting.module.error6": "垂木の間隔を入力してください。\n(屋根材: {0})",
"modal.module.basic.settting.module.error7": "下在ビーチを入力してください。\n(屋根材: {0})",
"modal.module.basic.settting.module.error8": "モジュール配置領域の値を入力してください。\n(屋根材: {0})",
- "modal.module.basic.settting.module.error9": "軒側の値は{0} mm以上でなければなりません。\n(屋根材: {1})",
- "modal.module.basic.settting.module.error10": "吊下側の値は{0} mm以上でなければなりません。\n(屋根材: {1})",
- "modal.module.basic.settting.module.error11": "ケラバ側の値は{0} mm以上でなければなりません。\n(屋根材: {1})",
+ "modal.module.basic.settting.module.error9": "軒側の配置領域の値を{0} mm以上に変更してください。\n(屋根材: {1})",
+ "modal.module.basic.settting.module.error10": "棟側の配置領域の値を{0} mm以上に変更してください。\n(屋根材: {1})",
+ "modal.module.basic.settting.module.error11": "ケラバ側の配置領域の値を{0} mm以上に変更してください。\n(屋根材: {1})",
"modal.module.basic.settting.module.error12": "施工方法を選択してください。\n(屋根材: {0})",
"modal.module.basic.setting.module.placement": "モジュールの配置",
"modal.module.basic.setting.module.placement.select.fitting.type": "設置形態を選択してください。",
@@ -786,7 +786,7 @@
"stuff.search.schObjectNo": "物件番号",
"stuff.search.schSaleStoreName": "販売代理店名",
"stuff.search.schAddress": "商品アドレス",
- "stuff.search.schObjectName": "商品名",
+ "stuff.search.schObjectName": "物件名",
"stuff.search.schDispCompanyName": "見積先",
"stuff.search.schSelSaleStoreId": "販売代理店選択",
"stuff.search.schReceiveUser": "担当者",
@@ -944,6 +944,8 @@
"estimate.detail.sepcialEstimateProductInfo.pkgPrice": "PKG金額",
"estimate.detail.header.showPrice": "価格表示",
"estimate.detail.header.unitPrice": "定価",
+ "estimate.detail.header.singleCable": "片端ケーブル長さ",
+ "estimate.detail.header.doubleCable": "両端ケーブル長さ",
"estimate.detail.showPrice.pricingBtn": "Pricing",
"estimate.detail.showPrice.pricingBtn.noItemId": "Pricingが欠落しているアイテムがあります。 Pricingを進めてください。",
"estimate.detail.showPrice.pricingBtn.confirm": "価格登録初期化されますがよろしいですか?",
diff --git a/src/locales/ko.json b/src/locales/ko.json
index daf9ad60..e7ab9711 100644
--- a/src/locales/ko.json
+++ b/src/locales/ko.json
@@ -945,6 +945,8 @@
"estimate.detail.sepcialEstimateProductInfo.pkgPrice": "PKG 금액",
"estimate.detail.header.showPrice": "가격표시",
"estimate.detail.header.unitPrice": "정가",
+ "estimate.detail.header.singleCable": "편당케이블 길이",
+ "estimate.detail.header.doubleCable": "양단케이블 길이",
"estimate.detail.showPrice.pricingBtn": "Pricing",
"estimate.detail.showPrice.pricingBtn.noItemId": "Pricing이 누락된 아이템이 있습니다. 제품 선택 후 Pricing을 진행해주세요.",
"estimate.detail.showPrice.pricingBtn.confirm": "가격등록을 초기화 하시겠습니까?",
@@ -1050,6 +1052,7 @@
"module.not.found": "모듈을 선택하세요.",
"module.circuit.minimun.error": "회로번호는 1 이상입력해주세요.",
"module.already.exist.error": "회로번호가 같은 다른 파워 컨디셔너 모듈이 있습니다. 다른 회로번호를 설정하십시오.",
+ "module.circuit.fix.not.same.roof.error": "다른 지붕면의 모듈이 선택되어 있습니다. 모듈 선택을 다시 하세요.",
"construction.length.difference": "지붕면 공법을 모두 선택하십시오.",
"menu.validation.canvas.roof": "패널을 배치하려면 지붕면을 입력해야 합니다.",
"batch.object.outside.roof": "오브젝트는 지붕내에 설치해야 합니다.",