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/.gitmessage.txt b/.gitmessage.txt new file mode 100644 index 00000000..07932f22 --- /dev/null +++ b/.gitmessage.txt @@ -0,0 +1,3 @@ +[일감번호] : [제목] + +[작업내용] : \ 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/common/common.js b/src/common/common.js index abda5acd..c98cf385 100644 --- a/src/common/common.js +++ b/src/common/common.js @@ -125,6 +125,11 @@ export const TRESTLE_MATERIAL = { BRACKET: 'bracket', } +export const MODULE_SETUP_TYPE = { + LAYOUT: 'layout', + AUTO: 'auto', +} + export const SAVE_KEY = [ 'selectable', 'name', 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/CanvasLayout.jsx b/src/components/floor-plan/CanvasLayout.jsx index 059e96aa..cce79df2 100644 --- a/src/components/floor-plan/CanvasLayout.jsx +++ b/src/components/floor-plan/CanvasLayout.jsx @@ -37,7 +37,7 @@ export default function CanvasLayout({ children }) { } + {currentCanvasPlan?.bgImageName && } diff --git a/src/components/floor-plan/modal/basic/BasicSetting.jsx b/src/components/floor-plan/modal/basic/BasicSetting.jsx index 9a09aa1e..a70b9a22 100644 --- a/src/components/floor-plan/modal/basic/BasicSetting.jsx +++ b/src/components/floor-plan/modal/basic/BasicSetting.jsx @@ -1,4 +1,4 @@ -import { POLYGON_TYPE } from '@/common/common' +import { POLYGON_TYPE, MODULE_SETUP_TYPE } from '@/common/common' import WithDraggable from '@/components/common/draggable/WithDraggable' import { Orientation } from '@/components/floor-plan/modal/basic/step/Orientation' import PitchPlacement from '@/components/floor-plan/modal/basic/step/pitch/PitchPlacement' @@ -331,10 +331,13 @@ export default function BasicSetting({ id, pos = { x: 50, y: 230 } }) { + - diff --git a/src/components/floor-plan/modal/basic/step/Placement.jsx b/src/components/floor-plan/modal/basic/step/Placement.jsx index 909f5229..ffb96d83 100644 --- a/src/components/floor-plan/modal/basic/step/Placement.jsx +++ b/src/components/floor-plan/modal/basic/step/Placement.jsx @@ -37,7 +37,7 @@ const Placement = forwardRef((props, refs) => { const resetModuleSetupOption = useResetRecoilState(moduleSetupOptionState) const [colspan, setColspan] = useState(1) - const [moduleRowColArray, setModuleRowColArray] = useRecoilState(moduleRowColArrayState) + const moduleRowColArray = useRecoilValue(moduleRowColArrayState) //모듈 배치면 생성 useEffect(() => { @@ -64,9 +64,9 @@ const Placement = forwardRef((props, refs) => { } }, []) - useEffect(() => { - console.log('moduleRowColArray', moduleRowColArray) - }, [moduleRowColArray]) + // useEffect(() => { + // console.log('moduleRowColArray', moduleRowColArray) + // }, [moduleRowColArray]) //최초 지입시 체크 useEffect(() => { @@ -180,8 +180,8 @@ const Placement = forwardRef((props, refs) => { ))} - {selectedModules.itemList && - selectedModules.itemList.map((item, index) => ( + {selectedModules?.itemList && + selectedModules?.itemList?.map((item, index) => (
@@ -329,22 +329,25 @@ const Placement = forwardRef((props, refs) => { {selectedModules && - selectedModules.itemList.map((item) => ( - + selectedModules.itemList?.map((item) => ( + // +
{item.itemNm}
))} + {colspan > 1 && {getMessage('modal.module.basic.setting.module.placement.max.rows.multiple')}} - {selectedModules.itemList.map((item) => ( - <> - {getMessage('modal.module.basic.setting.module.placement.max.row')} - {colspan > 1 && {getMessage('modal.module.basic.setting.module.placement.max.rows.multiple')}} - - ))} + {selectedModules && + selectedModules.itemList?.map((item) => ( + <> + {getMessage('modal.module.basic.setting.module.placement.max.row')} + {/* {colspan > 1 && {getMessage('modal.module.basic.setting.module.placement.max.rows.multiple')}} */} + + ))} @@ -356,10 +359,11 @@ const Placement = forwardRef((props, refs) => { {item.addRoof?.roofMatlNmJp}
- {moduleRowColArray[index]?.map((item) => ( + {moduleRowColArray[index]?.map((item, index2) => ( <> {item.moduleMaxRows} - {colspan > 1 && {item.mixModuleMaxRows}} + {/* {colspan > 1 && {item.mixModuleMaxRows}} */} + {colspan > 1 && index2 === moduleRowColArray[index].length - 1 && {item.maxRow}} ))} diff --git a/src/components/header/Header.jsx b/src/components/header/Header.jsx index 7ce753df..3a798011 100644 --- a/src/components/header/Header.jsx +++ b/src/components/header/Header.jsx @@ -20,6 +20,8 @@ import { stuffSearchState } from '@/store/stuffAtom' import { QcastContext } from '@/app/QcastProvider' import { usePopup } from '@/hooks/usePopup' +import { commonCodeState } from '@/store/commonCodeAtom' +import { isObjectNotEmpty } from '@/util/common-utils' export const ToggleonMouse = (e, act, target) => { const listWrap = e.target.closest(target) @@ -67,6 +69,21 @@ export default function Header(props) { const [SelectOptions, setSelectOptions] = useState([]) + const [commonCode, setCommonCode] = useRecoilState(commonCodeState) + const { promiseGet } = useAxios() + + useEffect(() => { + const getCommonCode = async () => { + await promiseGet({ url: '/api/commcode/qc-comm-code' }).then((res) => { + setCommonCode(Object.groupBy(res.data, ({ clHeadCd }) => clHeadCd)) + }) + } + + if (!isObjectNotEmpty(commonCode)) { + getCommonCode() + } + }, []) + const getAutoLoginParam = async () => { await promisePost({ url: '/api/login/v1.0/user/login/autoLoginEncryptData', data: { loginId: userSession.userId } }) .then((res) => { 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/useCommonCode.js b/src/hooks/common/useCommonCode.js index e85cbb64..7af6b764 100644 --- a/src/hooks/common/useCommonCode.js +++ b/src/hooks/common/useCommonCode.js @@ -22,9 +22,8 @@ import { useAxios } from '../useAxios' * const honorificCodes = findCommonCode(200800); */ export const useCommonCode = () => { - const [commonCode, setCommonCode] = useRecoilState(commonCodeState) const globalLocale = useRecoilValue(globalLocaleStore) - const { promiseGet } = useAxios() + const [commonCode, setCommonCode] = useRecoilState(commonCodeState) const findCommonCode = (key) => { const resultCodes = commonCode[key]?.map((code) => { @@ -41,18 +40,6 @@ export const useCommonCode = () => { return resultCodes } - useEffect(() => { - const getCommonCode = async () => { - await promiseGet({ url: '/api/commcode/qc-comm-code' }).then((res) => { - setCommonCode(Object.groupBy(res.data, ({ clHeadCd }) => clHeadCd)) - }) - } - - if (!isObjectNotEmpty(commonCode)) { - getCommonCode() - } - }, []) - return { commonCode, findCommonCode, diff --git a/src/hooks/common/useMasterController.js b/src/hooks/common/useMasterController.js index 60aab800..21220726 100644 --- a/src/hooks/common/useMasterController.js +++ b/src/hooks/common/useMasterController.js @@ -18,7 +18,7 @@ export function useMasterController() { */ const getRoofMaterialList = async () => { return await get({ url: '/api/v1/master/getRoofMaterialList' }).then((res) => { - console.log('🚀🚀 ~ getRoofMaterialList ~ res:', res) + // console.log('🚀🚀 ~ getRoofMaterialList ~ res:', res) return res }) } 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/module/useModuleBasicSetting.js b/src/hooks/module/useModuleBasicSetting.js index 1871e34a..a3f2eb0e 100644 --- a/src/hooks/module/useModuleBasicSetting.js +++ b/src/hooks/module/useModuleBasicSetting.js @@ -19,7 +19,7 @@ import offsetPolygon, { calculateAngle, createLinesFromPolygon } from '@/util/qp import { QPolygon } from '@/components/fabric/QPolygon' import { moduleSetupSurfaceState } from '@/store/canvasAtom' import { useEvent } from '@/hooks/useEvent' -import { POLYGON_TYPE, BATCH_TYPE, LINE_TYPE } from '@/common/common' +import { POLYGON_TYPE, BATCH_TYPE, LINE_TYPE, MODULE_SETUP_TYPE } from '@/common/common' import * as turf from '@turf/turf' import { useSwal } from '@/hooks/useSwal' import { compasDegAtom } from '@/store/orientationAtom' @@ -49,20 +49,17 @@ export function useModuleBasicSetting(tabNum) { const [isManualModuleLayoutSetup, setIsManualModuleLayoutSetup] = useRecoilState(isManualModuleLayoutSetupState) const canvasSetting = useRecoilValue(canvasSettingState) const moduleSelectionData = useRecoilValue(moduleSelectionDataState) - const [trestleDetailParams, setTrestleDetailParams] = useState([]) const [trestleDetailList, setTrestleDetailList] = useState([]) const selectedModules = useRecoilValue(selectedModuleState) - const { getTrestleDetailList } = useMasterController() const [saleStoreNorthFlg, setSaleStoreNorthFlg] = useState(false) const setCurrentObject = useSetRecoilState(currentObjectState) const { setModuleStatisticsData } = useCircuitTrestle() const { createRoofPolygon, createMarginPolygon, createPaddingPolygon } = useMode() - const { drawDirectionArrow } = usePolygon() const moduleSetupOption = useRecoilValue(moduleSetupOptionState) const setManualSetupMode = useSetRecoilState(toggleManualSetupModeState) - const [moduleRowColArray, setModuleRowColArray] = useRecoilState(moduleRowColArrayState) + const setModuleRowColArray = useSetRecoilState(moduleRowColArrayState) useEffect(() => { return () => { @@ -92,12 +89,12 @@ export function useModuleBasicSetting(tabNum) { //roofIndex 넣기 const roofConstructionArray = roofConstructions.map((detail) => ({ ...detail.trestleDetail, roofIndex: detail.roofIndex })) - setTrestleDetailList(roofConstructionArray) - //북면 설치 가능 판매점 - if (moduleSelectionData.common.saleStoreNorthFlg === '1') { + if (moduleSelectionData.common.saleStoreNorthFlg == '1') { setSaleStoreNorthFlg(true) } + + setTrestleDetailList(roofConstructionArray) } } else { //육지붕 일경우에는 바로 배치면 설치LL @@ -149,7 +146,7 @@ export function useModuleBasicSetting(tabNum) { const moduleRowArray = [] if (isObjectNotEmpty(detail) && detail.module.length > 0) { detail.module.forEach((module) => { - moduleRowArray.push({ moduleMaxRows: module.moduleMaxRows, mixModuleMaxRows: module.mixModuleMaxRows }) + moduleRowArray.push({ moduleMaxRows: module.moduleMaxRows, mixModuleMaxRows: module.mixModuleMaxRows, maxRow: detail.moduleMaxRows }) }) } rowColArray.push(moduleRowArray) @@ -236,8 +233,26 @@ export function useModuleBasicSetting(tabNum) { } }) + let isNorth = false + const isExistSurface = canvas.getObjects().find((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE && obj.parentId === roof.id) + if (isExistSurface) { + if (canvasSetting.roofSizeSet != '3') { + //북면이 있지만 + if (roof.directionText && roof.directionText.indexOf('北') > -1) { + //북쪽일때 해당 서북서, 동북동은 제외한다고 한다 + if (!(roof.directionText.indexOf('西北西') > -1 || roof.directionText.indexOf('東北東') > -1)) { + isNorth = true + } + } + + isExistSurface.set({ + isNorth: isNorth, //북면여부 + isSaleStoreNorthFlg: moduleSelectionData.common.saleStoreNorthFlg == '1' ? true : false, //북면설치가능점 여부 + }) + } + addTargetMouseEventListener('mousedown', isExistSurface, function () { toggleSelection(isExistSurface) }) @@ -270,7 +285,6 @@ export function useModuleBasicSetting(tabNum) { //모듈설치영역?? 생성 const surfaceId = uuidv4() - let isNorth = false if (canvasSetting.roofSizeSet != '3') { //북면이 있지만 @@ -307,6 +321,7 @@ export function useModuleBasicSetting(tabNum) { trestleDetail: trestleDetail, isNorth: isNorth, perPixelTargetFind: true, + isSaleStoreNorthFlg: moduleSelectionData.common.saleStoreNorthFlg == '1' ? true : false, //북면설치가능점 여부 // angle: -compasDeg, }) @@ -352,8 +367,11 @@ export function useModuleBasicSetting(tabNum) { const isExist = selectedModuleInstSurfaceArray.some((obj) => obj.parentId === setupSurface.parentId) //최초 선택일때 if (!isExist) { - //설치면이 북면이고 북면설치 허용점이 아니면 - if (setupSurface.isNorth && !saleStoreNorthFlg) { + //모듈에 북면 설치 가능 모듈이 있는지 확인함 + const isNorthModuleYn = moduleSelectionData?.module.itemList.some((module) => module.northModuleYn === 'Y') + + //설치면이 북면이고 북면설치 허용점이 아니면 북면 모듈이 한개도 없으면 + if (setupSurface.isNorth && !setupSurface.isSaleStoreNorthFlg && !isNorthModuleYn) { swalFire({ text: getMessage('module.not.batch.north'), icon: 'warning' }) return } @@ -431,14 +449,14 @@ export function useModuleBasicSetting(tabNum) { } if (checkedModule.length === 0) { - swalFire({ text: getMessage('module.place.select.module') }) + swalFire({ text: getMessage('module.place.select.module'), icon: 'warning' }) setIsManualModuleSetup(false) setManualSetupMode(`manualSetup_false`) return } if (checkedModule.length > 1) { - swalFire({ text: getMessage('module.place.select.one.module') }) + swalFire({ text: getMessage('module.place.select.one.module'), icon: 'warning' }) setIsManualModuleSetup(false) setManualSetupMode(`manualSetup_false`) return @@ -538,9 +556,15 @@ export function useModuleBasicSetting(tabNum) { parentId: moduleSetupSurfaces[i].parentId, }) + const northModuleYn = checkedModule.some((module) => module.northModuleYn === 'Y') //북면이고 북면설치상점이 아니면 그냥 return - if (trestlePolygon.isNorth && !saleStoreNorthFlg) { - return + if (trestlePolygon.isNorth && !trestlePolygon.isSaleStoreNorthFlg) { + if (!northModuleYn) { + //북면이고 설치 가능 상점이 아닌데 북면 설치 모듈이 있으면 + return + } else { + canvas?.add(tempModule) //움직여가면서 추가됨 + } } else { canvas?.add(tempModule) //움직여가면서 추가됨 } @@ -765,7 +789,7 @@ export function useModuleBasicSetting(tabNum) { const mixAsgYn = trestlePolygon.modules[0].moduleInfo.mixAsgYn //현재 체크된 모듈기준으로 혼합가능인지 확인 Y === Y, N === N 일때만 설치 가능 if (checkedModule[0].mixAsgYn !== mixAsgYn) { - swalFire({ text: getMessage('modal.module.basic.setting.module.placement.mix.asg.yn.error') }) + swalFire({ text: getMessage('modal.module.basic.setting.module.placement.mix.asg.yn.error'), icon: 'warning' }) return } } @@ -798,7 +822,7 @@ export function useModuleBasicSetting(tabNum) { const intersection = turf.intersect(turf.featureCollection([dormerTurfPolygon, tempTurfModule])) //겹치는지 확인 //겹치면 안됨 if (intersection) { - swalFire({ text: getMessage('module.place.overobject') }) + swalFire({ text: getMessage('module.place.overobject'), icon: 'warning' }) isIntersection = false } }) @@ -841,10 +865,10 @@ export function useModuleBasicSetting(tabNum) { // getModuleStatistics() } else { - swalFire({ text: getMessage('module.place.overlab') }) + swalFire({ text: getMessage('module.place.overlab'), icon: 'warning' }) } } else { - swalFire({ text: getMessage('module.place.out') }) + swalFire({ text: getMessage('module.place.out'), icon: 'warning' }) } } }) @@ -876,7 +900,7 @@ export function useModuleBasicSetting(tabNum) { } if (checkedModule.length === 0) { - swalFire({ text: getMessage('module.place.select.module') }) + swalFire({ text: getMessage('module.place.select.module'), icon: 'warning' }) setIsManualModuleLayoutSetup(false) setManualSetupMode(`manualLayoutSetup_false`) return @@ -890,7 +914,7 @@ export function useModuleBasicSetting(tabNum) { ) if (hasZeroLength) { - swalFire({ text: getMessage('module.layout.setup.has.zero.value') }) + swalFire({ text: getMessage('module.layout.setup.has.zero.value'), icon: 'warning' }) setIsManualModuleLayoutSetup(false) setManualSetupMode(`manualLayoutSetup_false`) return @@ -902,7 +926,7 @@ export function useModuleBasicSetting(tabNum) { //Y인 모듈과 N인 모듈이 둘다 존재하면 설치 불가 if (mixAsgY.length > 0 && mixAsgN.length > 0) { - swalFire({ text: getMessage('modal.module.basic.setting.module.placement.mix.asg.yn.error') }) + swalFire({ text: getMessage('modal.module.basic.setting.module.placement.mix.asg.yn.error'), icon: 'warning' }) return } @@ -1017,26 +1041,6 @@ export function useModuleBasicSetting(tabNum) { }, ] - //아래래 - // let points = [ - // { - // x: Number(mousePoint.x.toFixed(1)) - calcHalfWidth, - // y: Number(mousePoint.y.toFixed(1)) - calcHalfHeight, - // }, - // { - // x: Number(mousePoint.x.toFixed(1)) - calcHalfWidth, - // y: Number(mousePoint.y.toFixed(1)) + calcHalfHeight, - // }, - // { - // x: Number(mousePoint.x.toFixed(1)) + calcHalfWidth, - // y: Number(mousePoint.y.toFixed(1)) + calcHalfHeight, - // }, - // { - // x: Number(mousePoint.x.toFixed(1)) + calcHalfWidth, - // y: Number(mousePoint.y.toFixed(1)) - calcHalfHeight, - // }, - // ] - const turfPoints = coordToTurfPolygon(points) if (turf.booleanWithin(turfPoints, turfPolygon)) { @@ -1068,9 +1072,15 @@ export function useModuleBasicSetting(tabNum) { parentId: moduleSetupSurfaces[i].parentId, }) + const northModuleYn = checkedModule.some((module) => module.northModuleYn === 'Y') //북면이고 북면설치상점이 아니면 그냥 return - if (trestlePolygon.isNorth && !saleStoreNorthFlg) { - return + if (trestlePolygon.isNorth && !trestlePolygon.isSaleStoreNorthFlg) { + if (!northModuleYn) { + //북면이고 설치 가능 상점이 아닌데 북면 설치 모듈이 있으면 + return + } else { + canvas?.add(tempModule) //움직여가면서 추가됨 + } } else { canvas?.add(tempModule) //움직여가면서 추가됨 } @@ -1256,7 +1266,7 @@ export function useModuleBasicSetting(tabNum) { const mixAsgYn = trestlePolygon.modules[0].moduleInfo.mixAsgYn //현재 체크된 모듈기준으로 혼합가능인지 확인 Y === Y, N === N 일때만 설치 가능 if (checkedModule[0].mixAsgYn !== mixAsgYn) { - swalFire({ text: getMessage('modal.module.basic.setting.module.placement.mix.asg.yn.error') }) + swalFire({ text: getMessage('modal.module.basic.setting.module.placement.mix.asg.yn.error'), icon: 'warning' }) return } } @@ -1415,7 +1425,7 @@ export function useModuleBasicSetting(tabNum) { // canvas.renderAll() } } else { - swalFire({ text: getMessage('module.place.overlab') }) + swalFire({ text: getMessage('module.place.overlab'), icon: 'warning' }) return } } @@ -1510,7 +1520,7 @@ export function useModuleBasicSetting(tabNum) { // canvas.renderAll() } } else { - swalFire({ text: getMessage('module.place.overlab') }) + swalFire({ text: getMessage('module.place.overlab'), icon: 'warning' }) return } } @@ -1603,7 +1613,7 @@ export function useModuleBasicSetting(tabNum) { // canvas.renderAll() } } else { - swalFire({ text: getMessage('module.place.overlab') }) + swalFire({ text: getMessage('module.place.overlab'), icon: 'warning' }) return } } @@ -1695,7 +1705,7 @@ export function useModuleBasicSetting(tabNum) { // canvas.renderAll() } } else { - swalFire({ text: getMessage('module.place.overlab') }) + swalFire({ text: getMessage('module.place.overlab'), icon: 'warning' }) return } } @@ -1732,21 +1742,39 @@ export function useModuleBasicSetting(tabNum) { } //자동 모듈 설치(그리드 방식) - const autoModuleSetup = (placementRef) => { + const autoModuleSetup = (type, layoutSetupRef) => { initEvent() //마우스 이벤트 초기화 - if (checkedModule.length === 0) { - swalFire({ text: getMessage('module.place.select.module') }) - return + //실패한 지붕재 배열 + let failAutoSetupRoof = [] + + let checkedLayoutData + + /** + * 자동 레이아웃일때 0이 있거나 혼합이 있는지 확인하는 로직 + */ + if (type === MODULE_SETUP_TYPE.LAYOUT) { + checkedLayoutData = layoutSetupRef.filter((module) => module.checked) + const hasZeroLength = checkedLayoutData.some((module) => module.row === 0 || module.col === 0) + + if (hasZeroLength) { + swalFire({ text: getMessage('module.layout.setup.has.zero.value'), icon: 'warning' }) + return + } + + // //혼합 가능 모듈과 혼합 불가능 모듈을 선택했을때 카운트를 해서 확인 + // const mixAsgY = checkedModule.filter((obj) => obj.mixAsgYn === 'Y') + // const mixAsgN = checkedModule.filter((obj) => obj.mixAsgYn === 'N') + + // //Y인 모듈과 N인 모듈이 둘다 존재하면 설치 불가 + // if (mixAsgY.length > 0 && mixAsgN.length > 0) { + // swalFire({ text: getMessage('modal.module.basic.setting.module.placement.mix.asg.yn.error'), icon: 'warning' }) + // return + // } } - //혼합 가능 모듈과 혼합 불가능 모듈을 선택했을때 카운트를 해서 확인 - const mixAsgY = checkedModule.filter((obj) => obj.mixAsgYn === 'Y') - const mixAsgN = checkedModule.filter((obj) => obj.mixAsgYn === 'N') - - //Y인 모듈과 N인 모듈이 둘다 존재하면 설치 불가 - if (mixAsgY.length > 0 && mixAsgN.length > 0) { - swalFire({ text: getMessage('modal.module.basic.setting.module.placement.mix.asg.yn.error') }) + if (checkedModule.length === 0) { + swalFire({ text: getMessage('module.place.select.module'), icon: 'warning' }) return } @@ -1762,26 +1790,40 @@ export function useModuleBasicSetting(tabNum) { const batchObjects = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.OBJECT_SURFACE) //도머s 객체 if (moduleSetupSurfaces.length === 0) { - swalFire({ text: getMessage('module.place.no.surface') }) + swalFire({ text: getMessage('module.place.no.surface'), icon: 'warning' }) return } //어짜피 자동으로 누르면 선택안된데도 다 날아간다 - canvas.getObjects().forEach((obj) => { - if (obj.name === POLYGON_TYPE.MODULE) { - canvas.remove(obj) - } - }) + // canvas.getObjects().forEach((obj) => { + // if (obj.name === POLYGON_TYPE.MODULE) { + // canvas.remove(obj) + // } + // }) - notSelectedTrestlePolygons.forEach((obj) => { + //자동일때만 선택 안된 모듈 삭제 + moduleSetupSurfaces.forEach((obj) => { if (obj.modules) { obj.modules.forEach((module) => { canvas?.remove(module) + canvas?.renderAll() }) obj.modules = [] } }) + if (type === MODULE_SETUP_TYPE.AUTO) { + notSelectedTrestlePolygons.forEach((obj) => { + if (obj.modules) { + obj.modules.forEach((module) => { + canvas?.remove(module) + canvas?.renderAll() + }) + obj.modules = [] + } + }) + } + let moduleOptions = { stroke: 'black', strokeWidth: 0.3, @@ -1820,17 +1862,123 @@ export function useModuleBasicSetting(tabNum) { return turf.booleanContains(turfModuleSetupSurface, squarePolygon) || turf.booleanWithin(squarePolygon, turfModuleSetupSurface) } + /** + * 자동 레이아웃 설치 일시 row col 초과 여부 확인 + * 체크된 모듈중에 북면 모듈이 있으면 북면 모듈만 따로 계산하고 아니면 전체 모듈을 계산 + * 북면 모듈이 있고 북면이 들어오면 북면의 row를 계산한다 + * + * @param {*} trestleDetailData + * @returns + */ + const checkAutoLayoutModuleSetup = (moduleSetupSurface, trestleDetailData) => { + //북면 모듈이 없을때 + //체크된 모듈 중에 북면 모듈이 있는지 확인하는 로직 + const isIncludeNorthModule = checkedModule.some((module) => module.northModuleYn === 'Y') + const maxCol = trestleDetailData.moduleMaxCols //최대 열수 -> 얘는 멀티랑 관계없음 + + //북면 모듈이 없으면 + if (!isIncludeNorthModule) { + const isMultipleModules = checkedModule.length > 1 //모듈이 여러개인지 체크하고 + + //멀티 모듈이면 모듈밖에 내려온 정보의 maxRow 단일 모듈이면 모듈에 maxRow + const maxRow = isMultipleModules + ? trestleDetailData.moduleMaxRows + : trestleDetailData.module.find((item) => item.moduleTpCd === checkedModule[0].moduleTpCd).moduleMaxRows //멀티모듈이면 밖에 maxRows로 판단 아니면 module->itemmList를 가지고 판단 + + //멀티 모듈이면 모듈밖에 내려온 정보의 row 합, 단일 모듈이면 모듈에 row + const sumRowCount = isMultipleModules + ? layoutSetupRef.filter((item) => item.checked).reduce((acc, cur) => acc + cur.row, 0) + : layoutSetupRef.find((item) => item.moduleId === checkedModule[0].itemId).row //멀티모듈이면 전체 합, 체크된 한개의 열 + + //col는 moduleItems 배열 밖에 내려옴 + const sumColCount = layoutSetupRef.filter((item) => item.col).some((item) => item.col > maxCol) + + // 혼합일때 모듈 개별의 maxRow를 체크해서 가능여부 확인 + const isPassedObject = + isMultipleModules && layoutSetupRef.find((item, index) => item.checked && item.row > trestleDetailData.module[index].mixModuleMaxRows) //체크된 배열은 checked여부로 체크해서 index와 동일함 + //전체 카은트된 열수가 크거나 혼합카운트가 존재 하면 실패 + if (sumRowCount > maxRow || sumColCount || isPassedObject) { + failAutoSetupRoof.push(moduleSetupSurface) + return false + } + } else { + const normalModule = checkedModule.filter((item) => item.northModuleYn === 'N') + const northModule = checkedModule.filter((item) => item.northModuleYn === 'Y') + const northModuleIds = northModule.map((item) => item.itemId) + let isPassedNormalModule = false + + //만약 북면 모듈이 2개면 이 하위 로직 가져다가 쓰면됨 northModule === 만 바꾸면 될듯 + // northModule을 배열로 만들고 include로 해서 체크 해야됨 + if (normalModule.length > 0 && !moduleSetupSurface.isNorth) { + //C1C2 모듈일 경우ㅁㅁ + const isMultipleModules = normalModule.length > 1 //모듈이 여러개면 + const maxRow = isMultipleModules + ? trestleDetailData.moduleMaxRows + : trestleDetailData.module.find((item) => item.moduleTpCd === normalModule[0].moduleTpCd).moduleMaxRows //멀티모듈이면 밖에 maxRows로 판단 아니면 module->itemmList를 가지고 판단 + + //북면 모듈 id를 제외한 모듈의 단 체크 + const sumRowCount = isMultipleModules + ? layoutSetupRef.filter((item) => item.checked && !northModuleIds.includes(item.moduleId)).reduce((acc, cur) => acc + cur.row, 0) + : layoutSetupRef.find((item) => item.moduleId === normalModule[0].itemId).row //멀티모듈이면 전체 합, 체크된 한개의 열 + + //북면 모듈 id를 제외한 모듈의 열 체크 + const sumColCount = layoutSetupRef.filter((item) => item.col && !northModuleIds.includes(item.moduleId)).some((item) => item.col > maxCol) + + // 혼합일때 모듈 개별의 row를 체크함 + const isPassedObject = + isMultipleModules && + layoutSetupRef.find( + (item, index) => item.checked && !item.moduleId.includes(northModuleIds) && item.row > trestleDetailData.module[index].mixModuleMaxRows, + ) + + // 합산 단수가 맥스단수보다 크거나 열이 맥스열수보다 크거나 혼합일때 모듈 개별의 row가 맥스단수보다 크면 실패 + if (sumRowCount > maxRow || sumColCount || isPassedObject) { + failAutoSetupRoof.push(moduleSetupSurface) + return false + } else { + isPassedNormalModule = true + } + } + + //위에서 일반 모듈이 설치가 완료면 그냥 넘어간다 + //일반 모듈이 pass라면 일반 모듈이 설치됨 + //만약 일반모듈이 체크가 안되어 있으면 밑에 로직을 탐 + if (!isPassedNormalModule) { + //북면 모듈이 있고 북면에 있을때 + if (northModule.length > 0 && (moduleSetupSurface.isNorth || !moduleSetupSurface.isNorth)) { + //북면 모듈이 있는데 일반 모듈이 있을때 북면이 아니면 그냥 북면은 그냥 pass + const isMultipleModules = northModule.length > 1 //모듈이 여러개면 + const maxRow = isMultipleModules + ? trestleDetailData.moduleMaxRows + : trestleDetailData.module.find((item) => item.moduleTpCd === northModule[0].moduleTpCd).moduleMaxRows //멀티모듈이면 밖에 maxRows로 판단 아니면 module->itemmList를 가지고 판단 + + const sumRowCount = isMultipleModules + ? layoutSetupRef.filter((item) => northModuleIds.includes(item.moduleId)).reduce((acc, cur) => acc + cur.row, 0) + : layoutSetupRef.find((item) => item.moduleId === northModule[0].itemId).row //멀티모듈이면 전체 합, 체크된 한개의 열 + + const sumColCount = layoutSetupRef.filter((item) => item.col && northModuleIds.includes(item.moduleId)).some((item) => item.col > maxCol) + + // 혼합일때 모듈 개별의 row를 체크함 + const isPassedObject = + isMultipleModules && + layoutSetupRef.find( + (item, index) => + item.checked && northModuleIds.includes(item.moduleId) && item.row > trestleDetailData.module[index].mixModuleMaxRows, + ) + + // 합산 단수가 맥스단수보다 크거나 열이 맥스열수보다 크거나 혼합일때 모듈 개별의 row가 맥스단수보다 크면 실패 + if (sumRowCount > maxRow || sumColCount || isPassedObject) { + failAutoSetupRoof.push(moduleSetupSurface) + return false + } + } + } + } + return true + } + //흐름 방향이 남쪽(아래) - const downFlowSetupModule = ( - surfaceMaxLines, - maxLengthLine, - moduleSetupArray, - moduleSetupSurface, - containsBatchObjects, - isCenter = false, - intvHor, - intvVer, - ) => { + const downFlowSetupModule = (maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) => { let setupModule = [] const trestleDetailData = moduleSetupSurface.trestleDetail @@ -1840,8 +1988,35 @@ export function useModuleBasicSetting(tabNum) { let installedModuleHeightCount = 0 //마지막으로 설치된 모듈의 카운트 let isChidoriLine = false let flowLines + let installedModuleMixYn + const isNorthSurface = moduleSetupSurface.isNorth + const isIncludeNorthModule = checkedModule.some((module) => module.northModuleYn === 'Y') //체크된 모듈 중에 북면 모듈이 있는지 확인하는 로직 + + let layoutRow = 0 + let layoutCol = 0 + + if (type === MODULE_SETUP_TYPE.LAYOUT) { + const isPassed = checkAutoLayoutModuleSetup(moduleSetupSurface, trestleDetailData) + if (!isPassed) { + return + } + } + + for (let moduleIndex = 0; moduleIndex < checkedModule.length; moduleIndex++) { + const module = checkedModule[moduleIndex] + + if (type === MODULE_SETUP_TYPE.LAYOUT && checkedLayoutData) { + const layout = checkedLayoutData.find((item) => module.itemId === item.moduleId) + layoutRow = layout.row + layoutCol = layout.col + } + //혼합여부에 따라 설치 여부 결정 + if (installedModuleMixYn && installedModuleMixYn !== module.mixAsgYn) { + continue + } + //북면일때 + const isNorthModuleYn = module.northModuleYn === 'Y' - checkedModule.forEach((module, moduleIndex) => { const tmpModuleData = trestleDetailData.module.filter((moduleObj) => module.moduleTpCd === moduleObj.moduleTpCd)[0] //혼합모듈일때는 mixModuleMaxRows 값이 0 이상임 // let moduleMaxRows = tmpModuleData.mixModuleMaxRows === 0 ? tmpModuleData.moduleMaxRows : tmpModuleData.mixModuleMaxRows @@ -1857,18 +2032,38 @@ export function useModuleBasicSetting(tabNum) { } } + if (moduleSetupSurface.isSaleStoreNorthFlg) { + //북면일때 + if (isIncludeNorthModule) { + if (!isNorthModuleYn && isNorthSurface) { + continue + } //흐름 방향이 북쪽(위) + } + } else { + if (isNorthSurface) { + if (!isNorthModuleYn) { + continue + } + } + } + //육지붕이 아닐때만 넣는다 육지붕일땐 클릭 이벤트에 별도로 넣어놓음 const moduleArray = [] let calcAreaWidth = Math.abs(flowLines.right.x1 - flowLines.left.x1) //오른쪽 x에서 왼쪽 x를 뺀 가운데를 찾는 로직 let calcModuleWidthCount = calcAreaWidth / (width + intvHor + 1) //뺀 공간에서 모듈을 몇개를 넣을수 있는지 확인하는 로직 - let calcMaxModuleWidthCount = calcModuleWidthCount > moduleMaxCols ? moduleMaxCols : calcModuleWidthCount //최대 모듈 단수가 있기 때문에 최대 단수보다 카운트가 크면 최대 단수로 씀씀 - let totalModuleWidthCount = Math.floor(calcMaxModuleWidthCount) //치조배치일경우는 한개 더 넣는다 - let calcAreaHeight = flowLines.bottom.y1 - flowLines.top.y1 let calcModuleHeightCount = calcAreaHeight / (height + intvVer + 1) + if (type === MODULE_SETUP_TYPE.LAYOUT) { + calcModuleWidthCount = layoutCol > calcModuleWidthCount ? calcModuleWidthCount : layoutCol + calcModuleHeightCount = layoutRow + } + + let calcMaxModuleWidthCount = calcModuleWidthCount > moduleMaxCols ? moduleMaxCols : calcModuleWidthCount //최대 모듈 단수가 있기 때문에 최대 단수보다 카운트가 크면 최대 단수로 씀씀 + let totalModuleWidthCount = Math.floor(calcMaxModuleWidthCount) //치조배치일경우는 한개 더 넣는다 + let calcStartPoint = flowLines.right.type === 'flat' ? (calcAreaWidth - totalModuleWidthCount * width) / 2 : 0 //반씩 나눠서 중앙에 맞춤 bottom 기준으로 양변이 직선일때만 가운데 정렬 let startPointX = flowLines.left.x1 + calcStartPoint //시작점을 만든다 @@ -1882,7 +2077,7 @@ export function useModuleBasicSetting(tabNum) { let chidoriLength = 0 //첫번재 모듈 설치 후 두번째 모듈을 몇개까지 설치 할 수 있는지 계산 - if (moduleIndex > 0) { + if (installedModuleHeightCount > 0) { // moduleMaxRows = totalModuleMaxRows - installedModuleHeightCount //두번째 모듈일때 isChidoriLine = installedModuleHeightCount % 2 != 0 ? true : false //첫번째에서 짝수에서 끝났으면 홀수는 치도리가 아님 짝수는 치도리 } @@ -1892,12 +2087,12 @@ export function useModuleBasicSetting(tabNum) { let moduleY = flowLines.bottom.y1 - height * i - 1 //살짝 여유를 준다 //두번째 모듈 -> 혼합일 경우의 설치될 모듈 높이를 계산 - if (moduleIndex > 0) { - moduleY = installedLastHeightCoord - intvVer + if (installedModuleHeightCount > 0) { + moduleY = installedLastHeightCoord } //첫번째는 붙여서 두번째는 마진을 주고 설치 - heightMargin = i === 0 ? 0 : intvVer * i + heightMargin = installedModuleHeightCount === 0 ? 0 : intvVer for (let j = 0; j < totalModuleWidthCount; j++) { let moduleX = startPointX + width * j + 1 //5정도 마진을 준다 @@ -1954,23 +2149,15 @@ export function useModuleBasicSetting(tabNum) { } if (isInstall) { ++installedModuleHeightCount + installedModuleMixYn = module.mixAsgYn } } setupModule.push(moduleArray) - }) + } } - const topFlowSetupModule = ( - surfaceMaxLines, - maxLengthLine, - moduleSetupArray, - moduleSetupSurface, - containsBatchObjects, - isCenter = false, - intvHor, - intvVer, - ) => { + const topFlowSetupModule = (maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) => { let setupModule = [] const trestleDetailData = moduleSetupSurface.trestleDetail @@ -1980,8 +2167,36 @@ export function useModuleBasicSetting(tabNum) { let installedModuleHeightCount = 0 //마지막으로 설치된 모듈의 카운트 let isChidoriLine = false let flowLines + let installedModuleMixYn + const isNorthSurface = moduleSetupSurface.isNorth + const isIncludeNorthModule = checkedModule.some((module) => module.northModuleYn === 'Y') //체크된 모듈 중에 북면 모듈이 있는지 확인하는 로직 + + let layoutRow = 0 + let layoutCol = 0 + + if (type === MODULE_SETUP_TYPE.LAYOUT) { + const isPassed = checkAutoLayoutModuleSetup(moduleSetupSurface, trestleDetailData) + if (!isPassed) { + return + } + } + + for (let moduleIndex = 0; moduleIndex < checkedModule.length; moduleIndex++) { + const module = checkedModule[moduleIndex] + + if (type === MODULE_SETUP_TYPE.LAYOUT && checkedLayoutData) { + const layout = checkedLayoutData.find((item) => module.itemId === item.moduleId) + layoutRow = layout.row + layoutCol = layout.col + } + + //혼합여부에 따라 설치 여부 결정 + if (installedModuleMixYn && installedModuleMixYn !== module.mixAsgYn) { + continue + } + + const isNorthModuleYn = module.northModuleYn === 'Y' - checkedModule.forEach((module, moduleIndex) => { const tmpModuleData = trestleDetailData.module.filter((moduleObj) => module.moduleTpCd === moduleObj.moduleTpCd)[0] //혼합모듈일때는 mixModuleMaxRows 값이 0 이상임 let moduleMaxRows = tmpModuleData.mixModuleMaxRows === 0 ? tmpModuleData.moduleMaxRows : tmpModuleData.mixModuleMaxRows @@ -1997,20 +2212,45 @@ export function useModuleBasicSetting(tabNum) { } } - //흐름 방향이 북쪽(위) + if (moduleSetupSurface.isSaleStoreNorthFlg) { + //북면가능 설치 대리점이면 + //북면일때 + if (isIncludeNorthModule) { + //북면 모듈이 있는지 확인하는 로직 + if (!isNorthModuleYn && isNorthSurface) { + //북면 모듈이 있으면 북면 모듈만 깔고 나머지는 스킵 + continue + } //흐름 방향이 북쪽(위) + } + } else { + // 불면설치 불가 대리점이면 + if (isNorthSurface) { + //북면일때 + if (!isNorthModuleYn) { + //북면 모듈이 아니면 스킵 + continue + } + } + } //육지붕이 아닐때만 넣는다 육지붕일땐 클릭 이벤트에 별도로 넣어놓음 const moduleArray = [] let calcAreaWidth = flowLines.right.x1 - flowLines.left.x1 //오른쪽 x에서 왼쪽 x를 뺀 가운데를 찾는 로직 let calcModuleWidthCount = calcAreaWidth / (width + intvHor + 1) //뺀 공간에서 모듈을 몇개를 넣을수 있는지 확인하는 로직 + let calcAreaHeight = flowLines.bottom.y1 - flowLines.top.y1 + let calcModuleHeightCount = calcAreaHeight / (height + intvVer + 1) + + //단수지정 자동이면 + if (type === MODULE_SETUP_TYPE.LAYOUT) { + calcModuleWidthCount = layoutCol > calcModuleWidthCount ? calcModuleWidthCount : layoutCol + calcModuleHeightCount = layoutRow + } + let calcMaxModuleWidthCount = calcModuleWidthCount > moduleMaxCols ? moduleMaxCols : calcModuleWidthCount //최대 모듈 단수가 있기 때문에 최대 단수보다 카운트가 크면 최대 단수로 씀씀 // let totalModuleWidthCount = isChidori ? Math.abs(calcMaxModuleWidthCount) : Math.floor(calcMaxModuleWidthCount) //치조배치일경우는 한개 더 넣는다 //??어쩔때는 붙고 어쩔때는 안붙고 멋대로??? let totalModuleWidthCount = Math.floor(calcMaxModuleWidthCount) //치조배치일경우는 한개 더 넣는다 - let calcAreaHeight = flowLines.bottom.y1 - flowLines.top.y1 - let calcModuleHeightCount = calcAreaHeight / (height + intvVer + 1) - let calcStartPoint = flowLines.left.type === 'flat' ? (calcAreaWidth - totalModuleWidthCount * width) / 2 : 0 //반씩 나눠서 중앙에 맞춤 bottom 기준으로 양변이 직선일때만 가운데 정렬 let startPointX = flowLines.right.x1 - calcStartPoint //시작점을 만든다 @@ -2032,11 +2272,11 @@ export function useModuleBasicSetting(tabNum) { let isInstall = false let moduleY = flowLines.top.y1 + height * i //탑의 y점에서부터 아래로 그려 내려간다 - if (moduleIndex > 0) { - moduleY = installedLastHeightCoord + intvVer + 1 + if (installedModuleHeightCount > 0) { + moduleY = installedLastHeightCoord } - heightMargin = i === 0 ? 0 : intvVer * i //모듈간에 마진이 있어 마진값도 넣음 + heightMargin = installedModuleHeightCount === 0 ? 0 : intvVer //모듈간에 마진이 있어 마진값도 넣음 for (let j = 0; j < totalModuleWidthCount; j++) { //모듈 열수 만큼 반복 let moduleX = startPointX - width * j - 1 //시작점에서 우 -> 좌로 그려 내려간다 @@ -2087,25 +2327,17 @@ export function useModuleBasicSetting(tabNum) { } if (isInstall) { ++installedModuleHeightCount + installedModuleMixYn = module.mixAsgYn } } setupModule.push(moduleArray) - }) + } } //남, 북과 같은 로직으로 적용하려면 좌우는 열 -> 행 으로 그려야함 //변수명은 bottom 기준으로 작성하여 동일한 방향으로 진행한다 - const leftFlowSetupModule = ( - surfaceMaxLines, - maxLengthLine, - moduleSetupArray, - moduleSetupSurface, - containsBatchObjects, - isCenter = false, - intvHor, - intvVer, - ) => { + const leftFlowSetupModule = (maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) => { let setupModule = [] const trestleDetailData = moduleSetupSurface.trestleDetail //가대 상세 데이터 @@ -2115,8 +2347,36 @@ export function useModuleBasicSetting(tabNum) { let installedModuleHeightCount = 0 //마지막으로 설치된 모듈의 카운트 let isChidoriLine = false let flowLines + let installedModuleMixYn + const isNorthSurface = moduleSetupSurface.isNorth + const isIncludeNorthModule = checkedModule.some((module) => module.northModuleYn === 'Y') //체크된 모듈 중에 북면 모듈이 있는지 확인하는 로직 - checkedModule.forEach((module, moduleIndex) => { + let layoutRow = 0 + let layoutCol = 0 + + if (type === MODULE_SETUP_TYPE.LAYOUT) { + const isPassed = checkAutoLayoutModuleSetup(moduleSetupSurface, trestleDetailData) + if (!isPassed) { + return + } + } + + for (let moduleIndex = 0; moduleIndex < checkedModule.length; moduleIndex++) { + const module = checkedModule[moduleIndex] + + //단수 지정이면 + if (type === MODULE_SETUP_TYPE.LAYOUT && checkedLayoutData) { + const layout = checkedLayoutData.find((item) => module.itemId === item.moduleId) + layoutRow = layout.row + layoutCol = layout.col + } + + //혼합여부에 따라 설치 여부 결정 + if (installedModuleMixYn && installedModuleMixYn !== module.mixAsgYn) { + continue + } + + const isNorthModuleYn = module.northModuleYn === 'Y' const tmpModuleData = trestleDetailData.module.filter((moduleObj) => module.moduleTpCd === moduleObj.moduleTpCd)[0] //혼합모듈일때는 mixModuleMaxRows 값이 0 이상임 let moduleMaxRows = tmpModuleData.mixModuleMaxRows === 0 ? tmpModuleData.moduleMaxRows : tmpModuleData.mixModuleMaxRows @@ -2135,19 +2395,46 @@ export function useModuleBasicSetting(tabNum) { } } + if (moduleSetupSurface.isSaleStoreNorthFlg) { + //북면가능 설치 대리점이면 + //북면일때 + if (isIncludeNorthModule) { + //북면 모듈이 있는지 확인하는 로직 + if (!isNorthModuleYn && isNorthSurface) { + //북면 모듈이 있으면 북면 모듈만 깔고 나머지는 스킵 + continue + } //흐름 방향이 북쪽(위) + } + } else { + // 불면설치 불가 대리점이면 + if (isNorthSurface) { + //북면일때 + if (!isNorthModuleYn) { + //북면 모듈이 아니면 스킵 + continue + } + } + } + //육지붕이 아닐때만 넣는다 육지붕일땐 클릭 이벤트에 별도로 넣어놓음 const moduleArray = [] let calcAreaWidth = flowLines.bottom.y1 - flowLines.top.y1 //아래에서 y에서 위를 y를 뺀 가운데를 찾는 로직 let calcModuleWidthCount = calcAreaWidth / (height + intvHor + 1) //뺀 공간에서 모듈을 몇개를 넣을수 있는지 확인하는 로직 + let calcAreaHeight = flowLines.right.x1 - flowLines.left.x1 + let calcModuleHeightCount = calcAreaHeight / (width + intvVer + 1) + + //단수지정 자동이면 + if (type === MODULE_SETUP_TYPE.LAYOUT) { + calcModuleWidthCount = layoutCol > calcModuleWidthCount ? calcModuleWidthCount : layoutCol + calcModuleHeightCount = layoutRow + } + let calcMaxModuleWidthCount = calcModuleWidthCount > moduleMaxCols ? moduleMaxCols : calcModuleWidthCount //최대 모듈 단수가 있기 때문에 최대 단수보다 카운트가 크면 최대 단수로 씀씀 // let totalModuleWidthCount = isChidori ? Math.abs(calcMaxModuleWidthCount) : Math.floor(calcMaxModuleWidthCount) //치조배치일경우는 한개 더 넣는다 let totalModuleWidthCount = Math.floor(calcMaxModuleWidthCount) //치조배치일경우는 한개 더 넣는다 - let calcAreaHeight = flowLines.right.x1 - flowLines.left.x1 - let calcModuleHeightCount = calcAreaHeight / (width + intvVer + 1) - let calcStartPoint = flowLines.bottom.type === 'flat' ? (calcAreaWidth - totalModuleWidthCount * height) / 2 : 0 //반씩 나눠서 중앙에 맞춤 left 높이 기준으로 양변이 직선일때만 가운데 정렬 let startPointX = flowLines.top.y1 + calcStartPoint //시작점을 만든다 @@ -2171,12 +2458,12 @@ export function useModuleBasicSetting(tabNum) { let moduleY = flowLines.left.x1 + width * i + 1 //살짝 여유를 준다 //두번째 모듈 -> 혼합일 경우의 설치될 모듈 높이를 계산 - if (moduleIndex > 0) { - moduleY = installedLastHeightCoord + intvHor + if (installedModuleHeightCount > 0) { + moduleY = installedLastHeightCoord } //첫번째는 붙여서 두번째는 마진을 주고 설치 - heightMargin = i === 0 ? 0 : intvHor * i + heightMargin = installedModuleHeightCount === 0 ? 0 : intvHor for (let j = 0; j < totalModuleWidthCount; j++) { let moduleX = startPointX + height * j + 1 //5정도 마진을 준다 @@ -2227,22 +2514,14 @@ export function useModuleBasicSetting(tabNum) { if (isInstall) { ++installedModuleHeightCount + installedModuleMixYn = module.mixAsgYn } } setupModule.push(moduleArray) - }) + } } - const rightFlowSetupModule = ( - surfaceMaxLines, - maxLengthLine, - moduleSetupArray, - moduleSetupSurface, - containsBatchObjects, - isCenter = false, - intvHor, - intvVer, - ) => { + const rightFlowSetupModule = (maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) => { let setupModule = [] const trestleDetailData = moduleSetupSurface.trestleDetail //가대 상세 데이터 @@ -2252,8 +2531,35 @@ export function useModuleBasicSetting(tabNum) { let installedModuleHeightCount = 0 //마지막으로 설치된 모듈의 카운트 let isChidoriLine = false let flowLines + let installedModuleMixYn + const isNorthSurface = moduleSetupSurface.isNorth + const isIncludeNorthModule = checkedModule.some((module) => module.northModuleYn === 'Y') //체크된 모듈 중에 북면 모듈이 있는지 확인하는 로직 - checkedModule.forEach((module, moduleIndex) => { + let layoutRow = 0 + let layoutCol = 0 + + if (type === MODULE_SETUP_TYPE.LAYOUT) { + const isPassed = checkAutoLayoutModuleSetup(moduleSetupSurface, trestleDetailData) + if (!isPassed) { + return + } + } + + for (let moduleIndex = 0; moduleIndex < checkedModule.length; moduleIndex++) { + const module = checkedModule[moduleIndex] + + if (type === MODULE_SETUP_TYPE.LAYOUT && checkedLayoutData) { + const layout = checkedLayoutData.find((item) => module.itemId === item.moduleId) + layoutRow = layout.row + layoutCol = layout.col + } + + //혼합여부에 따라 설치 여부 결정 + if (installedModuleMixYn && installedModuleMixYn !== module.mixAsgYn) { + continue + } + + const isNorthModuleYn = module.northModuleYn === 'Y' const tmpModuleData = trestleDetailData.module.filter((moduleObj) => module.moduleTpCd === moduleObj.moduleTpCd)[0] //혼합모듈일때는 mixModuleMaxRows 값이 0 이상임 let moduleMaxRows = tmpModuleData.mixModuleMaxRows === 0 ? tmpModuleData.moduleMaxRows : tmpModuleData.mixModuleMaxRows @@ -2272,18 +2578,45 @@ export function useModuleBasicSetting(tabNum) { } } + if (moduleSetupSurface.isSaleStoreNorthFlg) { + //북면가능 설치 대리점이면 + //북면일때 + if (isIncludeNorthModule) { + //북면 모듈이 있는지 확인하는 로직 + if (!isNorthModuleYn && isNorthSurface) { + //북면 모듈이 있으면 북면 모듈만 깔고 나머지는 스킵 + continue + } //흐름 방향이 북쪽(위) + } + } else { + // 불면설치 불가 대리점이면 + if (isNorthSurface) { + //북면일때 + if (!isNorthModuleYn) { + //북면 모듈이 아니면 스킵 + continue + } + } + } + //육지붕이 아닐때만 넣는다 육지붕일땐 클릭 이벤트에 별도로 넣어놓음 const moduleArray = [] let calcAreaWidth = flowLines.bottom.y1 - flowLines.top.y1 //아래에서 y에서 위를 y를 뺀 가운데를 찾는 로직 let calcModuleWidthCount = calcAreaWidth / (height + intvHor + 1) //뺀 공간에서 모듈을 몇개를 넣을수 있는지 확인하는 로직 + let calcAreaHeight = flowLines.right.x1 - flowLines.left.x1 + let calcModuleHeightCount = calcAreaHeight / (width + intvVer + 1) + + //단수지정 자동이면 + if (type === MODULE_SETUP_TYPE.LAYOUT) { + calcModuleWidthCount = layoutCol > calcModuleWidthCount ? calcModuleWidthCount : layoutCol + calcModuleHeightCount = layoutRow + } + let calcMaxModuleWidthCount = calcModuleWidthCount > moduleMaxCols ? moduleMaxCols : calcModuleWidthCount //최대 모듈 단수가 있기 때문에 최대 단수보다 카운트가 크면 최대 단수로 씀씀 // let totalModuleWidthCount = isChidori ? Math.abs(calcMaxModuleWidthCount) : Math.floor(calcMaxModuleWidthCount) //치조배치일경우는 한개 더 넣는다 let totalModuleWidthCount = Math.floor(calcMaxModuleWidthCount) //치조배치일경우는 한개 더 넣는다 - let calcAreaHeight = flowLines.right.x1 - flowLines.left.x1 - let calcModuleHeightCount = calcAreaHeight / (width + intvVer + 1) - let calcStartPoint = flowLines.top.type === 'flat' ? (calcAreaWidth - totalModuleWidthCount * height) / 2 : 0 //반씩 나눠서 중앙에 맞춤 left 높이 기준으로 양변이 직선일때만 가운데 정렬 let startPointX = flowLines.bottom.y2 - calcStartPoint //시작점을 만든다 @@ -2307,12 +2640,12 @@ export function useModuleBasicSetting(tabNum) { let moduleY = flowLines.right.x1 - width * i - 1 //살짝 여유를 준다 //두번째 모듈 -> 혼합일 경우의 설치될 모듈 높이를 계산 - if (moduleIndex > 0) { - moduleY = installedLastHeightCoord - intvHor + if (installedModuleHeightCount > 0) { + moduleY = installedLastHeightCoord } //첫번째는 붙여서 두번째는 마진을 주고 설치 - heightMargin = i === 0 ? 0 : intvHor * i + heightMargin = installedModuleHeightCount === 0 ? 0 : intvHor for (let j = 0; j < totalModuleWidthCount; j++) { let moduleX = startPointX - height * j - 1 //5정도 마진을 준다 @@ -2366,11 +2699,12 @@ export function useModuleBasicSetting(tabNum) { if (isInstall) { ++installedModuleHeightCount + installedModuleMixYn = module.mixAsgYn } } setupModule.push(moduleArray) - }) + } } moduleSetupSurfaces.forEach((moduleSetupSurface, index) => { @@ -2379,7 +2713,6 @@ export function useModuleBasicSetting(tabNum) { const turfModuleSetupSurface = polygonToTurfPolygon(moduleSetupSurface) //폴리곤을 turf 객체로 변환 const containsBatchObjects = objectsIncludeSurface(turfModuleSetupSurface) //배치면에 오브젝트(도머, 개구등)이 있는지 확인하는 로직 - const surfaceMaxLines = findSetupSurfaceMaxLines(moduleSetupSurface) let maxLengthLine = moduleSetupSurface.lines.reduce((acc, cur) => { return acc.length > cur.length ? acc : cur @@ -2400,30 +2733,30 @@ export function useModuleBasicSetting(tabNum) { if (setupLocation === 'eaves') { // 흐름방향이 남쪽일때 if (moduleSetupSurface.direction === 'south') { - downFlowSetupModule(surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, false, intvHor, intvVer) + downFlowSetupModule(maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) } if (moduleSetupSurface.direction === 'west') { - leftFlowSetupModule(surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, false, intvHor, intvVer) + leftFlowSetupModule(maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) } if (moduleSetupSurface.direction === 'east') { - rightFlowSetupModule(surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, false, intvHor, intvVer) + rightFlowSetupModule(maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) } if (moduleSetupSurface.direction === 'north') { - topFlowSetupModule(surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, false, intvHor, intvVer) + topFlowSetupModule(maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) } } else if (setupLocation === 'ridge') { //용마루 if (moduleSetupSurface.direction === 'south') { - topFlowSetupModule(surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, false, intvHor, intvVer) + topFlowSetupModule(maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) } if (moduleSetupSurface.direction === 'west') { - rightFlowSetupModule(surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, false, intvHor, intvVer) + rightFlowSetupModule(maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) } if (moduleSetupSurface.direction === 'east') { - leftFlowSetupModule(surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, false, intvHor, intvVer) + leftFlowSetupModule(maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) } if (moduleSetupSurface.direction === 'north') { - downFlowSetupModule(surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, false, intvHor, intvVer) + downFlowSetupModule(maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) } } @@ -2460,6 +2793,14 @@ export function useModuleBasicSetting(tabNum) { } }) // calculateForApi() + + /** + * 자동 레이아웃일떄 설치 가능한 애들은 설치 해주고 실패한 애들은 이름, 최대 설치 가능한 높이 알려줄라고 + */ + if (type === MODULE_SETUP_TYPE.LAYOUT && failAutoSetupRoof.length) { + const roofNames = failAutoSetupRoof.map((roof) => roof.roofMaterial.roofMatlNmJp).join(', ') + swalFire({ text: getMessage('modal.module.basic.setting.module.placement.over.max.row', [roofNames]), icon: 'warning' }) + } } const coordToTurfPolygon = (points) => { @@ -2766,13 +3107,13 @@ export function useModuleBasicSetting(tabNum) { let moduleSetupSurfaces = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE) //모듈설치면를 가져옴 if (isManualModuleSetup) { if (checkedModule.length === 0) { - swalFire({ text: getMessage('module.place.select.module') }) + swalFire({ text: getMessage('module.place.select.module'), icon: 'warning' }) setIsManualModuleSetup(!isManualModuleSetup) return } if (checkedModule.length > 1) { - swalFire({ text: getMessage('module.place.select.one.module') }) + swalFire({ text: getMessage('module.place.select.one.module'), icon: 'warning' }) setIsManualModuleSetup(!isManualModuleSetup) return } @@ -3085,7 +3426,7 @@ export function useModuleBasicSetting(tabNum) { const mixAsgYn = trestlePolygon.modules[0].moduleInfo.mixAsgYn //현재 체크된 모듈기준으로 혼합가능인지 확인 Y === Y, N === N 일때만 설치 가능 if (checkedModule[0].mixAsgYn !== mixAsgYn) { - swalFire({ text: getMessage('modal.module.basic.setting.module.placement.mix.asg.yn.error') }) + swalFire({ text: getMessage('modal.module.basic.setting.module.placement.mix.asg.yn.error'), icon: 'warning' }) return } } @@ -3118,7 +3459,7 @@ export function useModuleBasicSetting(tabNum) { const intersection = turf.intersect(turf.featureCollection([dormerTurfPolygon, tempTurfModule])) //겹치는지 확인 //겹치면 안됨 if (intersection) { - swalFire({ text: getMessage('module.place.overobject') }) + swalFire({ text: getMessage('module.place.overobject'), icon: 'warning' }) isIntersection = false } }) @@ -3136,10 +3477,10 @@ export function useModuleBasicSetting(tabNum) { manualDrawModules.push(manualModule) setModuleStatisticsData() } else { - swalFire({ text: getMessage('module.place.overlab') }) + swalFire({ text: getMessage('module.place.overlab'), icon: 'warning' }) } } else { - swalFire({ text: getMessage('module.place.out') }) + swalFire({ text: getMessage('module.place.out'), icon: 'warning' }) } } }) @@ -3169,7 +3510,7 @@ export function useModuleBasicSetting(tabNum) { //Y인 모듈과 N인 모듈이 둘다 존재하면 설치 불가 if (mixAsgY.length > 0 && mixAsgN.length > 0) { - swalFire({ text: getMessage('modal.module.basic.setting.module.placement.mix.asg.yn.error') }) + swalFire({ text: getMessage('modal.module.basic.setting.module.placement.mix.asg.yn.error'), icon: 'warning' }) return } diff --git a/src/hooks/roofcover/useOuterLineWall.js b/src/hooks/roofcover/useOuterLineWall.js index 18a07cf8..302a4c9a 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() @@ -690,6 +694,7 @@ export function useOuterLineWall(id, propertiesId) { if (points.length === 0) { return } + enterCheck(e) // 포커스가 length1에 있지 않으면 length1에 포커스를 줌 const activeElem = document.activeElement if (activeElem !== length1Ref.current) { @@ -754,6 +759,7 @@ export function useOuterLineWall(id, propertiesId) { if (points.length === 0) { return } + enterCheck(e) const key = e.key const activeElem = document.activeElement @@ -787,6 +793,7 @@ export function useOuterLineWall(id, propertiesId) { if (points.length === 0) { return } + enterCheck(e) const key = e.key switch (key) { case 'Down': // IE/Edge에서 사용되는 값 @@ -812,6 +819,7 @@ export function useOuterLineWall(id, propertiesId) { if (points.length === 0) { return } + enterCheck(e) const key = e.key switch (key) { case 'Enter': { @@ -836,7 +844,7 @@ export function useOuterLineWall(id, propertiesId) { if (points.length === 0) { return } - + enterCheck(e) const key = e.key switch (key) { case 'Down': // IE/Edge에서 사용되는 값 @@ -902,6 +910,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/useCanvasEvent.js b/src/hooks/useCanvasEvent.js index 4a601ed8..71140097 100644 --- a/src/hooks/useCanvasEvent.js +++ b/src/hooks/useCanvasEvent.js @@ -4,7 +4,7 @@ import { v4 as uuidv4 } from 'uuid' import { canvasSizeState, canvasState, canvasZoomState, currentMenuState, currentObjectState } from '@/store/canvasAtom' import { QPolygon } from '@/components/fabric/QPolygon' import { fontSelector } from '@/store/fontAtom' -import { MENU } from '@/common/common' +import { MENU, POLYGON_TYPE } from '@/common/common' // 캔버스에 필요한 이벤트 export function useCanvasEvent() { @@ -204,9 +204,20 @@ export function useCanvasEvent() { if (selected?.length > 0) { selected.forEach((obj) => { - if (obj.type === 'QPolygon' && currentMenu !== MENU.MODULE_CIRCUIT_SETTING.BASIC_SETTING) { + // if (obj.type === 'QPolygon' && currentMenu !== MENU.MODULE_CIRCUIT_SETTING.BASIC_SETTING) { + if (obj.type === 'QPolygon') { + const originStroke = obj.stroke obj.set({ stroke: 'red' }) - obj.bringToFront() + + if (currentMenu === MENU.MODULE_CIRCUIT_SETTING.BASIC_SETTING) { + if (obj.name === POLYGON_TYPE.MODULE) { + obj.set({ strokeWidth: 3 }) + } + if (obj.name === POLYGON_TYPE.ROOF) { + canvas.discardActiveObject() + obj.set({ stroke: originStroke }) + } + } } }) canvas.renderAll() @@ -218,10 +229,13 @@ export function useCanvasEvent() { if (deselected?.length > 0) { deselected.forEach((obj) => { - if (obj.type === 'QPolygon' && currentMenu !== MENU.MODULE_CIRCUIT_SETTING.BASIC_SETTING) { + if (obj.type === 'QPolygon') { if (obj.name !== 'moduleSetupSurface') { obj.set({ stroke: 'black' }) } + if (obj.name === POLYGON_TYPE.MODULE && currentMenu === MENU.MODULE_CIRCUIT_SETTING.BASIC_SETTING) { + obj.set({ strokeWidth: 0.3 }) + } } }) } @@ -234,17 +248,24 @@ export function useCanvasEvent() { if (deselected?.length > 0) { deselected.forEach((obj) => { - if (obj.type === 'QPolygon' && currentMenu !== MENU.MODULE_CIRCUIT_SETTING.BASIC_SETTING) { + if (obj.type === 'QPolygon') { obj.set({ stroke: 'black' }) + if (obj.name === POLYGON_TYPE.MODULE && currentMenu === MENU.MODULE_CIRCUIT_SETTING.BASIC_SETTING) { + //모듈 미선택시 라인 두께 변경 + obj.set({ strokeWidth: 0.3 }) + } } }) } if (selected?.length > 0) { selected.forEach((obj) => { - if (obj.type === 'QPolygon' && currentMenu !== MENU.MODULE_CIRCUIT_SETTING.BASIC_SETTING) { + if (obj.type === 'QPolygon') { obj.set({ stroke: 'red' }) - obj.bringToFront() + if (obj.name === POLYGON_TYPE.MODULE && currentMenu === MENU.MODULE_CIRCUIT_SETTING.BASIC_SETTING) { + //모듈 선택시 라인 두께 변경 + obj.set({ strokeWidth: 3 }) + } } }) } diff --git a/src/hooks/useEstimate.js b/src/hooks/useEstimate.js index c64bdfac..3536fda9 100644 --- a/src/hooks/useEstimate.js +++ b/src/hooks/useEstimate.js @@ -31,6 +31,8 @@ export function useEstimate() { * @param {Object} estimateParam - 견적서 저장 데이터 */ const saveEstimate = async (estimateParam) => { + console.log('managementState', managementState) + const userId = loginUserState.userId const saleStoreId = managementState.saleStoreId const objectNo = currentCanvasPlan.objectNo diff --git a/src/hooks/usePlan.js b/src/hooks/usePlan.js index 74c03296..15783727 100644 --- a/src/hooks/usePlan.js +++ b/src/hooks/usePlan.js @@ -407,12 +407,21 @@ export function usePlan(params = {}) { } }) } else { - if (!currentCanvasPlan || currentCanvasPlan.id !== newCurrentId) { - await saveCanvas(true) - clearRecoilState() - } - setCurrentCanvasPlan(plans.find((plan) => plan.id === newCurrentId)) - setPlans((plans) => plans.map((plan) => ({ ...plan, isCurrent: plan.id === newCurrentId }))) + swalFire({ + text: getMessage('plan.message.confirm.save'), + type: 'confirm', + confirmFn: async () => { + //저장 전에 플랜이 이동되어 state가 변경되는 이슈가 있음 + await saveCanvas(true) + clearRecoilState() + setCurrentCanvasPlan(plans.find((plan) => plan.id === newCurrentId)) + setPlans((plans) => plans.map((plan) => ({ ...plan, isCurrent: plan.id === newCurrentId }))) + }, + denyFn: async () => { + setCurrentCanvasPlan(plans.find((plan) => plan.id === newCurrentId)) + setPlans((plans) => plans.map((plan) => ({ ...plan, isCurrent: plan.id === newCurrentId }))) + }, + }) } } @@ -447,9 +456,25 @@ export function usePlan(params = {}) { * @param {string} objectNo - 물건번호 */ const handleAddPlan = async (userId, objectNo) => { + let isSelected = false + if (currentCanvasPlan?.id) { - await saveCanvas(false) + swalFire({ + text: getMessage('plan.message.confirm.save'), + type: 'confirm', + confirmFn: async () => { + //저장 전에 플랜이 이동되어 state가 변경되는 이슈가 있음 + await saveCanvas(true) + handleAddPlanCopyConfirm(userId, objectNo) + }, + denyFn: async () => { + handleAddPlanCopyConfirm(userId, objectNo) + }, + }) } + } + + const handleAddPlanCopyConfirm = async (userId, objectNo) => { if (JSON.parse(currentCanvasData()).objects.length > 0) { swalFire({ text: `Plan ${currentCanvasPlan.planNo} ` + getMessage('plan.message.confirm.copy'), @@ -471,7 +496,6 @@ export function usePlan(params = {}) { setIsGlobalLoading(false) } } - /** * 물건번호(object) plan 삭제 (canvas 삭제 전 planNo 삭제) * diff --git a/src/locales/ja.json b/src/locales/ja.json index b4b66991..e4804401 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -153,9 +153,10 @@ "modal.module.basic.setting.pitch.module.column.amount": "列数", "modal.module.basic.setting.pitch.module.column.margin": "左右間隔", "modal.module.basic.setting.prev": "前に戻る", - "modal.module.basic.setting.row.batch": "段・列数指定配置", + "modal.module.basic.setting.row.batch": "レイアウト指定", "modal.module.basic.setting.passivity.placement": "手動配置", "modal.module.basic.setting.auto.placement": "自動配置", + "modal.module.basic.setting.auto.row.batch": "自動レイアウト指定", "plan.menu.module.circuit.setting.circuit.trestle.setting": "回路設定", "modal.circuit.trestle.setting": "回路設定", "modal.circuit.trestle.setting.alloc.trestle": "架台配置", @@ -345,9 +346,9 @@ "modal.actual.size.setting.not.exist.size": "実際の寸法の長さを入力してください", "modal.actual.size.setting.plane.size.length": "廊下寸法の長さ", "modal.actual.size.setting.actual.size.length": "実寸長", - "plan.message.confirm.save": "プラン保存しますか??", - "plan.message.confirm.copy": "プランコピーしますか??", - "plan.message.confirm.delete": "プラン削除しますか??", + "plan.message.confirm.save": "プラン保存しますか?", + "plan.message.confirm.copy": "プランコピーしますか?", + "plan.message.confirm.delete": "プラン削除しますか?", "plan.message.save": "保存されました。", "plan.message.delete": "削除されました。", "plan.message.leave": "物件状況(リスト)に移動しますか? [はい]を選択した場合は保存して移動します。", @@ -943,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": "価格登録初期化されますがよろしいですか?", @@ -1074,9 +1077,9 @@ "module.layout.setup.max.count.multiple": "モジュール{0}の単体での最大段数は{1}、最大列数は{2}です。 (JA)", "roofAllocation.not.found": "割り当てる屋根がありません。 (JA)", "modal.module.basic.setting.module.placement.max.size.check": "屋根材別モジュールの単体の単体での最大段数、2種混合の段数を確認して下さい", - "modal.module.basic.setting.module.placement.max.row": "単体での最大段数", - "modal.module.basic.setting.module.placement.max.rows.multiple": "2種混合時の最大段数", + "modal.module.basic.setting.module.placement.max.row": "単体で\rの最大段数", + "modal.module.basic.setting.module.placement.max.rows.multiple": "2種混合時\rの最大段数", "modal.module.basic.setting.module.placement.mix.asg.yn.error": "混合インストール不可能なモジュールです。 (JA)", "modal.module.basic.setting.module.placement.mix.asg.yn": "混合", - "modal.module.basic.setting.layoutpassivity.placement": "layout配置 (JA)" + "modal.module.basic.setting.module.placement.over.max.row": "{0} 最大段数超過しました。最大段数表を参考にしてください。" } diff --git a/src/locales/ko.json b/src/locales/ko.json index 43b45c52..3374c1b9 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -156,7 +156,8 @@ "modal.module.basic.setting.prev": "이전", "modal.module.basic.setting.row.batch": "단·열수 지정 배치", "modal.module.basic.setting.passivity.placement": "수동 배치", - "modal.module.basic.setting.auto.placement": "설정값으로 자동 배치", + "modal.module.basic.setting.auto.placement": "자동 배치", + "modal.module.basic.setting.auto.row.batch": "자동 단·열수 지정 배치 ", "plan.menu.module.circuit.setting.circuit.trestle.setting": "회로설정", "modal.circuit.trestle.setting": "회로설정", "modal.circuit.trestle.setting.alloc.trestle": "가대할당", @@ -944,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": "가격등록을 초기화 하시겠습니까?", @@ -1079,5 +1082,5 @@ "modal.module.basic.setting.module.placement.max.rows.multiple": "2종 혼합 최대단수", "modal.module.basic.setting.module.placement.mix.asg.yn.error": "혼합 설치 불가능한 모듈입니다.", "modal.module.basic.setting.module.placement.mix.asg.yn": "혼합", - "modal.module.basic.setting.layoutpassivity.placement": "레이아웃 배치" + "modal.module.basic.setting.module.placement.over.max.row": "{0}의 최대단수를 초과했습니다. 최대단수표를 참고해 주세요." }