This commit is contained in:
Jaeyoung Lee 2025-06-13 14:44:16 +09:00
commit 0430be1945
62 changed files with 1007 additions and 280 deletions

View File

@ -1,8 +1,10 @@
NEXT_PUBLIC_RUN_MODE="development"
NEXT_PUBLIC_API_SERVER_PATH="https://dev-api.hanasys.jp" NEXT_PUBLIC_API_SERVER_PATH="https://dev-api.hanasys.jp"
NEXT_PUBLIC_HOST_URL="//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" NEXT_PUBLIC_API_HOST_URL="https://dev.hanasys.jp"
SESSION_SECRET="i3iHH1yp2/2SpQSIySQ4bpyc4g0D+zCF9FAn5xUG0+Y=" SESSION_SECRET="i3iHH1yp2/2SpQSIySQ4bpyc4g0D+zCF9FAn5xUG0+Y="
@ -25,4 +27,6 @@ AWS_REGION="ap-northeast-1"
AMPLIFY_BUCKET="files.hanasys.jp" AMPLIFY_BUCKET="files.hanasys.jp"
AWS_ACCESS_KEY_ID="AKIA3K4QWLZHFZRJOM2E" AWS_ACCESS_KEY_ID="AKIA3K4QWLZHFZRJOM2E"
AWS_SECRET_ACCESS_KEY="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" AWS_SECRET_ACCESS_KEY="Cw87TjKwnTWRKgORGxYiFU6GUTgu25eUw4eLBNcA"
NEXT_PUBLIC_AWS_S3_BASE_URL="//files.hanasys.jp" NEXT_PUBLIC_AWS_S3_BASE_URL="//files.hanasys.jp"
S3_PROFILE="dev"

32
.env.local.dev Normal file
View File

@ -0,0 +1,32 @@
NEXT_PUBLIC_RUN_MODE="local.dev"
NEXT_PUBLIC_API_SERVER_PATH="https://dev-api.hanasys.jp"
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="
# NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_bV5zuYMyyIYFlOb3"
# NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_yAS4QDalL9jgQ7vS"
NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_a0FLEK6M2oTpXInK"
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"
# 테스트용
# 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"
S3_PROFILE="dev"

32
.env.localhost Normal file
View File

@ -0,0 +1,32 @@
NEXT_PUBLIC_RUN_MODE="local"
NEXT_PUBLIC_API_SERVER_PATH="https://dev-api.hanasys.jp"
NEXT_PUBLIC_HOST_URL="//1.248.227.176:4000"
NEXT_PUBLIC_API_HOST_URL="http://localhost:3000"
SESSION_SECRET="i3iHH1yp2/2SpQSIySQ4bpyc4g0D+zCF9FAn5xUG0+Y="
# NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_bV5zuYMyyIYFlOb3"
# NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_yAS4QDalL9jgQ7vS"
NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_a0FLEK6M2oTpXInK"
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"
# 테스트용
# 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"
S3_PROFILE="dev"

View File

@ -1,8 +1,10 @@
NEXT_PUBLIC_RUN_MODE="production"
NEXT_PUBLIC_API_SERVER_PATH="https://api.hanasys.jp/" NEXT_PUBLIC_API_SERVER_PATH="https://api.hanasys.jp/"
NEXT_PUBLIC_HOST_URL="//1.248.227.176:4000" NEXT_PUBLIC_HOST_URL="//1.248.227.176:4000"
NEXT_PUBLIC_API_HOST_URL="https://www.hanasys.jp/" NEXT_PUBLIC_API_HOST_URL="https://www.hanasys.jp"
SESSION_SECRET="i3iHH1yp2/2SpQSIySQ4bpyc4g0D+zCF9FAn5xUG0+Y=" SESSION_SECRET="i3iHH1yp2/2SpQSIySQ4bpyc4g0D+zCF9FAn5xUG0+Y="
@ -15,8 +17,17 @@ NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secr
NEXT_PUBLIC_Q_ORDER_AUTO_LOGIN_URL="http://q-order-stg.q-cells.jp:8120/eos/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" NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL="http://q-musubi-stg.q-cells.jp:8120/qm/login/autoLogin"
AWS_REGION="ap-northeast-2" # AWS_REGION="ap-northeast-2"
AMPLIFY_BUCKET="interplug" # AMPLIFY_BUCKET="interplug"
AWS_ACCESS_KEY_ID="AKIAVWMWJCUXFHEAZ4FR" # AWS_ACCESS_KEY_ID="AKIAVWMWJCUXFHEAZ4FR"
AWS_SECRET_ACCESS_KEY="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" # AWS_SECRET_ACCESS_KEY="NDzSvPUo4/ErpPOEs1eZAnoUBilc1FL7YaoHkqe4"
NEXT_PUBLIC_AWS_S3_BASE_URL="//files.hanasys.jp" # NEXT_PUBLIC_AWS_S3_BASE_URL="//files.hanasys.jp"
# 실제 일본 서버
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"
S3_PROFILE="prd"

View File

@ -6,7 +6,7 @@ module.exports = {
instances: 1, instances: 1,
exec_mode: 'fork', exec_mode: 'fork',
env: { env: {
NODE_ENV: 'development', PORT: 5010,
}, },
}, },
], ],

View File

@ -0,0 +1,13 @@
module.exports = {
apps: [
{
name: 'qcast-front-local-development',
script: 'node_modules/next/dist/bin/next',
instances: 1,
exec_mode: 'fork',
env: {
PORT: 5000,
},
},
],
}

View File

@ -3,11 +3,14 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev", "dev": "env-cmd -f .env.localhost next dev",
"build": "next build", "local:dev": "env-cmd -f .env.local.dev next dev",
"start:cluster1": "next start -p 5000", "build": "env-cmd -f .env.production next build",
"start:cluster2": "next start -p 5001", "build:dev": "env-cmd -f .env.development next build",
"start:dev": "next start -p 5010", "build:local.dev": "env-cmd -f .env.local.dev next build",
"start:cluster1": "env-cmd -f .env.production next start -p 5000",
"start:cluster2": "env-cmd -f .env.production next start -p 5001",
"start:dev": "env-cmd -f .env.development next start -p 5010",
"lint": "next lint", "lint": "next lint",
"serve": "node server.js" "serve": "node server.js"
}, },
@ -18,10 +21,12 @@
"big.js": "^6.2.2", "big.js": "^6.2.2",
"chart.js": "^4.4.6", "chart.js": "^4.4.6",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"env-cmd": "^10.1.0",
"fabric": "^5.3.0", "fabric": "^5.3.0",
"framer-motion": "^11.2.13", "framer-motion": "^11.2.13",
"fs": "^0.0.1-security", "fs": "^0.0.1-security",
"iron-session": "^8.0.2", "iron-session": "^8.0.2",
"jimp": "^1.6.0",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"mathjs": "^13.0.2", "mathjs": "^13.0.2",
"mssql": "^11.0.1", "mssql": "^11.0.1",

13
prd1.ecosystem.config.js Normal file
View File

@ -0,0 +1,13 @@
module.exports = {
apps: [
{
name: 'qcast-front-production-1',
script: 'node_modules/next/dist/bin/next',
instances: 1,
exec_mode: 'fork',
env: {
PORT: 5000,
},
},
],
}

13
prd2.ecosystem.config.js Normal file
View File

@ -0,0 +1,13 @@
module.exports = {
apps: [
{
name: 'qcast-front-production-2',
script: 'node_modules/next/dist/bin/next',
instances: 1,
exec_mode: 'fork',
env: {
PORT: 5001,
},
},
],
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -11,9 +11,10 @@ const s3 = new S3Client({
}) })
const uploadImage = async (file) => { const uploadImage = async (file) => {
console.log('🚀 ~ uploadImage ~ file:', file)
const Body = Buffer.from(await file.arrayBuffer()) const Body = Buffer.from(await file.arrayBuffer())
const Key = `cads/${file.name}` const Key = `cads/${file.name}`
const ContentType = file.ContentType const ContentType = 'image/png'
await s3.send( await s3.send(
new PutObjectCommand({ new PutObjectCommand({

View File

@ -1,7 +1,8 @@
import { NextResponse } from 'next/server' import { NextResponse } from 'next/server'
import { S3Client, PutObjectCommand, DeleteObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3' import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3'
import sharp from 'sharp'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { Jimp } from 'jimp'
const Bucket = process.env.AMPLIFY_BUCKET const Bucket = process.env.AMPLIFY_BUCKET
const s3 = new S3Client({ const s3 = new S3Client({
region: process.env.AWS_REGION, region: process.env.AWS_REGION,
@ -23,8 +24,6 @@ const checkArea = (obj) => {
const cropImage = async (Key, width, height, left, top) => { const cropImage = async (Key, width, height, left, top) => {
try { try {
const checkResult = checkArea({ width, height, left, top })
// Get the image from S3 // Get the image from S3
const { Body } = await s3.send( const { Body } = await s3.send(
new GetObjectCommand({ new GetObjectCommand({
@ -33,28 +32,45 @@ const cropImage = async (Key, width, height, left, top) => {
}), }),
) )
// Convert stream to buffer
const chunks = [] const chunks = []
for await (const chunk of Body) { for await (const chunk of Body) {
chunks.push(chunk) chunks.push(chunk)
} }
const imageBuffer = Buffer.concat(chunks) const buffer = Buffer.concat(chunks)
let processedImage const image = await Jimp.read(buffer)
if (!checkResult) {
processedImage = await sharp(imageBuffer).toBuffer() image.autocrop({ tolerance: 0.0002, leaveBorder: 10 })
} else { return await image.getBuffer('image/png')
processedImage = await sharp(imageBuffer)
.extract({ // Convert stream to buffer
width: parseInt(width), // const chunks = []
height: parseInt(height), // for await (const chunk of Body) {
left: parseInt(left), // chunks.push(chunk)
top: parseInt(top), // }
}) // const imageBuffer = Buffer.concat(chunks)
.png()
.toBuffer() // const image = await Jimp.read(Body)
}
return processedImage // if (!checkResult) {
// processedImage = await image.toBuffer()
// }
//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) { } catch (error) {
console.error('Error processing image:', error) console.error('Error processing image:', error)
throw error throw error
@ -96,7 +112,7 @@ export async function POST(req) {
/** /**
* 크롭 이미지 이름을 결정한다. * 크롭 이미지 이름을 결정한다.
*/ */
const Key = `Drawing/${objectNo}_${planNo}_${type}.png` const Key = `Drawing/${process.env.S3_PROFILE}/${objectNo}_${planNo}_${type}.png`
/** /**
* 크롭이 완료된 이미지를 업로드한다. * 크롭이 완료된 이미지를 업로드한다.

View File

@ -0,0 +1,59 @@
import { NextResponse } from 'next/server'
import { S3Client, CopyObjectCommand, 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,
},
})
export async function POST(req) {
const { objectNo, planNo, newObjectNo, newPlanNo } = await req.json()
const responseArray = []
//견적서1 번 이미지
const isExistImage1 = await s3.send(
new GetObjectCommand({
Bucket,
Key: `Drawing/${process.env.S3_PROFILE}/${objectNo}_${planNo}_1.png`,
}),
)
//견적서2 번 이미지
const isExistImage2 = await s3.send(
new GetObjectCommand({
Bucket,
Key: `Drawing/${process.env.S3_PROFILE}/${objectNo}_${planNo}_2.png`,
}),
)
//견적서1,2 번 이미지 둘다 있어야함
if (isExistImage1.$metadata.httpStatusCode === 200 && isExistImage2.$metadata.httpStatusCode === 200) {
//견적서1 번 이미지 복사
const copyCommand = new CopyObjectCommand({
Bucket,
CopySource: encodeURI(`${Bucket}/Drawing/${process.env.S3_PROFILE}/${objectNo}_${planNo}_1.png`),
Key: `Drawing/${process.env.S3_PROFILE}/${newObjectNo}_${newPlanNo}_1.png`,
})
const response = await s3.send(copyCommand)
const copyCommand2 = new CopyObjectCommand({
Bucket,
CopySource: encodeURI(`${Bucket}/Drawing/${process.env.S3_PROFILE}/${objectNo}_${planNo}_2.png`),
Key: `Drawing/${process.env.S3_PROFILE}/${newObjectNo}_${newPlanNo}_2.png`,
})
const response2 = await s3.send(copyCommand2)
responseArray.push(response, response2)
return NextResponse.json({ message: '견적서 이미지 복사 성공', responseArray }, { status: 200 })
} else {
return NextResponse.json({ message: '견적서 이미지 복사 실패(존재하지 않는 이미지)', responseArray }, { status: 400 })
}
}

View File

@ -45,8 +45,19 @@ const FloorPlanProvider = ({ children }) => {
// const pathname = usePathname() // const pathname = usePathname()
// const setCorrentObjectNo = useSetRecoilState(correntObjectNoState) // const setCorrentObjectNo = useSetRecoilState(correntObjectNoState)
const searchParams = useSearchParams() const searchParams = useSearchParams()
const path = usePathname()
const objectNo = searchParams.get('objectNo') const objectNo = searchParams.get('objectNo')
const pid = searchParams.get('pid') const pid = searchParams.get('pid')
useEffect(() => {
setFloorPlanState((prev) => {
return {
...prev,
objectNo,
pid,
}
})
}, [path])
// useEffect(() => { // useEffect(() => {
// console.log('🚀 ~ useEffect ~ objectNo:') // console.log('🚀 ~ useEffect ~ objectNo:')
// if (pathname === '/floor-plan') { // if (pathname === '/floor-plan') {

View File

@ -210,6 +210,7 @@ export const SAVE_KEY = [
'toFixed', 'toFixed',
'startPoint', 'startPoint',
'endPoint', 'endPoint',
'editable',
'isSortedPoints', 'isSortedPoints',
] ]

View File

@ -14,6 +14,7 @@ import { sessionStore } from '@/store/commonAtom'
import { isObjectNotEmpty } from '@/util/common-utils' import { isObjectNotEmpty } from '@/util/common-utils'
import BoardDetailModal from './community/modal/BoardDetailModal' import BoardDetailModal from './community/modal/BoardDetailModal'
import Config from '@/config/config.export'
export default function MainPage() { export default function MainPage() {
const [sessionState, setSessionState] = useRecoilState(sessionStore) const [sessionState, setSessionState] = useRecoilState(sessionStore)

View File

@ -195,9 +195,7 @@ export default function Qna() {
)) ))
) : ( ) : (
<tr> <tr>
<td colSpan={4} className="al-c"> <td className="al-c no-data" colSpan={5}>{getMessage('common.message.no.data')}</td>
{getMessage('common.message.no.data')}
</td>
</tr> </tr>
)} )}
</tbody> </tbody>

View File

@ -73,7 +73,7 @@ export default function QnaDetailModal({ qnaNo, setOpen, qnaType }) {
<dt>{getMessage('qna.detail.sub.fileList')}</dt> <dt>{getMessage('qna.detail.sub.fileList')}</dt>
{boardDetail.listFile.map((boardFile) => ( {boardDetail.listFile.map((boardFile) => (
<dd key={boardFile.encodeFileNo}> <dd key={boardFile.encodeFileNo}>
<button type="button" className="down" onClick={() => handleFileDown(boardFile.fileNo, 'N')}> <button type="button" className="down" onClick={() => handleFileDown(boardFile.fileNo, 'NO')}>
{boardFile.srcFileNm} {boardFile.srcFileNm}
</button> </button>
</dd> </dd>

View File

@ -13,6 +13,7 @@ import { QcastContext } from '@/app/QcastProvider'
import { useAxios } from '@/hooks/useAxios' import { useAxios } from '@/hooks/useAxios'
import { globalLocaleStore } from '@/store/localeAtom' import { globalLocaleStore } from '@/store/localeAtom'
import { e } from 'mathjs' import { e } from 'mathjs'
import { set } from 'react-hook-form'
export default function QnaRegModal({ setOpen, setReload, searchValue, selectPageBlock }) { export default function QnaRegModal({ setOpen, setReload, searchValue, selectPageBlock }) {
@ -24,9 +25,11 @@ export default function QnaRegModal({ setOpen, setReload, searchValue, selectPag
const [qnaData, setQnaData] = useState([]) const [qnaData, setQnaData] = useState([])
const [closeMdFlg, setCloseMdFlg] = useState(true) const [closeMdFlg, setCloseMdFlg] = useState(true)
const [closeSmFlg, setCloseSmFlg] = useState(true) const [closeSmFlg, setCloseSmFlg] = useState(true)
const [hideSmFlg, setHideSmFlg] = useState(false)
const qnaTypeLgCodeRef = useRef(null) const qnaTypeLgCodeRef = useRef(null)
const qnaTypeMdCodeRef = useRef(null) const qnaTypeMdCodeRef = useRef(null)
const qnaTypeSmCodeRef = useRef(null) const qnaTypeSmCodeRef = useRef(null)
const qstMail = useRef(null);
const regUserNmRef = useRef(null) const regUserNmRef = useRef(null)
const regUserTelNoRef = useRef(null) const regUserTelNoRef = useRef(null)
const titleRef = useRef(null) const titleRef = useRef(null)
@ -65,11 +68,12 @@ let fileCheck = false;
const initQnaReg = async () => { const initQnaReg = async () => {
qstMail.current.value = ''
regUserNmRef.current.value = '' regUserNmRef.current.value = ''
regUserTelNoRef.current.value = '' regUserTelNoRef.current.value = ''
qnaTypeLgCodeRef.current.setValue(); qnaTypeLgCodeRef.current.setValue();
qnaTypeMdCodeRef.current.setValue(); qnaTypeMdCodeRef.current.setValue();
qnaTypeSmCodeRef.current.setValue(); qnaTypeSmCodeRef.current?.setValue();
titleRef.current.value = '' titleRef.current.value = ''
contentsRef.current.value = '' contentsRef.current.value = ''
@ -110,7 +114,7 @@ let fileCheck = false;
setQnaData({ ...qnaData, qnaClsLrgCd:e.clCode}) setQnaData({ ...qnaData, qnaClsLrgCd:e.clCode})
setCloseMdFlg(false) setCloseMdFlg(false)
qnaTypeMdCodeRef.current.setValue(); qnaTypeMdCodeRef.current.setValue();
qnaTypeSmCodeRef.current.setValue(); qnaTypeSmCodeRef.current?.setValue();
} }
} }
@ -129,10 +133,20 @@ let fileCheck = false;
} }
}) })
setQnaTypeSmCodeList(codeList)
setQnaData({ ...qnaData, qnaClsMidCd: e.clCode }) setQnaData({ ...qnaData, qnaClsMidCd: e.clCode })
setCloseSmFlg(false) setCloseSmFlg(false)
qnaTypeSmCodeRef.current.setValue(); setQnaTypeSmCodeList(codeList)
qnaTypeSmCodeRef.current?.setValue();
if(codeList.length > 0) {
setHideSmFlg(false)
}else{
setHideSmFlg(true)
}
} }
@ -148,7 +162,7 @@ let fileCheck = false;
if(!fileCheck) return; if(!fileCheck) return;
fileUploadProps.uploadFiles.forEach((file) => { fileUploadProps.uploadFiles.forEach((file) => {
console.log("file::::::::",file) //console.log("file::::::::",file)
formData.push(file) formData.push(file)
}) })
@ -156,6 +170,16 @@ let fileCheck = false;
fileCheck = false; fileCheck = false;
} }
const isValidEmail = (email) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};
const isEmpty = (value) => {
return value === null || value === undefined || value.trim() === "";
};
const handleQnaSubmit = async () => { const handleQnaSubmit = async () => {
// //
@ -164,13 +188,22 @@ let fileCheck = false;
let regUserNm = qnaData?.regUserNm??''; let regUserNm = qnaData?.regUserNm??'';
if (regUserNm.trim().length === 0) { if (!isValidEmail(qnaData.qstMail)) {
qstMail.current.focus();
swalFire({
title: getMessage('qna.reg.alert.require.qstMail'),
icon: 'warning',
});
return;
}
regUserNmRef.current.value = '';
if (isEmpty(regUserNm)) {
regUserNmRef.current.value = '';
regUserNmRef.current.focus() regUserNmRef.current.focus()
swalFire({ swalFire({
text: getMessage('qna.reg.alert.require.regUserNm'), title: getMessage('qna.reg.alert.require.regUserNm'),
type: 'alert', icon: 'warning',
}) })
return false return false
} }
@ -178,35 +211,35 @@ let fileCheck = false;
let qnaClsLrgCd = qnaData?.qnaClsLrgCd??''; let qnaClsLrgCd = qnaData?.qnaClsLrgCd??'';
let qnaClsMidCd = qnaData?.qnaClsMidCd??''; let qnaClsMidCd = qnaData?.qnaClsMidCd??'';
if (qnaClsLrgCd.trim().length === 0 || qnaClsMidCd.trim().length === 0 ) { if (isEmpty(qnaClsLrgCd) || isEmpty(qnaClsMidCd) ) {
(qnaClsLrgCd.trim().length === 0)?qnaTypeLgCodeRef.current.focus():qnaTypeMdCodeRef.current.focus() (isEmpty(qnaClsLrgCd))?qnaTypeLgCodeRef.current.focus():qnaTypeMdCodeRef.current.focus()
swalFire({ swalFire({
text: getMessage('qna.reg.alert.select.type'), title: getMessage('qna.reg.alert.select.type'),
type: 'alert', icon: 'warning',
}) })
return false return false
} }
let title = qnaData?.title??''; let title = qnaData?.title??'';
if (title.trim().length === 0) { if (isEmpty(title)) {
titleRef.current.value = ''; titleRef.current.value = '';
titleRef.current.focus() titleRef.current.focus()
swalFire({ swalFire({
text: getMessage('qna.reg.alert.require.title'), title: getMessage('qna.reg.alert.require.title'),
type: 'alert', icon: 'warning',
}) })
return false return false
} }
//console.log("5::::",qnaData) //console.log("5::::",qnaData)
let contents = qnaData?.contents??''; let contents = qnaData?.contents??'';
if (contents.trim().length === 0) { if (isEmpty(contents)) {
contentsRef.current.value = ''; contentsRef.current.value = '';
contentsRef.current.focus() contentsRef.current.focus()
swalFire({ swalFire({
text: getMessage('qna.reg.alert.require.contents'), title: getMessage('qna.reg.alert.require.contents'),
type: 'alert', icon: 'warning',
}) })
return false return false
} }
@ -310,7 +343,13 @@ let fileCheck = false;
<th>{getMessage('qna.list.header.regNm')}</th> <th>{getMessage('qna.list.header.regNm')}</th>
<td><input type="text" className="input-light" value={sessionState?.userNm || ''} readOnly /></td> <td><input type="text" className="input-light" value={sessionState?.userNm || ''} readOnly /></td>
<th>E-Mail<span className="red">*</span></th> <th>E-Mail<span className="red">*</span></th>
<td ><input type="text" className="input-light" value={sessionState?.email || ''} readOnly /></td> <td ><input type="text" className="input-light" required
ref={qstMail}
value={qnaData?.qstMail || ''}
onChange={(e) => setQnaData({...qnaData, qstMail: e.target.value })}
onBlur={(e) => setQnaData({ ...qnaData, qstMail: e.target.value })} />
</td>
<th>{getMessage('qna.reg.header.regDt')}</th> <th>{getMessage('qna.reg.header.regDt')}</th>
<td>{dayjs(new Date()).format('YYYY-MM-DD')}</td> <td>{dayjs(new Date()).format('YYYY-MM-DD')}</td>
</tr> </tr>
@ -366,6 +405,7 @@ let fileCheck = false;
/> />
</div> </div>
<div className="select-wrap" > <div className="select-wrap" >
{!hideSmFlg && (
<Select name="" ref={qnaTypeSmCodeRef} <Select name="" ref={qnaTypeSmCodeRef}
options={qnaTypeSmCodeList} options={qnaTypeSmCodeList}
placeholder="Select" placeholder="Select"
@ -374,8 +414,8 @@ let fileCheck = false;
getOptionValue={(x) => x.clCode} getOptionValue={(x) => x.clCode}
isClearable={false} isClearable={false}
isSearchable={false} isSearchable={false}
isDisabled={closeSmFlg} isDisabled={closeSmFlg}
/> />)}
</div> </div>
</div> </div>
<div className="input-wrap mt5"> <div className="input-wrap mt5">
@ -401,8 +441,8 @@ let fileCheck = false;
</div> </div>
<div className="footer-btn-wrap"> <div className="footer-btn-wrap">
{isBtnDisable === false && <button className="btn-origin grey mr5" onClick={handleQnaSubmit}>{getMessage("qna.reg.header.save")}</button>} {isBtnDisable === false && <button className="btn-origin navy mr5" onClick={handleQnaSubmit}>{getMessage("qna.reg.header.save")}</button>}
<button className="btn-origin navy" onClick={() => { <button className="btn-origin grey" onClick={() => {
setOpen(false) setOpen(false)
}}>{getMessage("board.sub.btn.close")}</button> }}>{getMessage("board.sub.btn.close")}</button>
</div> </div>

View File

@ -154,7 +154,7 @@ export default function Estimate({}) {
useEffect(() => { useEffect(() => {
// console.log('🚀 ~ Estimate ~ selectedPlan:', selectedPlan) // console.log('🚀 ~ Estimate ~ selectedPlan:', selectedPlan)
if (selectedPlan) initEstimate(selectedPlan.planNo) if (selectedPlan) initEstimate(selectedPlan?.planNo?? currentPid)
}, [selectedPlan]) }, [selectedPlan])
useEffect(() => { useEffect(() => {
@ -1281,7 +1281,7 @@ export default function Estimate({}) {
<div className="estimate-box"> <div className="estimate-box">
<div className="estimate-tit">{getMessage('estimate.detail.objectNo')}</div> <div className="estimate-tit">{getMessage('estimate.detail.objectNo')}</div>
<div className="estimate-name"> <div className="estimate-name">
{currentObjectNo} (Plan No: {planNo}) {currentObjectNo} (Plan No: {currentPid})
</div> </div>
</div> </div>
<div className="estimate-box"> <div className="estimate-box">

View File

@ -76,8 +76,12 @@ export default function CanvasFrame() {
} }
initEvent() initEvent()
}) })
} else {
setSelectedMenu(null)
} }
Object.keys(currentCanvasPlan).length > 0 && canvas && handleModuleSelectionTotal() Object.keys(currentCanvasPlan).length > 0 && canvas && handleModuleSelectionTotal()
} else {
setSelectedMenu(null)
} }
gridInit() gridInit()
} }

View File

@ -4,7 +4,7 @@ import { useContext, useEffect, useState } from 'react'
import { usePathname, useRouter, useSearchParams } from 'next/navigation' import { usePathname, useRouter, useSearchParams } from 'next/navigation'
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil' import { useRecoilState, useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
@ -50,6 +50,7 @@ import JA from '@/locales/ja.json'
import { QcastContext } from '@/app/QcastProvider' import { QcastContext } from '@/app/QcastProvider'
import { useRoofFn } from '@/hooks/common/useRoofFn' import { useRoofFn } from '@/hooks/common/useRoofFn'
import { usePolygon } from '@/hooks/usePolygon' import { usePolygon } from '@/hooks/usePolygon'
import { useTrestle } from '@/hooks/module/useTrestle'
export default function CanvasMenu(props) { export default function CanvasMenu(props) {
const { selectedMenu, setSelectedMenu } = props const { selectedMenu, setSelectedMenu } = props
const pathname = usePathname() const pathname = usePathname()
@ -67,6 +68,7 @@ export default function CanvasMenu(props) {
const globalLocale = useRecoilValue(globalLocaleStore) const globalLocale = useRecoilValue(globalLocaleStore)
const canvas = useRecoilValue(canvasState) const canvas = useRecoilValue(canvasState)
const { handleZoomClear, handleZoom } = useCanvasEvent() const { handleZoomClear, handleZoom } = useCanvasEvent()
const { setAllModuleSurfaceIsComplete, isAllComplete } = useTrestle()
const { handleMenu } = useMenu() const { handleMenu } = useMenu()
// const urlParams = useSearchParams() // const urlParams = useSearchParams()
const { handleEstimateSubmit, fetchSetting, estimateContextState, setEstimateContextState } = useEstimateController() const { handleEstimateSubmit, fetchSetting, estimateContextState, setEstimateContextState } = useEstimateController()
@ -96,7 +98,7 @@ export default function CanvasMenu(props) {
const [lockButtonStyle, setLockButtonStyle] = useState('') // const [lockButtonStyle, setLockButtonStyle] = useState('') //
const setFloorPlanObjectNo = useSetRecoilState(floorPlanObjectState) // const setFloorPlanObjectNo = useSetRecoilState(floorPlanObjectState) //
const resetCommonUtils = useResetRecoilState(commonUtilsState)
// //
const { objectNo, pid } = floorPlanState const { objectNo, pid } = floorPlanState
@ -164,6 +166,7 @@ export default function CanvasMenu(props) {
} }
const onClickNav = async (menu) => { const onClickNav = async (menu) => {
resetCommonUtils()
switch (menu.type) { switch (menu.type) {
case 'drawing': case 'drawing':
swalFire({ swalFire({
@ -194,6 +197,7 @@ export default function CanvasMenu(props) {
confirmFn: () => { confirmFn: () => {
// //
setAllModuleSurfaceIsComplete(false)
const moduleSurfacesArray = canvas const moduleSurfacesArray = canvas
.getObjects() .getObjects()
.filter((obj) => [POLYGON_TYPE.MODULE_SETUP_SURFACE, POLYGON_TYPE.MODULE, POLYGON_TYPE.OBJECT_SURFACE].includes(obj.name)) .filter((obj) => [POLYGON_TYPE.MODULE_SETUP_SURFACE, POLYGON_TYPE.MODULE, POLYGON_TYPE.OBJECT_SURFACE].includes(obj.name))
@ -233,8 +237,12 @@ export default function CanvasMenu(props) {
await reloadCanvasStatus(objectNo, pid) await reloadCanvasStatus(objectNo, pid)
break break
case 'estimate': case 'estimate':
if (!isAllComplete()) {
swalFire({ text: getMessage('estimate.menu.move.valid1') })
return
}
setIsGlobalLoading(true) setIsGlobalLoading(true)
promiseGet({ url: `/api/estimate/${objectNo}/${selectedPlan.planNo}/detail` }).then((res) => { promiseGet({ url: `/api/estimate/${objectNo}/${selectedPlan?.planNo ?? pid}/detail` }).then((res) => {
if (res.status === 200) { if (res.status === 200) {
const estimateDetail = res.data const estimateDetail = res.data
if (estimateDetail.estimateDate !== null) { if (estimateDetail.estimateDate !== null) {
@ -242,7 +250,7 @@ export default function CanvasMenu(props) {
setCurrentMenu(menu.title) setCurrentMenu(menu.title)
setFloorPlanObjectNo({ floorPlanObjectNo: objectNo }) setFloorPlanObjectNo({ floorPlanObjectNo: objectNo })
setIsGlobalLoading(false) setIsGlobalLoading(false)
router.push(`/floor-plan/estimate/5?pid=${selectedPlan.planNo}&objectNo=${objectNo}`) router.push(`/floor-plan/estimate/5?pid=${selectedPlan?.planNo ?? pid}&objectNo=${objectNo}`)
if (pathname === '/floor-plan/estimate/5') { if (pathname === '/floor-plan/estimate/5') {
setIsGlobalLoading(false) setIsGlobalLoading(false)
} }
@ -255,13 +263,13 @@ export default function CanvasMenu(props) {
break break
case 'simulation': case 'simulation':
setIsGlobalLoading(true) setIsGlobalLoading(true)
promiseGet({ url: `/api/estimate/${objectNo}/${selectedPlan.planNo}/detail` }).then((res) => { promiseGet({ url: `/api/estimate/${objectNo}/${selectedPlan?.planNo ?? pid}/detail` }).then((res) => {
if (res.status === 200) { if (res.status === 200) {
const estimateDetail = res.data const estimateDetail = res.data
if (estimateDetail.estimateDate !== null && estimateDetail.docNo) { if (estimateDetail.estimateDate !== null && estimateDetail.docNo) {
setSelectedMenu(menu.type) setSelectedMenu(menu.type)
setCurrentMenu(menu.title) setCurrentMenu(menu.title)
router.push(`/floor-plan/simulator/6?pid=${selectedPlan.planNo}&objectNo=${objectNo}`) router.push(`/floor-plan/simulator/6?pid=${selectedPlan?.planNo ?? pid}&objectNo=${objectNo}`)
if (pathname === '/floor-plan/simulator/6') { if (pathname === '/floor-plan/simulator/6') {
setIsGlobalLoading(false) setIsGlobalLoading(false)
} }

View File

@ -5,9 +5,10 @@ import { useEffect } from 'react'
import { useMessage } from '@/hooks/useMessage' import { useMessage } from '@/hooks/useMessage'
import useMenu from '@/hooks/common/useMenu' import useMenu from '@/hooks/common/useMenu'
import { canvasState, currentMenuState } from '@/store/canvasAtom' import { canvasState, currentMenuState } from '@/store/canvasAtom'
import { useRecoilState, useRecoilValue } from 'recoil' import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil'
import { subMenusState } from '@/store/menuAtom' import { subMenusState } from '@/store/menuAtom'
import { useCanvasMenu } from '@/hooks/common/useCanvasMenu' import { useCanvasMenu } from '@/hooks/common/useCanvasMenu'
import { commonUtilsState } from '@/store/commonUtilsAtom'
export default function MenuDepth01() { export default function MenuDepth01() {
const canvas = useRecoilValue(canvasState) const canvas = useRecoilValue(canvasState)
@ -16,8 +17,10 @@ export default function MenuDepth01() {
const { selectedMenu, setSelectedMenu } = useCanvasMenu() const { selectedMenu, setSelectedMenu } = useCanvasMenu()
const [currentMenu, setCurrentMenu] = useRecoilState(currentMenuState) const [currentMenu, setCurrentMenu] = useRecoilState(currentMenuState)
const subMenus = useRecoilValue(subMenusState) const subMenus = useRecoilValue(subMenusState)
const resetCommonUtils = useResetRecoilState(commonUtilsState)
const onClickMenu = ({ id, menu }) => { const onClickMenu = ({ id, menu }) => {
resetCommonUtils()
if (menu === currentMenu) { if (menu === currentMenu) {
handleMenu(selectedMenu) handleMenu(selectedMenu)
} else { } else {

View File

@ -12,10 +12,12 @@ import {
import { useRecoilState, useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil' import { useRecoilState, useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil'
import { moduleSelectionDataState, selectedModuleState } from '@/store/selectedModuleOptions' import { moduleSelectionDataState, selectedModuleState } from '@/store/selectedModuleOptions'
import { isObjectNotEmpty } from '@/util/common-utils' import { isObjectNotEmpty } from '@/util/common-utils'
import Image from 'next/image'
const Placement = forwardRef((props, refs) => { const Placement = forwardRef((props, refs) => {
const { getMessage } = useMessage() const { getMessage } = useMessage()
const [useTab, setUseTab] = useState(true) const [useTab, setUseTab] = useState(true)
const [guideType, setGuideType] = useState('batch')
const [isChidoriNotAble, setIsChidoriNotAble] = useState(false) const [isChidoriNotAble, setIsChidoriNotAble] = useState(false)
@ -317,60 +319,91 @@ const Placement = forwardRef((props, refs) => {
</div> </div>
</div> </div>
</div> </div>
<div className="hide-check-guide"> <div className="hide-tab-wrap">
{getMessage('modal.module.basic.setting.module.placement.max.size.check')} <div className="hide-check-guide">
<button className={`arr ${!useTab ? 'act' : ''}`} onClick={() => setUseTab(!useTab)}></button> {getMessage('modal.module.basic.setting.module.placement.info')}
</div> <button className={`arr ${useTab ? 'act' : ''}`} onClick={() => setUseTab(!useTab)}></button>
<div className={`module-table-box mt10 ${useTab ? 'hide' : ''}`}> </div>
<div className="module-table-inner"> <div className={`hide-tab-contents ${!useTab ? 'hide' : ''}`}>
<div className="roof-module-table"> <div className="roof-content-tab-wrap">
<table className=""> <button className={`btn-frame block modal mr5 ${guideType === 'batch' ? 'act' : ''} `} onClick={() => setGuideType('batch')}>
<thead> {getMessage('modal.module.basic.setting.module.placement.info.batch')}
<tr> </button>
<th rowSpan={2} style={{ width: '22%' }}></th> <button className={`btn-frame block modal mr5 ${guideType === 'module' ? 'act' : ''}`} onClick={() => setGuideType('module')}>
{selectedModules && {getMessage('modal.module.basic.setting.module.placement.info.module')}
selectedModules.itemList?.map((item) => ( </button>
// <th colSpan={colspan}>
<th>
<div className="color-wrap">
<span className="color-box" style={{ backgroundColor: item.color }}></span>
<span className="name">{item.itemNm}</span>
</div>
</th>
))}
{colspan > 1 && <th rowSpan={2}>{getMessage('modal.module.basic.setting.module.placement.max.rows.multiple')}</th>}
</tr>
<tr>
{selectedModules &&
selectedModules.itemList?.map((item) => (
<>
<th>{getMessage('modal.module.basic.setting.module.placement.max.row')}</th>
{/* {colspan > 1 && <th>{getMessage('modal.module.basic.setting.module.placement.max.rows.multiple')}</th>} */}
</>
))}
</tr>
</thead>
<tbody>
{moduleSelectionData.roofConstructions.map((item, index) => (
<tr>
<td>
<div className="color-wrap">
<span className="color-box" style={{ backgroundColor: roofOutlineColor(item.addRoof?.index) }}></span>
<span className="name">{item.addRoof?.roofMatlNmJp}</span>
</div>
</td>
{moduleRowColArray[index]?.map((item, index2) => (
<>
<td className="al-c">{item.moduleMaxRows}</td>
{/* {colspan > 1 && <td className="al-c">{item.mixModuleMaxRows}</td>} */}
{colspan > 1 && index2 === moduleRowColArray[index].length - 1 && <td className="al-c">{item.maxRow}</td>}
</>
))}
</tr>
))}
</tbody>
</table>
</div> </div>
{guideType === 'batch' && (
<div className={`roof-warning-wrap mt10`}>
<div className="guide">
{getMessage('modal.module.basic.setting.module.placement.info.batch.content1')}
<br />
{getMessage('modal.module.basic.setting.module.placement.info.batch.content2')}
</div>
<div className="roof-warning-img-wrap">
<div className="roof-warning-img">
<Image src={'/static/images/canvas/roof_warning_correct.png'} width={350} height={198} alt="" />
</div>
<div className="roof-warning-img">
<Image src={'/static/images/canvas/roof_warning_wrong.png'} width={350} height={198} alt="" />
</div>
</div>
</div>
)}
{guideType === 'module' && (
<div className={`module-table-box mt10 ${!useTab ? 'hide' : ''}`}>
<div className="module-table-inner">
<div className="roof-module-table">
<table className="">
<thead>
<tr>
<th rowSpan={2} style={{ width: '22%' }}></th>
{selectedModules &&
selectedModules.itemList?.map((item) => (
// <th colSpan={colspan}>
<th>
<div className="color-wrap">
<span className="color-box" style={{ backgroundColor: item.color }}></span>
<span className="name">{item.itemNm}</span>
</div>
</th>
))}
{colspan > 1 && <th rowSpan={2}>{getMessage('modal.module.basic.setting.module.placement.max.rows.multiple')}</th>}
</tr>
<tr>
{selectedModules &&
selectedModules.itemList?.map((item) => (
<>
<th>{getMessage('modal.module.basic.setting.module.placement.max.row')}</th>
{/* {colspan > 1 && <th>{getMessage('modal.module.basic.setting.module.placement.max.rows.multiple')}</th>} */}
</>
))}
</tr>
</thead>
<tbody>
{moduleSelectionData.roofConstructions.map((item, index) => (
<tr>
<td>
<div className="color-wrap">
<span className="color-box" style={{ backgroundColor: roofOutlineColor(item.addRoof?.index) }}></span>
<span className="name">{item.addRoof?.roofMatlNmJp}</span>
</div>
</td>
{moduleRowColArray[index]?.map((item, index2) => (
<>
<td className="al-c">{item.moduleMaxRows}</td>
{/* {colspan > 1 && <td className="al-c">{item.mixModuleMaxRows}</td>} */}
{colspan > 1 && index2 === moduleRowColArray[index].length - 1 && <td className="al-c">{item.maxRow}</td>}
</>
))}
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
)}
</div> </div>
</div> </div>
</> </>

View File

@ -217,7 +217,7 @@ const Trestle = forwardRef((props, ref) => {
stdWindSpeed: managementState?.standardWindSpeedId ?? '', stdWindSpeed: managementState?.standardWindSpeedId ?? '',
stdSnowLd: managementState?.verticalSnowCover ?? '', stdSnowLd: managementState?.verticalSnowCover ?? '',
inclCd: selectedRoof?.pitch ?? 0, inclCd: selectedRoof?.pitch ?? 0,
roofPitch: Math.round(selectedRoof?.roofPchBase ?? 0), roofPitch: Math.round(hajebichi ?? 0),
}, },
}) })
} }
@ -236,9 +236,9 @@ const Trestle = forwardRef((props, ref) => {
illuminationTp: managementState?.surfaceTypeValue ?? '', illuminationTp: managementState?.surfaceTypeValue ?? '',
instHt: managementState?.installHeight ?? '', instHt: managementState?.installHeight ?? '',
stdWindSpeed: managementState?.standardWindSpeedId ?? '', stdWindSpeed: managementState?.standardWindSpeedId ?? '',
stdSnowLd: +managementState?.verticalSnowCover ?? '', stdSnowLd: managementState?.verticalSnowCover ?? '',
inclCd: selectedRoof?.pitch ?? 0, inclCd: selectedRoof?.pitch ?? 0,
roofPitch: Math.round(selectedRoof?.roofPchBase ?? 0), roofPitch: Math.round(hajebichi ?? 0),
constTp: constructionList[index].constTp, constTp: constructionList[index].constTp,
snowGdPossYn: constructionList[index].snowGdPossYn, snowGdPossYn: constructionList[index].snowGdPossYn,
cvrYn: constructionList[index].cvrYn, cvrYn: constructionList[index].cvrYn,
@ -304,6 +304,7 @@ const Trestle = forwardRef((props, ref) => {
kerabaMargin, kerabaMargin,
roofIndex: roof.index, roofIndex: roof.index,
raft: selectedRaftBase?.clCode, raft: selectedRaftBase?.clCode,
hajebichi: hajebichi,
trestle: { trestle: {
length: lengthBase, length: lengthBase,
hajebichi: hajebichi, hajebichi: hajebichi,

View File

@ -13,10 +13,10 @@ import { useRecoilState } from 'recoil'
import { makersState, modelsState, modelState, pcsCheckState, selectedMakerState, selectedModelsState, seriesState } from '@/store/circuitTrestleAtom' import { makersState, modelsState, modelState, pcsCheckState, selectedMakerState, selectedModelsState, seriesState } from '@/store/circuitTrestleAtom'
import { POLYGON_TYPE } from '@/common/common' import { POLYGON_TYPE } from '@/common/common'
import { useSwal } from '@/hooks/useSwal' import { useSwal } from '@/hooks/useSwal'
import { canvasState } from '@/store/canvasAtom' import { canvasState, canvasZoomState } from '@/store/canvasAtom'
import { useTrestle } from '@/hooks/module/useTrestle' import { useTrestle } from '@/hooks/module/useTrestle'
import { selectedModuleState } from '@/store/selectedModuleOptions' import { moduleSelectionDataState, selectedModuleState } from '@/store/selectedModuleOptions'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { useEstimate } from '@/hooks/useEstimate' import { useEstimate } from '@/hooks/useEstimate'
@ -25,6 +25,8 @@ import { useCanvasPopupStatusController } from '@/hooks/common/useCanvasPopupSta
import { useImgLoader } from '@/hooks/floorPlan/useImgLoader' import { useImgLoader } from '@/hooks/floorPlan/useImgLoader'
import { usePlan } from '@/hooks/usePlan' import { usePlan } from '@/hooks/usePlan'
import { QcastContext } from '@/app/QcastProvider' import { QcastContext } from '@/app/QcastProvider'
import { fabric } from 'fabric'
import { fontSelector } from '@/store/fontAtom'
const ALLOCATION_TYPE = { const ALLOCATION_TYPE = {
AUTO: 'auto', AUTO: 'auto',
@ -37,13 +39,16 @@ export default function CircuitTrestleSetting({ id }) {
const { swalFire } = useSwal() const { swalFire } = useSwal()
const { saveEstimate } = useEstimate() const { saveEstimate } = useEstimate()
const canvas = useRecoilValue(canvasState) const canvas = useRecoilValue(canvasState)
const [canvasZoom, setCanvasZoom] = useRecoilState(canvasZoomState)
const [tabNum, setTabNum] = useState(1) const [tabNum, setTabNum] = useState(1)
const [allocationType, setAllocationType] = useState(ALLOCATION_TYPE.AUTO) const [allocationType, setAllocationType] = useState(ALLOCATION_TYPE.AUTO)
const [circuitAllocationType, setCircuitAllocationType] = useState(1) const [circuitAllocationType, setCircuitAllocationType] = useState(1)
const { managementState, setManagementState } = useContext(GlobalDataContext) const { managementState, setManagementState } = useContext(GlobalDataContext)
const selectedModules = useRecoilValue(selectedModuleState) const selectedModules = useRecoilValue(selectedModuleState)
const { getPcsAutoRecommendList, getPcsVoltageChk, getPcsVoltageStepUpList, getPcsManualConfChk } = useMasterController() const { getPcsAutoRecommendList, getPcsVoltageChk, getPcsVoltageStepUpList, getPcsManualConfChk } = useMasterController()
const flowText = useRecoilValue(fontSelector('flowText'))
const lengthText = useRecoilValue(fontSelector('lengthText'))
const circuitNumberText = useRecoilValue(fontSelector('circuitNumberText'))
// () // ()
const [selectedStepUpValues, setSelectedStepUpValues] = useState({}) const [selectedStepUpValues, setSelectedStepUpValues] = useState({})
@ -55,7 +60,7 @@ export default function CircuitTrestleSetting({ id }) {
const [seletedSubOption, setSeletedSubOption] = useState(null) const [seletedSubOption, setSeletedSubOption] = useState(null)
const { setModuleStatisticsData } = useCircuitTrestle() const { setModuleStatisticsData } = useCircuitTrestle()
const { handleCanvasToPng } = useImgLoader() const { handleCanvasToPng } = useImgLoader()
const moduleSelectionData = useRecoilValue(moduleSelectionDataState)
const passivityCircuitAllocationRef = useRef() const passivityCircuitAllocationRef = useRef()
const { setIsGlobalLoading } = useContext(QcastContext) const { setIsGlobalLoading } = useContext(QcastContext)
@ -102,6 +107,29 @@ export default function CircuitTrestleSetting({ id }) {
} }
}, []) }, [])
//
const beforeCapture = () => {
// setCanvasZoom(100)
const x = canvas.width / 2
const y = canvas.height / 2
canvas.zoomToPoint(new fabric.Point(x, y), 0.5)
changeFontSize('lengthText', '28')
changeFontSize('circuitNumber', '28')
changeFontSize('flowText', '28')
canvas.renderAll()
}
//
const afterCapture = () => {
setCanvasZoom(100)
canvas.set({ zoom: 1 })
canvas.viewportTransform = [1, 0, 0, 1, 0, 0]
changeFontSize('lengthText', lengthText.fontSize.value)
changeFontSize('circuitNumber', circuitNumberText.fontSize.value)
changeFontSize('flowText', flowText.fontSize.value)
canvas.renderAll()
}
// //
// PCS // PCS
@ -154,6 +182,7 @@ export default function CircuitTrestleSetting({ id }) {
getPcsVoltageChk(pcsVoltageChkParams).then((res) => { getPcsVoltageChk(pcsVoltageChkParams).then((res) => {
if (res.resultCode === 'S') { if (res.resultCode === 'S') {
setTabNum(2) setTabNum(2)
setAllModuleSurfaceIsComplete(false)
} else { } else {
swalFire({ swalFire({
title: res.resultMsg, title: res.resultMsg,
@ -187,6 +216,7 @@ export default function CircuitTrestleSetting({ id }) {
}).then((res) => { }).then((res) => {
if (res?.result.resultCode === 'S' && res?.data) { if (res?.result.resultCode === 'S' && res?.data) {
setTabNum(2) setTabNum(2)
setAllModuleSurfaceIsComplete(false)
} else { } else {
swalFire({ text: getMessage('common.message.send.error') }) swalFire({ text: getMessage('common.message.send.error') })
} }
@ -286,6 +316,8 @@ export default function CircuitTrestleSetting({ id }) {
setSelectedModels(pcsItemList) setSelectedModels(pcsItemList)
getPcsVoltageChk(pcsVoltageChkParams).then((res) => { getPcsVoltageChk(pcsVoltageChkParams).then((res) => {
setAllocationType(ALLOCATION_TYPE.PASSIVITY) setAllocationType(ALLOCATION_TYPE.PASSIVITY)
setAllModuleSurfaceIsComplete(false)
clearTrestle()
}) })
} else { } else {
swalFire({ swalFire({
@ -308,8 +340,15 @@ export default function CircuitTrestleSetting({ id }) {
const target = pcsCheck.max ? moduleMaxQty : moduleStdQty const target = pcsCheck.max ? moduleMaxQty : moduleStdQty
const placementModules = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE) const placementModules = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE)
let moduleAmount = placementModules.reduce((acc, module) => {
if (moduleSelectionData.module.itemList.length === 1 || module.moduleInfo.itemId === moduleSelectionData.module.itemList[0].itemId) {
return acc + 1
} else {
return acc + 0.66
}
}, 0)
if (placementModules.length > target) { if (moduleAmount > target) {
swalFire({ swalFire({
title: getMessage('modal.circuit.trestle.setting.circuit.allocation.passivity.all.power.conditional.validation.error01'), title: getMessage('modal.circuit.trestle.setting.circuit.allocation.passivity.all.power.conditional.validation.error01'),
type: 'alert', type: 'alert',
@ -318,6 +357,7 @@ export default function CircuitTrestleSetting({ id }) {
} }
setAllocationType(ALLOCATION_TYPE.PASSIVITY) setAllocationType(ALLOCATION_TYPE.PASSIVITY)
clearTrestle()
} }
} }
@ -347,13 +387,11 @@ export default function CircuitTrestleSetting({ id }) {
.map((obj) => { .map((obj) => {
obj.pcses = getStepUpListData() obj.pcses = getStepUpListData()
}) })
beforeCapture()
setViewCircuitNumberTexts(false)
handleCanvasToPng(1) handleCanvasToPng(1)
afterCapture()
// result=null // result=null
setViewCircuitNumberTexts(true)
// //
// //
@ -368,7 +406,9 @@ export default function CircuitTrestleSetting({ id }) {
const result = await getEstimateData() const result = await getEstimateData()
if (result) { if (result) {
beforeCapture()
handleCanvasToPng(2) handleCanvasToPng(2)
afterCapture()
// //
await saveEstimate(result) await saveEstimate(result)
} else { } else {
@ -378,6 +418,16 @@ export default function CircuitTrestleSetting({ id }) {
// removeNotAllocationModules() // removeNotAllocationModules()
} }
const changeFontSize = (name, size) => {
const textObjs = canvas?.getObjects().filter((obj) => obj.name === name)
textObjs.forEach((obj) => {
obj.set({
fontSize: size,
})
})
canvas.renderAll()
}
// //
const onClickPrev = () => { const onClickPrev = () => {
// setAllocationType(ALLOCATION_TYPE.AUTO) // setAllocationType(ALLOCATION_TYPE.AUTO)
@ -654,6 +704,7 @@ export default function CircuitTrestleSetting({ id }) {
return return
} else { } else {
setTabNum(2) setTabNum(2)
setAllModuleSurfaceIsComplete(false)
} }
}) })
} }

View File

@ -227,7 +227,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla
console.log('save Info', { console.log('save Info', {
...basicSetting, ...basicSetting,
selectedRoofMaterial: { selectedRoofMaterial: {
roofInfo, ...newAddedRoofs[0],
}, },
}) })
@ -240,7 +240,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla
* 선택된 지붕재 정보 * 선택된 지붕재 정보
*/ */
selectedRoofMaterial: { selectedRoofMaterial: {
roofInfo, ...newAddedRoofs[0],
}, },
}) })

View File

@ -952,9 +952,18 @@ export default function StuffDetail() {
// //
const setZipInfo = (info) => { const setZipInfo = (info) => {
setPrefValue(info.prefId)
form.setValue('prefId', info.prefId) prefCodeList.map((row) => {
form.setValue('prefName', info.address1) if (row.prefName == info.address1) {
setPrefValue(row.prefId)
form.setValue('prefId', row.prefId)
form.setValue('prefName', info.address1)
}
})
//setPrefValue(info.prefId)
// form.setValue('prefId', info.prefId)
// form.setValue('prefName', info.address1)
form.setValue('address', info.address2 + info.address3) form.setValue('address', info.address2 + info.address3)
form.setValue('zipNo', info.zipNo) form.setValue('zipNo', info.zipNo)
} }

View File

@ -56,19 +56,20 @@ export default function StuffSubHeader({ type }) {
*/ */
const moveFloorPlan = () => { const moveFloorPlan = () => {
setFloorPlanObjectNo({ floorPlanObjectNo: objectNo }) setFloorPlanObjectNo({ floorPlanObjectNo: objectNo })
const param = { const param = {
pid: managementState?.planList?.length === 0 ? '1' : managementState?.planList[0].planNo, pid: managementState?.planList?.length > 0 ? managementState?.planList[0].planNo : '1',
objectNo: objectNo, objectNo: objectNo,
} }
if (managementState?.planList?.length === 0) { if (managementState?.planList?.length > 0) {
setSelectedMenu('surface')
} else {
if (managementState?.planList[0].estimateDate) { if (managementState?.planList[0].estimateDate) {
setSelectedMenu('module') setSelectedMenu('module')
} else { } else {
setSelectedMenu('surface') setSelectedMenu('surface')
} }
} else {
setSelectedMenu('surface')
} }
const url = `/floor-plan?${queryStringFormatter(param)}` const url = `/floor-plan?${queryStringFormatter(param)}`

View File

@ -114,7 +114,7 @@ export default function Simulator() {
setHatsudenryouPeakcutAllSnow([]) setHatsudenryouPeakcutAllSnow([])
if (objectNo && pid && selectedPlan) { if (objectNo && pid && selectedPlan) {
fetchObjectDetail(objectNo, selectedPlan.planNo) fetchObjectDetail(objectNo, selectedPlan?.planNo??pid)
fetchSimulatorNotice() fetchSimulatorNotice()
setPwrGnrSimType('D') setPwrGnrSimType('D')
setPwrRecoil({ ...pwrRecoil, type: 'D' }) setPwrRecoil({ ...pwrRecoil, type: 'D' })

View File

@ -0,0 +1,11 @@
// local, development, production 과 관계없이 동일한 값으로 반환되는 부분은 해당 함수의 return 되는 부분만 수정하면 됩니다. (달라져야 하는 값이 아닌, 같은 값에 대해서는 local, development, production 파일을 모두 수정할 필요가 없어지게 됩니다.)
export default function getConfigs(params) {
// local, development, production 마다 달라지는 값
const { baseUrl, mode } = params
// 공통으로 반환되는 구조
return {
baseUrl,
mode,
}
}

View File

@ -0,0 +1,13 @@
import getConfigs from './config.common'
// 환경마다 달라져야 할 변수, 값들을 정의합니다. (여기는 development 환경에 맞는 값을 지정합니다.)
const baseUrl = 'https://dev.hanssys.jp'
const mode = 'development'
// 환경마다 달라져야 할 값들을 getConfig 함수에 전달합니다.
const configDevelopment = getConfigs({
baseUrl,
mode,
})
export default configDevelopment

View File

@ -0,0 +1,23 @@
import configDevelopment from './config.development'
import configLocal from './config.local'
import configLocalDev from './config.local.dev'
import configProduction from './config.production'
// 클라이언트에서는 이 함수를 사용하여 config 값을 참조합니다.
const Config = () => {
console.log('🚀 ~ Config ~ process.env.NEXT_PUBLIC_RUN_MODE:', process.env.NEXT_PUBLIC_RUN_MODE)
switch (process.env.NEXT_PUBLIC_RUN_MODE) {
case 'local':
return configLocal
case 'local.dev':
return configLocalDev
case 'development':
return configDevelopment
case 'production':
return configProduction
default:
return configLocal
}
}
export default Config

View File

@ -0,0 +1,13 @@
import getConfigs from './config.common'
// 환경마다 달라져야 할 변수, 값들을 정의합니다. (여기는 local 환경에 맞는 값을 지정합니다.)
const baseUrl = 'http://1.248.227.176:5000'
const mode = 'local.dev'
// 환경마다 달라져야 할 값들을 getConfig 함수에 전달합니다.
const configLocalDev = getConfigs({
baseUrl,
mode,
})
export default configLocalDev

View File

@ -0,0 +1,13 @@
import getConfigs from './config.common'
// 환경마다 달라져야 할 변수, 값들을 정의합니다. (여기는 local 환경에 맞는 값을 지정합니다.)
const baseUrl = 'http://localhost:3000'
const mode = 'local'
// 환경마다 달라져야 할 값들을 getConfig 함수에 전달합니다.
const configLocal = getConfigs({
baseUrl,
mode,
})
export default configLocal

View File

@ -0,0 +1,13 @@
import getConfigs from './config.common'
// 환경마다 달라져야 할 변수, 값들을 정의합니다. (여기는 production 환경에 맞는 값을 지정합니다.)
const baseUrl = 'https://www.hanasys.jp'
const mode = 'production'
// 환경마다 달라져야 할 값들을 getConfig 함수에 전달합니다.
const configProduction = getConfigs({
baseUrl,
mode,
})
export default configProduction

View File

@ -1,11 +1,11 @@
import { useEffect } from 'react' import { useEffect } from 'react'
import { useRecoilState, useRecoilValue } from 'recoil' import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil'
import { wordDisplaySelector } from '@/store/settingAtom' import { wordDisplaySelector } from '@/store/settingAtom'
import { useEvent } from '@/hooks/useEvent' import { useEvent } from '@/hooks/useEvent'
import { checkLineOrientation, getDistance } from '@/util/canvas-util' import { checkLineOrientation, getDistance } from '@/util/canvas-util'
import { commonUtilsState, dimensionLineSettingsState } from '@/store/commonUtilsAtom' import { commonUtilsState, dimensionLineSettingsState } from '@/store/commonUtilsAtom'
import { fontSelector } from '@/store/fontAtom' import { fontSelector } from '@/store/fontAtom'
import { canvasState } from '@/store/canvasAtom' import { canvasState, currentMenuState } from '@/store/canvasAtom'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { usePopup } from '@/hooks/usePopup' import { usePopup } from '@/hooks/usePopup'
import Distance from '@/components/floor-plan/modal/distance/Distance' import Distance from '@/components/floor-plan/modal/distance/Distance'
@ -16,57 +16,91 @@ import { BATCH_TYPE } from '@/common/common'
export function useCommonUtils() { export function useCommonUtils() {
const canvas = useRecoilValue(canvasState) const canvas = useRecoilValue(canvasState)
const wordDisplay = useRecoilValue(wordDisplaySelector) const wordDisplay = useRecoilValue(wordDisplaySelector)
const { addCanvasMouseEventListener, addDocumentEventListener, initEvent } = useEvent() const { addCanvasMouseEventListener, addDocumentEventListener, initEvent, removeMouseEvent } = useEvent()
const dimensionSettings = useRecoilValue(dimensionLineSettingsState) const dimensionSettings = useRecoilValue(dimensionLineSettingsState)
const dimensionLineTextFont = useRecoilValue(fontSelector('dimensionLineText')) const dimensionLineTextFont = useRecoilValue(fontSelector('dimensionLineText'))
const lengthTextFont = useRecoilValue(fontSelector('lengthText')) const lengthTextFont = useRecoilValue(fontSelector('lengthText'))
const commonTextFont = useRecoilValue(fontSelector('commonText')) const commonTextFont = useRecoilValue(fontSelector('commonText'))
const [commonUtils, setCommonUtilsState] = useRecoilState(commonUtilsState) const [commonUtils, setCommonUtilsState] = useRecoilState(commonUtilsState)
const { addPopup } = usePopup() const { addPopup, closeAll, targetClose } = usePopup()
const { drawDirectionArrow, addLengthText } = usePolygon() const { drawDirectionArrow, addLengthText } = usePolygon()
const { applyDormers } = useObjectBatch({}) const { applyDormers } = useObjectBatch({})
useEffect(() => { useEffect(() => {
if (commonUtils.text) { commonTextMode()
commonTextMode() if (commonUtils.dimension) {
} else if (commonUtils.dimension) {
commonDimensionMode() commonDimensionMode()
} else if (commonUtils.distance) { return
}
if (commonUtils.distance) {
commonDistanceMode() commonDistanceMode()
return
} }
}, [commonUtils, dimensionSettings, commonTextFont, dimensionLineTextFont]) }, [commonUtils, dimensionSettings, commonTextFont, dimensionLineTextFont])
const commonTextMode = () => { const commonTextMode = () => {
let textbox let textbox
if (commonUtils.text) { if (commonUtils.text) {
commonTextKeyEvent() targetClose('other')
addCanvasMouseEventListener('mouse:down', (event) => { setTimeout(() => {
const pointer = canvas?.getPointer(event.e) commonTextKeyEvent()
addCanvasMouseEventListener('mouse:down', (event) => {
const pointer = canvas?.getPointer(event.e)
textbox = new fabric.Textbox('', { textbox = new fabric.Textbox('', {
left: pointer.x, left: pointer.x,
top: pointer.y, top: pointer.y,
width: 200, width: 200,
editable: true, editable: true,
name: 'commonText', name: 'commonText',
visible: wordDisplay, visible: wordDisplay,
fill: commonTextFont.fontColor.value, fill: commonTextFont.fontColor.value,
fontFamily: commonTextFont.fontFamily.value, fontFamily: commonTextFont.fontFamily.value,
fontSize: commonTextFont.fontSize.value, fontSize: commonTextFont.fontSize.value,
fontStyle: commonTextFont.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal', fontStyle: commonTextFont.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal',
fontWeight: commonTextFont.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal', fontWeight: commonTextFont.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal',
selectable: true, selectable: true,
lockMovementX: true, lockMovementX: true,
lockMovementY: true, lockMovementY: true,
originX: 'center', originX: 'center',
originY: 'center', originY: 'center',
})
canvas?.add(textbox)
canvas.setActiveObject(textbox)
textbox.enterEditing()
textbox.selectAll()
}) })
}, 100)
} else {
removeMouseEvent('mouse:down')
const activeObject = canvas?.getActiveObject()
const commonTexts = canvas?.getObjects().filter((obj) => obj.name === 'commonText')
if (commonTexts) {
commonTexts.forEach((text) => {
if (text.text === '') {
canvas?.remove(text)
}
})
}
/*if (activeObject && activeObject.name === 'commonText') {
if (activeObject && activeObject.isEditing) {
if (activeObject.text === '') {
canvas?.remove(activeObject)
} else {
activeObject.exitEditing()
}
//정책 협의
const texts = canvas.getObjects().filter((obj) => obj.name === 'commonText')
texts.forEach((text) => {
text.set({ editable: false })
})
canvas.renderAll()
}
}*/
canvas?.add(textbox) initEvent()
canvas.setActiveObject(textbox)
textbox.enterEditing()
textbox.selectAll()
})
} }
} }

View File

@ -212,9 +212,14 @@ export function useMasterController() {
} }
const getQuotationItem = async (params) => { const getQuotationItem = async (params) => {
return await post({ url: '/api/v1/master/getQuotationItem', data: params }).then((res) => { return await post({ url: '/api/v1/master/getQuotationItem', data: params })
return res .then((res) => {
}) return res
})
.catch((error) => {
console.log('🚀 ~ getQuotationItem ~ error:', error)
setIsGlobalLoading(false)
})
} }
/** /**

View File

@ -8,6 +8,7 @@ import { useCanvas } from '@/hooks/useCanvas'
import { deleteBackGroundImage, setBackGroundImage } from '@/lib/imageActions' import { deleteBackGroundImage, setBackGroundImage } from '@/lib/imageActions'
import { settingModalFirstOptionsState } from '@/store/settingAtom' import { settingModalFirstOptionsState } from '@/store/settingAtom'
import { popSpinnerState } from '@/store/popupAtom' import { popSpinnerState } from '@/store/popupAtom'
import Config from '@/config/config.export'
/** /**
* 배경 이미지 관리 * 배경 이미지 관리
@ -97,7 +98,7 @@ export function useRefFiles() {
setPopSpinnerStore(true) setPopSpinnerStore(true)
console.log('🚀 ~ handleFileDelete ~ handleFileDelete:', refImage) console.log('🚀 ~ handleFileDelete ~ handleFileDelete:', refImage)
console.log('🚀 ~ handleFileDelete ~ currentCanvasPlan.bgImageName:', currentCanvasPlan.bgImageName) console.log('🚀 ~ handleFileDelete ~ currentCanvasPlan.bgImageName:', currentCanvasPlan.bgImageName)
await del({ url: `${process.env.NEXT_PUBLIC_API_HOST_URL}/api/image/upload?fileName=${currentCanvasPlan.bgImageName}` }) await del({ url: `${Config().baseUrl}/api/image/upload?fileName=${currentCanvasPlan.bgImageName}` })
setCurrentBgImage(null) setCurrentBgImage(null)
await deleteBackGroundImage({ await deleteBackGroundImage({
objectId: currentCanvasPlan.id, objectId: currentCanvasPlan.id,
@ -118,7 +119,7 @@ export function useRefFiles() {
confirmFn: async () => { confirmFn: async () => {
console.log('🚀 ~ handleAddressDelete ~ handleAddressDelete:', refImage) console.log('🚀 ~ handleAddressDelete ~ handleAddressDelete:', refImage)
console.log('🚀 ~ handleAddressDelete ~ currentCanvasPlan.bgImageName:', currentCanvasPlan.bgImageName) console.log('🚀 ~ handleAddressDelete ~ currentCanvasPlan.bgImageName:', currentCanvasPlan.bgImageName)
await del({ url: `${process.env.NEXT_PUBLIC_API_HOST_URL}/api/image/map?fileName=${currentCanvasPlan.bgImageName}` }) await del({ url: `${Config().baseUrl}/api/image/map?fileName=${currentCanvasPlan.bgImageName}` })
setMapPositionAddress('') setMapPositionAddress('')
setCurrentBgImage(null) setCurrentBgImage(null)
await deleteBackGroundImage({ await deleteBackGroundImage({
@ -149,7 +150,7 @@ export function useRefFiles() {
})) }))
const res = await get({ const res = await get({
url: `${process.env.NEXT_PUBLIC_API_HOST_URL}/api/image/map?q=${queryRef.current.value}&fileNm=${currentCanvasPlan.id}&zoom=20`, url: `${Config().baseUrl}/api/image/map?q=${queryRef.current.value}&fileNm=${currentCanvasPlan.id}&zoom=20`,
}) })
console.log('🚀 ~ handleMapImageDown ~ res:', res) console.log('🚀 ~ handleMapImageDown ~ res:', res)
setCurrentBgImage(`${process.env.NEXT_PUBLIC_AWS_S3_BASE_URL}/${res.fileName}`) setCurrentBgImage(`${process.env.NEXT_PUBLIC_AWS_S3_BASE_URL}/${res.fileName}`)
@ -208,7 +209,7 @@ export function useRefFiles() {
formData.append('file', file) formData.append('file', file)
const res = await post({ const res = await post({
url: `${process.env.NEXT_PUBLIC_API_HOST_URL}/api/image/upload`, url: `${Config().baseUrl}/api/image/upload`,
data: formData, data: formData,
}) })
console.log('🚀 ~ handleUploadImageRefFile ~ res:', res) console.log('🚀 ~ handleUploadImageRefFile ~ res:', res)
@ -238,14 +239,40 @@ export function useRefFiles() {
const res = await post({ url: converterUrl, data: formData }) const res = await post({ url: converterUrl, data: formData })
console.log('🚀 ~ handleUploadConvertRefFile ~ res:', res) console.log('🚀 ~ handleUploadConvertRefFile ~ res:', res)
// Convert Base64 to Blob
const base64Data = res.Files[0].FileData
const byteCharacters = atob(base64Data)
const byteArrays = []
for (let offset = 0; offset < byteCharacters.length; offset += 512) {
const slice = byteCharacters.slice(offset, offset + 512)
const byteNumbers = new Array(slice.length)
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i)
}
const byteArray = new Uint8Array(byteNumbers)
byteArrays.push(byteArray)
}
const blob = new Blob(byteArrays, { type: 'image/png' })
// Create File object from Blob
const convertImg = new File([blob], res.Files[0].FileName, { type: 'image/png' })
const newFormData = new FormData()
newFormData.append('file', convertImg)
/** 캐드 도면 파일 업로드 */ /** 캐드 도면 파일 업로드 */
const result = await post({ const result = await post({
url: `${process.env.NEXT_PUBLIC_API_HOST_URL}/api/image/cad`, url: `${Config().baseUrl}/api/image/cad`,
data: res, data: newFormData,
}) })
console.log('🚀 ~ handleUploadConvertRefFile ~ result:', result) console.log('🚀 ~ handleUploadConvertRefFile ~ result:', result)
setCurrentBgImage(`${process.env.NEXT_PUBLIC_AWS_S3_BASE_URL}/${res.fileName}`) setCurrentBgImage(`${process.env.NEXT_PUBLIC_AWS_S3_BASE_URL}/${result.fileName}`)
// setCurrentBgImage(result.filePath)
setRefImage(file) setRefImage(file)
const params = { const params = {

View File

@ -13,6 +13,8 @@ import { useSwal } from '@/hooks/useSwal'
// Constants // Constants
const ESTIMATE_API_ENDPOINT = '/api/estimate' // API 엔드포인트 정의 const ESTIMATE_API_ENDPOINT = '/api/estimate' // API 엔드포인트 정의
import Config from '@/config/config.export'
// Helper functions // Helper functions
const updateItemInList = (itemList, dispOrder, updates) => { const updateItemInList = (itemList, dispOrder, updates) => {
return itemList.map((item) => (item.dispOrder === dispOrder ? { ...item, ...updates } : item)) return itemList.map((item) => (item.dispOrder === dispOrder ? { ...item, ...updates } : item))
@ -464,11 +466,13 @@ export const useEstimateController = (planNo, flag) => {
setIsGlobalLoading(true) setIsGlobalLoading(true)
await promisePost({ url: '/api/estimate/save-estimate-copy', data: params }) await promisePost({ url: '/api/estimate/save-estimate-copy', data: params })
.then((res) => { .then(async (res) => {
setIsGlobalLoading(false) setIsGlobalLoading(false)
if (res.status === 201) { if (res.status === 201) {
if (isObjectNotEmpty(res.data)) { if (isObjectNotEmpty(res.data)) {
let newObjectNo = res.data.objectNo let newObjectNo = res.data.objectNo
const copyImage = await handleEstimateImageCopy(params.objectNo, params.planNo, newObjectNo, '1')
swalFire({ swalFire({
text: getMessage('estimate.detail.estimateCopyPopup.copy.alertMessage'), text: getMessage('estimate.detail.estimateCopyPopup.copy.alertMessage'),
type: 'alert', type: 'alert',
@ -489,6 +493,27 @@ export const useEstimateController = (planNo, flag) => {
}) })
} }
const handleEstimateImageCopy = async (objectNo, planNo, newObjectNo, newPlanNo) => {
await promisePost({ url: `${Config().baseUrl}/api/image/estimate-image-copy`, data: { objectNo, planNo, newObjectNo, newPlanNo } }).then(
(res) => {
return res
},
)
}
const handleDeleteEstimate = async (canvasStatus) => {
try {
setIsGlobalLoading(true)
await promisePost({ url: `${ESTIMATE_API_ENDPOINT}/delete-estimate`, data: canvasStatus }).then((res) => {
if (res.status === 201) {
}
})
} catch (e) {
console.error('error::::::::::::', e.response.data.message)
}
setIsGlobalLoading(false)
}
/** /**
* 전각20자 (반각40자) * 전각20자 (반각40자)
*/ */
@ -509,5 +534,7 @@ export const useEstimateController = (planNo, flag) => {
fetchSetting, fetchSetting,
handleEstimateFileDownload, handleEstimateFileDownload,
handleEstimateCopy, handleEstimateCopy,
handleDeleteEstimate,
handleEstimateImageCopy,
} }
} }

View File

@ -5,6 +5,7 @@ import { usePlan } from '../usePlan'
import { POLYGON_TYPE } from '@/common/common' import { POLYGON_TYPE } from '@/common/common'
import { QcastContext } from '@/app/QcastProvider' import { QcastContext } from '@/app/QcastProvider'
import { useContext } from 'react' import { useContext } from 'react'
import Config from '@/config/config.export'
/** /**
* 이미지 로더 hook * 이미지 로더 hook
@ -80,7 +81,7 @@ export function useImgLoader() {
/** 이미지 크롭 요청 */ /** 이미지 크롭 요청 */
const result = await post({ const result = await post({
// url: `${process.env.NEXT_PUBLIC_API_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`, url: `${Config().baseUrl}/api/image/canvas`,
data: formData, data: formData,
}) })
console.log('🚀 ~ handleCanvasToPng ~ result:', result) console.log('🚀 ~ handleCanvasToPng ~ result:', result)

View File

@ -661,7 +661,7 @@ export function useModuleBasicSetting(tabNum) {
//가운데 가운데 //가운데 가운데
if (Math.abs(smallCenterY - holdCellCenterY) < snapDistance) { if (Math.abs(smallCenterY - holdCellCenterY) < snapDistance) {
tempModule.top = holdCellCenterY - toFixedWithoutRounding(width / 2, 2) tempModule.top = holdCellCenterY - toFixedWithoutRounding(height / 2, 2)
} }
if (isChidori) { if (isChidori) {

View File

@ -209,7 +209,7 @@ export function useModuleTrestle(props) {
stdSnowLd: trestleState.stdSnowLd ?? '', stdSnowLd: trestleState.stdSnowLd ?? '',
inclCd: trestleState.inclCd ?? '', inclCd: trestleState.inclCd ?? '',
raftBaseCd: trestleState.raft ?? '', raftBaseCd: trestleState.raft ?? '',
roofPitch: Math.round(trestleState.roofPitch) ?? '', roofPitch: trestleState.hajebichi ? trestleState.hajebichi : (trestleState.roofPitch ?? ''),
}) })
.then((res) => { .then((res) => {
if (res?.data) setConstructionList(res.data) if (res?.data) setConstructionList(res.data)
@ -236,7 +236,7 @@ export function useModuleTrestle(props) {
inclCd: trestleState.inclCd ?? '', inclCd: trestleState.inclCd ?? '',
constTp: trestleState.constTp ?? '', constTp: trestleState.constTp ?? '',
mixMatlNo: trestleState.mixMatlNo ?? '', mixMatlNo: trestleState.mixMatlNo ?? '',
roofPitch: trestleState.roofPitch ?? '', roofPitch: trestleState.hajebichi ? trestleState.hajebichi : (trestleState.roofPitch ?? ''),
// workingWidth: trestleState.length ?? '', // workingWidth: trestleState.length ?? '',
workingWidth: lengthBase ?? '', workingWidth: lengthBase ?? '',
}, },

View File

@ -53,6 +53,7 @@ export const useTrestle = () => {
} }
}) })
surfaces.forEach((surface) => { surfaces.forEach((surface) => {
getSameLineModules(surface)
const parent = canvas.getObjects().find((obj) => obj.id === surface.parentId) const parent = canvas.getObjects().find((obj) => obj.id === surface.parentId)
const roofMaterialIndex = parent.roofMaterial.index const roofMaterialIndex = parent.roofMaterial.index
if (+roofSizeSet === 3) { if (+roofSizeSet === 3) {
@ -67,6 +68,7 @@ export const useTrestle = () => {
let moduleRowsTotCnt = 0 let moduleRowsTotCnt = 0
let isEaveBar = construction.setupCover let isEaveBar = construction.setupCover
let isSnowGuard = construction.setupSnowCover let isSnowGuard = construction.setupSnowCover
let cvrLmtRow = construction.cvrLmtRow
const direction = parent.direction const direction = parent.direction
const rack = surface.trestleDetail.rack const rack = surface.trestleDetail.rack
let { rackQty, rackIntvlPct, rackYn, cvrPlvrYn, lessSupFitIntvlPct, lessSupFitQty } = surface.trestleDetail let { rackQty, rackIntvlPct, rackYn, cvrPlvrYn, lessSupFitIntvlPct, lessSupFitQty } = surface.trestleDetail
@ -149,6 +151,10 @@ export const useTrestle = () => {
if (isEaveBar) { if (isEaveBar) {
// 처마력바설치 true인 경우 설치 // 처마력바설치 true인 경우 설치
exposedBottomModules.forEach((module) => { exposedBottomModules.forEach((module) => {
const level = module.level
if (level > cvrLmtRow) {
return
}
const bottomPoints = findTopTwoPoints([...module.getCurrentPoints()], direction) const bottomPoints = findTopTwoPoints([...module.getCurrentPoints()], direction)
if (!bottomPoints) return if (!bottomPoints) return
const eaveBar = new fabric.Line([bottomPoints[0].x, bottomPoints[0].y, bottomPoints[1].x, bottomPoints[1].y], { const eaveBar = new fabric.Line([bottomPoints[0].x, bottomPoints[0].y, bottomPoints[1].x, bottomPoints[1].y], {
@ -167,6 +173,10 @@ export const useTrestle = () => {
if (isChidory && cvrPlvrYn === 'Y') { if (isChidory && cvrPlvrYn === 'Y') {
leftExposedHalfBottomModules.forEach((module) => { leftExposedHalfBottomModules.forEach((module) => {
const level = module.level
if (level > cvrLmtRow) {
return
}
const bottomPoints = findTopTwoPoints([...module.getCurrentPoints()], direction) const bottomPoints = findTopTwoPoints([...module.getCurrentPoints()], direction)
let barPoints = [] let barPoints = []
//설치해야할 반처마커버 포인트를 방향에 따라 설정 //설치해야할 반처마커버 포인트를 방향에 따라 설정
@ -197,6 +207,10 @@ export const useTrestle = () => {
}) })
rightExposedHalfBottomPoints.forEach((module) => { rightExposedHalfBottomPoints.forEach((module) => {
const level = module.level
if (level > cvrLmtRow) {
return
}
const bottomPoints = findTopTwoPoints([...module.points], direction) const bottomPoints = findTopTwoPoints([...module.points], direction)
let barPoints = [] let barPoints = []
//설치해야할 반처마커버 포인트를 방향에 따라 설정 //설치해야할 반처마커버 포인트를 방향에 따라 설정
@ -1059,7 +1073,8 @@ export const useTrestle = () => {
const roof = canvas.getObjects().find((obj) => obj.id === surface.parentId) const roof = canvas.getObjects().find((obj) => obj.id === surface.parentId)
const degree = getDegreeByChon(roof.roofMaterial.pitch) const degree = getDegreeByChon(roof.roofMaterial.pitch)
rackIntvlPct = rackIntvlPct === 0 ? 1 : rackIntvlPct // 0인 경우 1로 변경
rackIntvlPct = 100 / rackIntvlPct // 퍼센트로 변경
const moduleLeft = lastX ?? left const moduleLeft = lastX ?? left
const moduleTop = lastY ?? top const moduleTop = lastY ?? top
@ -1573,7 +1588,9 @@ export const useTrestle = () => {
drawBracketWithOutRack(module, rackIntvlPct, module.leftRows + 1, 'L', surface.direction, moduleIntvlHor, moduleIntvlVer) drawBracketWithOutRack(module, rackIntvlPct, module.leftRows + 1, 'L', surface.direction, moduleIntvlHor, moduleIntvlVer)
if (rackQty === 3 && !findSamePointInBottom(exposedBottomModules, module)) { if (rackQty === 3 && !findSamePointInBottom(exposedBottomModules, module)) {
// 해당 모듈과 같은 위치 맨 아래에 모듈이 없는 경우 하나 더 설치 필요 // 해당 모듈과 같은 위치 맨 아래에 모듈이 없는 경우 하나 더 설치 필요
drawBracketWithOutRack(module, rackIntvlPct / 3, module.leftRows + 1, 'L', surface.direction, moduleIntvlHor, moduleIntvlVer) if (module.level % 2 !== 0) {
drawBracketWithOutRack(module, rackIntvlPct / 3, module.leftRows + 1, 'L', surface.direction, moduleIntvlHor, moduleIntvlVer)
}
} }
if (rackQty === 4) { if (rackQty === 4) {
drawBracketWithOutRack(module, rackIntvlPct / 3, module.leftRows + 1, 'L', surface.direction, moduleIntvlHor, moduleIntvlVer) drawBracketWithOutRack(module, rackIntvlPct / 3, module.leftRows + 1, 'L', surface.direction, moduleIntvlHor, moduleIntvlVer)
@ -1581,10 +1598,13 @@ export const useTrestle = () => {
}) })
rightExposedHalfBottomPoints.forEach((module) => { rightExposedHalfBottomPoints.forEach((module) => {
console.log('rightmodule.level', module.level)
drawBracketWithOutRack(module, rackIntvlPct, module.rightRows + 1, 'R', surface.direction, moduleIntvlHor, moduleIntvlVer) drawBracketWithOutRack(module, rackIntvlPct, module.rightRows + 1, 'R', surface.direction, moduleIntvlHor, moduleIntvlVer)
if (rackQty === 3 && !findSamePointInBottom(exposedBottomModules, module, direction)) { if (rackQty === 3 && !findSamePointInBottom(exposedBottomModules, module, direction)) {
// 해당 모듈과 같은 위치 맨 아래에 모듈이 없는 경우 하나 더 설치 필요 // 해당 모듈과 같은 위치 맨 아래에 모듈이 없는 경우 하나 더 설치 필요
drawBracketWithOutRack(module, rackIntvlPct / 3, module.rightRows + 1, 'R', surface.direction, moduleIntvlHor, moduleIntvlVer) if (module.level % 2 === 0) {
drawBracketWithOutRack(module, rackIntvlPct / 3, module.rightRows + 1, 'R', surface.direction, moduleIntvlHor, moduleIntvlVer)
}
} }
if (rackQty === 4) { if (rackQty === 4) {
drawBracketWithOutRack(module, rackIntvlPct / 3, module.rightRows + 1, 'R', surface.direction, moduleIntvlHor, moduleIntvlVer) drawBracketWithOutRack(module, rackIntvlPct / 3, module.rightRows + 1, 'R', surface.direction, moduleIntvlHor, moduleIntvlVer)
@ -1627,6 +1647,8 @@ export const useTrestle = () => {
// 랙 없음의 지지금구를 그린다. // 랙 없음의 지지금구를 그린다.
const drawBracketWithOutRack = (module, rackIntvlPct, count, l, direction, moduleIntvlHor, moduleIntvlVer) => { const drawBracketWithOutRack = (module, rackIntvlPct, count, l, direction, moduleIntvlHor, moduleIntvlVer) => {
const { leftFindModuleList, rightFindModuleList, centerFindModuleList } = module const { leftFindModuleList, rightFindModuleList, centerFindModuleList } = module
rackIntvlPct = rackIntvlPct === 0 ? 1 : rackIntvlPct // 0인 경우 1로 변경
rackIntvlPct = 100 / rackIntvlPct // 퍼센트로 변경
let { width, height, left, top } = module let { width, height, left, top } = module
let startPointX let startPointX
@ -1641,14 +1663,14 @@ export const useTrestle = () => {
break break
} else if (direction === 'east') { } else if (direction === 'east') {
startPointX = left + width startPointX = left + width
startPointY = top + height - height / rackIntvlPct startPointY = top + height - height / rackIntvlPct - 10
break break
} else if (direction === 'west') { } else if (direction === 'west') {
startPointX = left startPointX = left
startPointY = top + height / rackIntvlPct startPointY = top + height / rackIntvlPct
break break
} else if (direction === 'north') { } else if (direction === 'north') {
startPointX = left + width - width / rackIntvlPct startPointX = left + width - width / rackIntvlPct - 10
startPointY = top startPointY = top
break break
} }
@ -1657,7 +1679,7 @@ export const useTrestle = () => {
case 'R': { case 'R': {
// 오른쪽부분 시작 점 // 오른쪽부분 시작 점
if (direction === 'south') { if (direction === 'south') {
startPointX = left + width - width / rackIntvlPct startPointX = left + width - width / rackIntvlPct - 10
startPointY = top + height / 2 + height / 2 startPointY = top + height / 2 + height / 2
break break
} else if (direction === 'east') { } else if (direction === 'east') {
@ -1666,7 +1688,7 @@ export const useTrestle = () => {
break break
} else if (direction === 'west') { } else if (direction === 'west') {
startPointX = left startPointX = left
startPointY = top + height - height / rackIntvlPct startPointY = top + height - height / rackIntvlPct - 10
break break
} else if (direction === 'north') { } else if (direction === 'north') {
startPointX = left + width / rackIntvlPct startPointX = left + width / rackIntvlPct
@ -2543,23 +2565,15 @@ export const useTrestle = () => {
return result return result
} }
// 가장 왼쪽에 있는 모듈을 기준으로 같은 단에 있는 모듈들 파라미터 생성 const getSameLineModules = (surface) => {
const getMostLeftModules = (surface, exposedBottomModules) => {
const { direction, modules, isChidory } = surface const { direction, modules, isChidory } = surface
const parent = canvas.getObjects().find((obj) => obj.id === surface.parentId)
const roofMaterialIndex = parent.roofMaterial.index
const construction = moduleSelectionData?.roofConstructions?.find((construction) => construction.roofIndex === roofMaterialIndex).construction
let isEaveBar = construction.setupCover
let isSnowGuard = construction.setupSnowCover
let { rackYn, cvrPlvrYn, moduleIntvlHor, moduleIntvlVer, rackQty, lessSupFitQty } = surface.trestleDetail
if (rackYn === 'N') { if (!modules || modules.length === 0) {
rackQty = lessSupFitQty return
} }
// 같은 단에 있는 모듈들의 리스트 // 같은 단에 있는 모듈들의 리스트
let sameLineModuleList = [] let sameLineModuleList = []
let result = []
if (direction === 'south') { if (direction === 'south') {
// 모듈의 top으로 groupBy // 모듈의 top으로 groupBy
@ -2610,9 +2624,34 @@ export const useTrestle = () => {
sameLineModuleList = Object.values(groupedByLeft).sort((a, b) => a[0].left - b[0].left) sameLineModuleList = Object.values(groupedByLeft).sort((a, b) => a[0].left - b[0].left)
} }
sameLineModuleList.forEach((modules, index) => {
modules.forEach((module) => {
module.set({ level: index + 1 }) // 각 모듈에 level 속성 추가
})
})
return sameLineModuleList
}
// 가장 왼쪽에 있는 모듈을 기준으로 같은 단에 있는 모듈들 파라미터 생성
const getMostLeftModules = (surface, exposedBottomModules) => {
const { direction, modules, isChidory } = surface
const parent = canvas.getObjects().find((obj) => obj.id === surface.parentId)
const roofMaterialIndex = parent.roofMaterial.index
const construction = moduleSelectionData?.roofConstructions?.find((construction) => construction.roofIndex === roofMaterialIndex).construction
let isEaveBar = construction.setupCover
let isSnowGuard = construction.setupSnowCover
let cvrLmtRow = construction.cvrLmtRow
let { rackYn, cvrPlvrYn, moduleIntvlHor, moduleIntvlVer, rackQty, lessSupFitQty } = surface.trestleDetail
let result = []
if (rackYn === 'N') {
rackQty = lessSupFitQty
}
const sameLineModuleList = getSameLineModules(surface, exposedBottomModules)
sameLineModuleList.forEach((modules, index) => { sameLineModuleList.forEach((modules, index) => {
const moduleRowResultData = { const moduleRowResultData = {
seq: index, seq: index + 1,
moduleItemId: modules[0].moduleInfo.itemId, moduleItemId: modules[0].moduleInfo.itemId,
moduleTpCd: modules[0].moduleInfo.itemTp, moduleTpCd: modules[0].moduleInfo.itemTp,
moduleCnt: modules.length, moduleCnt: modules.length,
@ -2644,6 +2683,7 @@ export const useTrestle = () => {
} }
// 모듈 하면,최하면 등 구해야함 // 모듈 하면,최하면 등 구해야함
modules.forEach((module, index) => { modules.forEach((module, index) => {
const level = module.level
// 해당 모듈 주변에 다른 모듈이 있는지 확인 // 해당 모듈 주변에 다른 모듈이 있는지 확인
let { let {
bottomModule, bottomModule,
@ -2657,6 +2697,7 @@ export const useTrestle = () => {
bottomLeftModule, bottomLeftModule,
bottomRightModule, bottomRightModule,
} = findSideModule(module, surface) } = findSideModule(module, surface)
if (bottomModule) { if (bottomModule) {
moduleRowResultData.touchedSurfaceCnt++ moduleRowResultData.touchedSurfaceCnt++
if (rackYn === 'N') { if (rackYn === 'N') {
@ -2700,10 +2741,12 @@ export const useTrestle = () => {
} }
if (cvrPlvrYn === 'Y') { if (cvrPlvrYn === 'Y') {
moduleRowResultData.eavesHalfCnt++ if (level <= cvrLmtRow) {
if (bottomLeftModule || bottomRightModule || halfBottomLeftModule || halfBottomRightModule) { moduleRowResultData.eavesHalfCnt++
//처마커버 한개 노출 추가 if (bottomLeftModule || bottomRightModule || halfBottomLeftModule || halfBottomRightModule) {
moduleRowResultData.exposedSideEavesCnt++ //처마커버 한개 노출 추가
moduleRowResultData.exposedSideEavesCnt++
}
} }
} }
} else { } else {
@ -2712,13 +2755,15 @@ export const useTrestle = () => {
moduleRowResultData.exposedBottomBracketCnt += rackQty moduleRowResultData.exposedBottomBracketCnt += rackQty
} }
if (isEaveBar) { if (isEaveBar) {
moduleRowResultData.eavesCnt++ if (level <= cvrLmtRow) {
if ((rightModule && !leftModule) || (!rightModule && leftModule)) { moduleRowResultData.eavesCnt++
// 둘중 하나가 없는경우는 처마커버 노출 추가 if ((rightModule && !leftModule) || (!rightModule && leftModule)) {
moduleRowResultData.exposedSideEavesCnt++ // 둘중 하나가 없는경우는 처마커버 노출 추가
} else if (!rightModule && !leftModule) { moduleRowResultData.exposedSideEavesCnt++
// 양쪽 둘다 없는경우는 처마커버 노출 2개 추가 } else if (!rightModule && !leftModule) {
moduleRowResultData.exposedSideEavesCnt += 2 // 양쪽 둘다 없는경우는 처마커버 노출 2개 추가
moduleRowResultData.exposedSideEavesCnt += 2
}
} }
} }
} }
@ -3077,6 +3122,9 @@ export const useTrestle = () => {
// 배치면 전체에 가대 설치 여부 // 배치면 전체에 가대 설치 여부
const isAllComplete = () => { const isAllComplete = () => {
const surfaces = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE) const surfaces = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE)
if (surfaces.length === 0) {
return false
}
return surfaces.every((surface) => surface.isComplete) return surfaces.every((surface) => surface.isComplete)
} }

View File

@ -199,6 +199,9 @@ export function useCanvasSetting(executeEffect = true) {
if (!executeEffect) { if (!executeEffect) {
return return
} }
if (roofMaterials.length === 0) {
return
}
const selectedRoofMaterial = roofMaterials[0] const selectedRoofMaterial = roofMaterials[0]
if (addedRoofs.length === 0) { if (addedRoofs.length === 0) {
@ -495,11 +498,26 @@ export function useCanvasSetting(executeEffect = true) {
roofSeq: 0, roofSeq: 0,
roofMatlCd: roofMatlCd:
params.roofsData.roofMatlCd === null || params.roofsData.roofMatlCd === undefined ? 'ROOF_ID_WA_53A' : params.roofsData.roofMatlCd, params.roofsData.roofMatlCd === null || params.roofsData.roofMatlCd === undefined ? 'ROOF_ID_WA_53A' : params.roofsData.roofMatlCd,
roofWidth: params.roofsData.roofWidth === null || params.roofsData.roofWidth === undefined ? 0 : params.roofsData.roofWidth, roofWidth:
roofHeight: params.roofsData.roofHeight === null || params.roofsData.roofHeight === undefined ? 0 : params.roofsData.roofHeight, params.selectedRoofMaterial.width === null || params.selectedRoofMaterial.width === undefined
? !params.selectedRoofMaterial.widBase
? 0
: Number(params.roofsData.widBase)
: Number(params.selectedRoofMaterial.width),
roofHeight:
params.selectedRoofMaterial.height === null || params.selectedRoofMaterial.height === undefined
? !params.selectedRoofMaterial.lenBase
? 0
: Number(params.selectedRoofMaterial.lenBase)
: Number(params.roofsData.roofHeight),
roofHajebichi: roofHajebichi:
params.roofsData.roofHajebichi === null || params.roofsData.roofHajebichi === undefined ? 0 : params.roofsData.roofHajebichi, params.selectedRoofMaterial.hajebichi === null || params.selectedRoofMaterial.hajebichi === undefined
roofGap: params.roofsData.roofGap === null || params.roofsData.roofGap === undefined ? 'HEI_455' : params.roofsData.roofGap, ? 0
: Number(params.selectedRoofMaterial.hajebichi),
roofGap:
params.selectedRoofMaterial.raft === null || params.selectedRoofMaterial.raft === undefined
? params.selectedRoofMaterial.raftBaseCd
: params.roofsData.raft,
roofLayout: params.roofsData.roofLayout === null || params.roofsData.roofLayout === undefined ? 'P' : params.roofsData.roofLayout, roofLayout: params.roofsData.roofLayout === null || params.roofsData.roofLayout === undefined ? 'P' : params.roofsData.roofLayout,
roofPitch: params.roofsData.roofPitch === null || params.roofsData.roofPitch === undefined ? 0 : params.roofsData.roofPitch, roofPitch: params.roofsData.roofPitch === null || params.roofsData.roofPitch === undefined ? 0 : params.roofsData.roofPitch,
roofAngle: params.roofsData.roofAngle === null || params.roofsData.roofAngle === undefined ? 0 : params.roofsData.roofAngle, roofAngle: params.roofsData.roofAngle === null || params.roofsData.roofAngle === undefined ? 0 : params.roofsData.roofAngle,

View File

@ -212,7 +212,6 @@ export function useRoofAllocationSetting(id) {
} }
await post({ url: `/api/canvas-management/roof-allocation-settings`, data: patternData }).then((res) => { await post({ url: `/api/canvas-management/roof-allocation-settings`, data: patternData }).then((res) => {
swalFire({ text: getMessage(res.returnMessage) })
setIsGlobalLoading(false) setIsGlobalLoading(false)
}) })
@ -315,7 +314,8 @@ export function useRoofAllocationSetting(id) {
setSurfaceShapePattern(currentObject, roofDisplay.column, false, selectedRoofMaterial, true) setSurfaceShapePattern(currentObject, roofDisplay.column, false, selectedRoofMaterial, true)
drawDirectionArrow(currentObject) drawDirectionArrow(currentObject)
modifyModuleSelectionData() modifyModuleSelectionData()
closeAll() // closeAll()
closePopup(id)
basicSettingSave() basicSettingSave()
setModuleSelectionData({ ...moduleSelectionData, roofConstructions: newRoofList }) setModuleSelectionData({ ...moduleSelectionData, roofConstructions: newRoofList })
} }

View File

@ -1,14 +1,22 @@
import { useRef } from 'react' import { useEffect, useRef } from 'react'
import { useRecoilValue, useSetRecoilState } from 'recoil' import { useRecoilValue, useSetRecoilState } from 'recoil'
import { canvasState, canvasZoomState, currentMenuState, textModeState } from '@/store/canvasAtom' import { canvasState, canvasZoomState, currentMenuState, textModeState } from '@/store/canvasAtom'
import { fabric } from 'fabric' import { fabric } from 'fabric'
import { calculateDistance, calculateDistancePoint, calculateIntersection, distanceBetweenPoints, findClosestPoint } from '@/util/canvas-util' import {
calculateDistance,
calculateDistancePoint,
calculateIntersection,
distanceBetweenPoints,
findClosestPoint,
getInterSectionLineNotOverCoordinate,
} from '@/util/canvas-util'
import { useAdsorptionPoint } from '@/hooks/useAdsorptionPoint' import { useAdsorptionPoint } from '@/hooks/useAdsorptionPoint'
import { useDotLineGrid } from '@/hooks/useDotLineGrid' import { useDotLineGrid } from '@/hooks/useDotLineGrid'
import { useTempGrid } from '@/hooks/useTempGrid' import { useTempGrid } from '@/hooks/useTempGrid'
import { gridColorState } from '@/store/gridAtom' import { gridColorState } from '@/store/gridAtom'
import { gridDisplaySelector } from '@/store/settingAtom' import { gridDisplaySelector } from '@/store/settingAtom'
import { POLYGON_TYPE } from '@/common/common' import { MENU, POLYGON_TYPE } from '@/common/common'
import useMenu from '@/hooks/common/useMenu'
export function useEvent() { export function useEvent() {
const canvas = useRecoilValue(canvasState) const canvas = useRecoilValue(canvasState)
@ -18,6 +26,7 @@ export function useEvent() {
const setCanvasZoom = useSetRecoilState(canvasZoomState) const setCanvasZoom = useSetRecoilState(canvasZoomState)
const gridColor = useRecoilValue(gridColorState) const gridColor = useRecoilValue(gridColorState)
const isGridDisplay = useRecoilValue(gridDisplaySelector) const isGridDisplay = useRecoilValue(gridDisplaySelector)
const zoom = useRecoilValue(canvasZoomState)
const { adsorptionPointAddMode, adsorptionPointMode, adsorptionRange, getAdsorptionPoints, adsorptionPointAddModeStateEvent } = useAdsorptionPoint() const { adsorptionPointAddMode, adsorptionPointMode, adsorptionRange, getAdsorptionPoints, adsorptionPointAddModeStateEvent } = useAdsorptionPoint()
const { dotLineGridSetting, interval, getClosestLineGrid } = useDotLineGrid() const { dotLineGridSetting, interval, getClosestLineGrid } = useDotLineGrid()
@ -52,6 +61,13 @@ export function useEvent() {
addDefaultEvent() addDefaultEvent()
} }
useEffect(() => {
const whiteMenus = [MENU.BATCH_CANVAS.SURFACE_SHAPE_BATCH, MENU.BATCH_CANVAS.OBJECT_BATCH, MENU.MODULE_CIRCUIT_SETTING.BASIC_SETTING]
if (canvas && !whiteMenus.includes(currentMenu)) {
addCanvasMouseEventListener('mouse:move', defaultMouseMoveEvent)
}
}, [zoom])
const addDefaultEvent = () => { const addDefaultEvent = () => {
//default Event 추가 //default Event 추가
addCanvasMouseEventListener('mouse:move', defaultMouseMoveEvent) addCanvasMouseEventListener('mouse:move', defaultMouseMoveEvent)
@ -146,13 +162,14 @@ export function useEvent() {
...innerLinePoints, ...innerLinePoints,
] ]
if (dotLineGridSetting.LINE || canvas.getObjects().filter((obj) => ['lineGrid', 'tempGrid'].includes(obj.name)).length > 0) { if (dotLineGridSetting.LINE || canvas.getObjects().filter((obj) => ['lineGrid', 'tempGrid'].includes(obj.name)).length > 1) {
const closestLine = getClosestLineGrid(pointer) const closestLine = getClosestLineGrid(pointer)
const horizonLines = canvas.getObjects().filter((obj) => ['lineGrid', 'tempGrid'].includes(obj.name) && obj.direction === 'horizontal') const horizonLines = canvas.getObjects().filter((obj) => ['lineGrid', 'tempGrid'].includes(obj.name) && obj.direction === 'horizontal')
const verticalLines = canvas.getObjects().filter((obj) => ['lineGrid', 'tempGrid'].includes(obj.name) && obj.direction === 'vertical') const verticalLines = canvas.getObjects().filter((obj) => ['lineGrid', 'tempGrid'].includes(obj.name) && obj.direction === 'vertical')
if (!horizonLines || !verticalLines) { if (!horizonLines || !verticalLines) {
drawMouseLine(pointer)
return return
} }
@ -176,6 +193,7 @@ export function useEvent() {
} }
if (!closestVerticalLine || !closestHorizontalLine) { if (!closestVerticalLine || !closestHorizontalLine) {
drawMouseLine(pointer)
return return
} }
@ -260,9 +278,18 @@ export function useEvent() {
arrivalPoint = guideIntersectionPoint arrivalPoint = guideIntersectionPoint
} }
} }
} catch (e) {} } catch (e) {
console.error(e)
}
const horizontalLine = new fabric.Line([-4 * canvas.width, arrivalPoint.y, 4 * canvas.width, arrivalPoint.y], { drawMouseLine(arrivalPoint)
// 캔버스를 다시 그립니다.
canvas?.renderAll()
}
const drawMouseLine = (pointer) => {
const horizontalLine = new fabric.Line([-2 * canvas.width, pointer.y, 2 * canvas.width, pointer.y], {
stroke: 'red', stroke: 'red',
strokeWidth: 1, strokeWidth: 1,
selectable: false, selectable: false,
@ -270,7 +297,7 @@ export function useEvent() {
}) })
// 세로선 // 세로선
const verticalLine = new fabric.Line([arrivalPoint.x, -4 * canvas.height, arrivalPoint.x, 4 * canvas.height], { const verticalLine = new fabric.Line([pointer.x, -2 * canvas.height, pointer.x, 2 * canvas.height], {
stroke: 'red', stroke: 'red',
strokeWidth: 1, strokeWidth: 1,
selectable: false, selectable: false,
@ -279,9 +306,6 @@ export function useEvent() {
// 선들을 캔버스에 추가합니다. // 선들을 캔버스에 추가합니다.
canvas?.add(horizontalLine, verticalLine) canvas?.add(horizontalLine, verticalLine)
// 캔버스를 다시 그립니다.
canvas?.renderAll()
} }
const removeMouseLine = () => { const removeMouseLine = () => {
@ -298,7 +322,12 @@ export function useEvent() {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
//임의 그리드 모드일 경우 //임의 그리드 모드일 경우
let pointer = { x: e.offsetX, y: e.offsetY } let originPointer = { x: e.offsetX, y: e.offsetY }
const mouseLines = canvas.getObjects().filter((obj) => obj.name === 'mouseLine')
let pointer = getInterSectionLineNotOverCoordinate(mouseLines[0], mouseLines[1]) || {
x: Math.round(originPointer.x),
y: Math.round(originPointer.y),
}
const tempGrid = new fabric.Line([-1500, pointer.y, 2500, pointer.y], { const tempGrid = new fabric.Line([-1500, pointer.y, 2500, pointer.y], {
stroke: gridColor, stroke: gridColor,
@ -372,7 +401,7 @@ export function useEvent() {
const removeMouseEvent = (type) => { const removeMouseEvent = (type) => {
mouseEventListeners.current = mouseEventListeners.current.filter((event) => { mouseEventListeners.current = mouseEventListeners.current.filter((event) => {
if (event.eventType === type) { if (event.eventType === type) {
canvas.off(type, event.handler) canvas?.off(type, event.handler)
return false return false
} }
return true return true

View File

@ -159,5 +159,6 @@ export const useLine = () => {
addPitchText, addPitchText,
removePitchText, removePitchText,
addPitchTextsByOuterLines, addPitchTextsByOuterLines,
getLengthByLine,
} }
} }

View File

@ -3,9 +3,17 @@
import { useContext, useEffect, useState } from 'react' import { useContext, useEffect, useState } from 'react'
import { usePathname, useRouter } from 'next/navigation' import { usePathname, useRouter } from 'next/navigation'
import { useRecoilState, useResetRecoilState } from 'recoil' import { useRecoilState, useResetRecoilState, useSetRecoilState } from 'recoil'
import { canvasState, currentCanvasPlanState, plansState, canvasSettingState, currentObjectState, moduleSetupSurfaceState } from '@/store/canvasAtom' import {
canvasState,
currentCanvasPlanState,
plansState,
canvasSettingState,
currentObjectState,
moduleSetupSurfaceState,
currentMenuState,
} from '@/store/canvasAtom'
import { useAxios } from '@/hooks/useAxios' import { useAxios } from '@/hooks/useAxios'
import { useMessage } from '@/hooks/useMessage' import { useMessage } from '@/hooks/useMessage'
import { useSwal } from '@/hooks/useSwal' import { useSwal } from '@/hooks/useSwal'
@ -22,6 +30,7 @@ import { useCanvasPopupStatusController } from './common/useCanvasPopupStatusCon
import { useCanvasMenu } from './common/useCanvasMenu' import { useCanvasMenu } from './common/useCanvasMenu'
import { QcastContext } from '@/app/QcastProvider' import { QcastContext } from '@/app/QcastProvider'
import { unescapeString } from '@/util/common-utils' import { unescapeString } from '@/util/common-utils'
import { useTrestle } from '@/hooks/module/useTrestle'
/** /**
* 플랜 처리 * 플랜 처리
@ -33,7 +42,7 @@ export function usePlan(params = {}) {
const { floorPlanState } = useContext(FloorPlanContext) const { floorPlanState } = useContext(FloorPlanContext)
const [selectedPlan, setSelectedPlan] = useState(null) const [selectedPlan, setSelectedPlan] = useState(null)
const setCurrentMenu = useSetRecoilState(currentMenuState)
const [canvas, setCanvas] = useRecoilState(canvasState) const [canvas, setCanvas] = useRecoilState(canvasState)
const [currentCanvasPlan, setCurrentCanvasPlan] = useRecoilState(currentCanvasPlanState) const [currentCanvasPlan, setCurrentCanvasPlan] = useRecoilState(currentCanvasPlanState)
@ -46,7 +55,7 @@ export function usePlan(params = {}) {
const { getMessage } = useMessage() const { getMessage } = useMessage()
const { get, post, promisePost, promisePut, promiseDel, promiseGet } = useAxios() const { get, post, promisePost, promisePut, promiseDel, promiseGet } = useAxios()
const { setEstimateContextState } = useEstimateController() const { setEstimateContextState, handleDeleteEstimate, handleEstimateImageCopy } = useEstimateController()
const resetOuterLinePoints = useResetRecoilState(outerLinePointsState) const resetOuterLinePoints = useResetRecoilState(outerLinePointsState)
const resetPlacementShapeDrawingPoints = useResetRecoilState(placementShapeDrawingPointsState) const resetPlacementShapeDrawingPoints = useResetRecoilState(placementShapeDrawingPointsState)
@ -70,6 +79,7 @@ export function usePlan(params = {}) {
const resetCurrentObject = useResetRecoilState(currentObjectState) const resetCurrentObject = useResetRecoilState(currentObjectState)
//선택된 모듈 배치면 초기화 //선택된 모듈 배치면 초기화
const resetModuleSetupSurface = useResetRecoilState(moduleSetupSurfaceState) const resetModuleSetupSurface = useResetRecoilState(moduleSetupSurfaceState)
const { isAllComplete } = useTrestle()
/** /**
* 마우스 포인터의 가이드라인 제거 * 마우스 포인터의 가이드라인 제거
@ -164,7 +174,11 @@ export function usePlan(params = {}) {
*/ */
const saveCanvas = async (saveAlert = true) => { const saveCanvas = async (saveAlert = true) => {
const canvasStatus = currentCanvasData('save') const canvasStatus = currentCanvasData('save')
await putCanvasStatus(canvasStatus, saveAlert) const result = await putCanvasStatus(canvasStatus, saveAlert)
//캔버스 저장 완료 후
if (result && !isAllComplete()) {
handleDeleteEstimate(currentCanvasPlan)
}
} }
/** /**
@ -293,6 +307,9 @@ export function usePlan(params = {}) {
setModuleSelectionDataStore(copyData) setModuleSelectionDataStore(copyData)
if (copyData.module) setSelectedModules(copyData.module) if (copyData.module) setSelectedModules(copyData.module)
setSelectedMenu(currentSelectedMenu) setSelectedMenu(currentSelectedMenu)
//이미지 복사
handleEstimateImageCopy(planData.objectNo, planData.planNo, planData.objectNo, newPlan.planNo)
} else { } else {
setSelectedMenu('placement') setSelectedMenu('placement')
} }
@ -310,20 +327,25 @@ export function usePlan(params = {}) {
* @param {boolean} saveAlert - 저장 완료 알림 표시 여부 * @param {boolean} saveAlert - 저장 완료 알림 표시 여부
*/ */
const putCanvasStatus = async (canvasStatus, saveAlert = true) => { const putCanvasStatus = async (canvasStatus, saveAlert = true) => {
let rtn = false
const planData = { const planData = {
id: currentCanvasPlan.id, id: currentCanvasPlan.id,
bgImageName: currentCanvasPlan?.bgImageName ?? null, bgImageName: currentCanvasPlan?.bgImageName ?? null,
mapPositionAddress: currentCanvasPlan?.mapPositionAddress ?? null, mapPositionAddress: currentCanvasPlan?.mapPositionAddress ?? null,
canvasStatus: canvasToDbFormat(canvasStatus), canvasStatus: canvasToDbFormat(canvasStatus),
} }
await promisePut({ url: '/api/canvas-management/canvas-statuses', data: planData }) await promisePut({ url: '/api/canvas-management/canvas-statuses', data: planData })
.then((res) => { .then((res) => {
setPlans((plans) => plans.map((plan) => (plan.id === currentCanvasPlan.id ? { ...plan, canvasStatus: canvasStatus } : plan))) setPlans((plans) => plans.map((plan) => (plan.id === currentCanvasPlan.id ? { ...plan, canvasStatus: canvasStatus } : plan)))
if (saveAlert) swalFire({ text: getMessage('plan.message.save') }) if (saveAlert) swalFire({ text: getMessage('plan.message.save') })
rtn = true
}) })
.catch((error) => { .catch((error) => {
swalFire({ text: error.message, icon: 'error' }) swalFire({ text: error.message, icon: 'error' })
setIsGlobalLoading(false)
}) })
return rtn
} }
/** /**
@ -577,8 +599,10 @@ export function usePlan(params = {}) {
* plan canvasStatus 초기화 * plan canvasStatus 초기화
*/ */
const resetCanvasStatus = () => { const resetCanvasStatus = () => {
setCurrentCanvasPlan((prev) => ({ ...prev, canvasStatus: null })) setCurrentCanvasPlan((prev) => ({ ...prev, canvasStatus: null, objectNo: null, planNo: null, id: null }))
setPlans((plans) => plans.map((plan) => ({ ...plan, canvasStatus: null }))) setPlans((plans) => plans.map((plan) => ({ ...plan, canvasStatus: null })))
setCurrentMenu(null)
setSelectedMenu(null)
} }
/** /**
@ -592,8 +616,10 @@ export function usePlan(params = {}) {
if (pathname === '/floor-plan/estimate/5' || pathname === '/floor-plan/simulator/6') { if (pathname === '/floor-plan/estimate/5' || pathname === '/floor-plan/simulator/6') {
await getCanvasByObjectNo(objectNo, planNo).then((res) => { await getCanvasByObjectNo(objectNo, planNo).then((res) => {
if (res.length > 0) { if (res.length > 0) {
setCurrentCanvasPlan((prev) => ({ ...prev, canvasStatus: res.find((plan) => plan.planNo === planNo).canvasStatus })) // setCurrentCanvasPlan((prev) => ({ ...prev, canvasStatus: res.find((plan) => plan.planNo === planNo).canvasStatus }))
setPlans((plans) => plans.map((plan) => ({ ...plan, canvasStatus: res.find((resPlan) => resPlan.planNo === plan.planNo).canvasStatus }))) // setPlans((plans) => plans.map((plan) => ({ ...plan, canvasStatus: res.find((resPlan) => resPlan.planNo === plan.planNo).canvasStatus })))
setCurrentCanvasPlan(res.find((plan) => plan.planNo === planNo))
setPlans(res)
} }
}) })
} }

View File

@ -15,6 +15,7 @@ import { flowDisplaySelector } from '@/store/settingAtom'
import { fontSelector } from '@/store/fontAtom' import { fontSelector } from '@/store/fontAtom'
import { QLine } from '@/components/fabric/QLine' import { QLine } from '@/components/fabric/QLine'
import { LINE_TYPE, POLYGON_TYPE } from '@/common/common' import { LINE_TYPE, POLYGON_TYPE } from '@/common/common'
import { useLine } from '@/hooks/useLine'
export const usePolygon = () => { export const usePolygon = () => {
const canvas = useRecoilValue(canvasState) const canvas = useRecoilValue(canvasState)
@ -24,6 +25,8 @@ export const usePolygon = () => {
const currentAngleType = useRecoilValue(currentAngleTypeSelector) const currentAngleType = useRecoilValue(currentAngleTypeSelector)
const pitchText = useRecoilValue(pitchTextSelector) const pitchText = useRecoilValue(pitchTextSelector)
const { getLengthByLine } = useLine()
const addPolygon = (points, options, isAddCanvas = true) => { const addPolygon = (points, options, isAddCanvas = true) => {
const polygon = new QPolygon(points, { const polygon = new QPolygon(points, {
...options, ...options,
@ -1093,25 +1096,37 @@ export const usePolygon = () => {
}) })
if (startFlag && endFlag) { if (startFlag && endFlag) {
if (!representLines.includes(line)) { if (!representLines.includes(line) && line.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
representLines.push(line) representLines.push(line)
} }
} }
}) })
// blue로 생성된 것들은 대표라인이 될 수 없음.
// representLines = representLines.filter((line) => line.stroke !== 'blue')
// representLines중 가장 긴 line을 찾는다. // representLines중 가장 긴 line을 찾는다.
representLines.forEach((line) => { representLines.forEach((line) => {
if (!representLine) { if (!representLine) {
representLine = line representLine = line
} else { } else {
if (representLine.length < line.length) { if (getLengthByLine(representLine) < getLengthByLine(line)) {
representLine = line representLine = line
} }
} }
}) })
const direction = polygon.direction ?? representLine?.direction
if (!representLine) {
representLines = representLines.filter((line) => line.stroke !== 'blue')
representLines.forEach((line) => {
if (!representLine) {
representLine = line
} else {
if (representLine.length < line.length) {
representLine = line
}
}
})
}
const direction = polygon.direction ?? representLine?.direction ?? ''
const polygonDirection = polygon.direction const polygonDirection = polygon.direction
switch (direction) { switch (direction) {
@ -1178,7 +1193,7 @@ export const usePolygon = () => {
}) })
canvas.add(roof) canvas.add(roof)
// addLengthText(roof) addLengthText(roof)
canvas.remove(polygon) canvas.remove(polygon)
canvas.renderAll() canvas.renderAll()
}) })

View File

@ -1,5 +1,7 @@
import { useRecoilState } from 'recoil' import { useRecoilState, useResetRecoilState } from 'recoil'
import { contextPopupState, popupState } from '@/store/popupAtom' import { contextPopupState, popupState } from '@/store/popupAtom'
import { useEffect } from 'react'
import { commonUtilsState } from '@/store/commonUtilsAtom'
/** /**
* 팝업 관리 * 팝업 관리
@ -128,11 +130,17 @@ export function usePopup() {
} }
} }
const targetClose = (type) => {
popup[type] = []
setPopup({ ...popup, [type]: [] })
}
return { return {
popup, popup,
addPopup, addPopup,
closePopup, closePopup,
closePopups, closePopups,
closeAll, closeAll,
targetClose,
} }
} }

View File

@ -2,6 +2,7 @@ import { canvasState, tempGridModeState } from '@/store/canvasAtom'
import { useRecoilState, useRecoilValue } from 'recoil' import { useRecoilState, useRecoilValue } from 'recoil'
import { gridColorState } from '@/store/gridAtom' import { gridColorState } from '@/store/gridAtom'
import { gridDisplaySelector } from '@/store/settingAtom' import { gridDisplaySelector } from '@/store/settingAtom'
import { useMouse } from '@/hooks/useMouse'
const GRID_PADDING = 5 const GRID_PADDING = 5
export function useTempGrid() { export function useTempGrid() {
@ -9,10 +10,10 @@ export function useTempGrid() {
const gridColor = useRecoilValue(gridColorState) const gridColor = useRecoilValue(gridColorState)
const isGridDisplay = useRecoilValue(gridDisplaySelector) const isGridDisplay = useRecoilValue(gridDisplaySelector)
const [tempGridMode, setTempGridMode] = useRecoilState(tempGridModeState) const [tempGridMode, setTempGridMode] = useRecoilState(tempGridModeState)
const { getIntersectMousePoint } = useMouse()
const tempGridModeStateLeftClickEvent = (e) => { const tempGridModeStateLeftClickEvent = (e) => {
//임의 그리드 모드일 경우 //임의 그리드 모드일 경우
let pointer = canvas.getPointer(e.e) let pointer = getIntersectMousePoint(e)
const tempGrid = new fabric.Line([pointer.x, -1500, pointer.x, 2500], { const tempGrid = new fabric.Line([pointer.x, -1500, pointer.x, 2500], {
stroke: gridColor, stroke: gridColor,

View File

@ -612,11 +612,12 @@
"qna.reg.header.contents": "お問い合わせ内容", "qna.reg.header.contents": "お問い合わせ内容",
"qna.reg.header.fileList": "ファイル添付", "qna.reg.header.fileList": "ファイル添付",
"qna.reg.header.save": "保存", "qna.reg.header.save": "保存",
"qna.reg.alert.require.qstMail": "無効なメール形式です。",
"qna.reg.alert.require.regUserNm": "名前を入力してください。", "qna.reg.alert.require.regUserNm": "名前を入力してください。",
"qna.reg.alert.select.type": "お問い合わせ区分を選択してください。", "qna.reg.alert.select.type": "お問い合わせ区分を選択してください。",
"qna.reg.alert.require.title": "タイトルを入力してください。", "qna.reg.alert.require.title": "タイトルを入力してください。",
"qna.reg.alert.require.contents": "内容を入力してください。", "qna.reg.alert.require.contents": "内容を入力してください。",
"qna.reg.confirm.save": "1:1お問い合わせを登録しますか? <br/>Hanwha Japan 担当者にお問い合わせメールが送信されます。", "qna.reg.confirm.save": "お問い合わせを登録しますか? <br/>Hanwha Japan 担当者にお問い合わせメールが送信されます。",
"qna.reg.alert.save": "保存されました。", "qna.reg.alert.save": "保存されました。",
"qna.reg.alert.saveFail": "保存に失敗しました。", "qna.reg.alert.saveFail": "保存に失敗しました。",
"qna.list.header.regNm": "登録者", "qna.list.header.regNm": "登録者",
@ -665,9 +666,9 @@
"join.sub1.title": "販売代理店情報", "join.sub1.title": "販売代理店情報",
"join.sub1.comment": "※登録される販売店の会社名を入力してください。 2次店は「○○販売株式会社2次店××設備株式会社」でご記入ください。", "join.sub1.comment": "※登録される販売店の会社名を入力してください。 2次店は「○○販売株式会社2次店××設備株式会社」でご記入ください。",
"join.sub1.storeQcastNm": "販売代理店名", "join.sub1.storeQcastNm": "販売代理店名",
"join.sub1.storeQcastNm_placeholder": "株式会社エネルギーギアソリューションアンサービス(2次点山口周期販売有限会社", "join.sub1.storeQcastNm_placeholder": "ハンファジャパン株式会社",
"join.sub1.storeQcastNmKana": "販売代理店名フリガナ", "join.sub1.storeQcastNmKana": "販売代理店名フリガナ",
"join.sub1.storeQcastNmKana_placeholder": "株式会社エネルギーギアソリューション", "join.sub1.storeQcastNmKana_placeholder": "ハンファジャパンカブシキカイシャ",
"join.sub1.postCd": "郵便番号", "join.sub1.postCd": "郵便番号",
"join.sub1.postCd_placeholder": "数字7桁", "join.sub1.postCd_placeholder": "数字7桁",
"join.sub1.addr": "住所", "join.sub1.addr": "住所",
@ -680,7 +681,7 @@
"join.sub2.title": "担当者情報", "join.sub2.title": "担当者情報",
"join.sub2.userNm": "担当者名", "join.sub2.userNm": "担当者名",
"join.sub2.userNmKana": "担当者名ふりがな", "join.sub2.userNmKana": "担当者名ふりがな",
"join.sub2.userId": "申請ID", "join.sub2.userId": "ユーザーID",
"join.sub2.email": "メールアドレス", "join.sub2.email": "メールアドレス",
"join.sub2.telNo": "電話番号", "join.sub2.telNo": "電話番号",
"join.sub2.telNo_placeholder": "00 0000 0000", "join.sub2.telNo_placeholder": "00 0000 0000",
@ -688,12 +689,12 @@
"join.sub2.fax_placeholder": "00 0000 0000", "join.sub2.fax_placeholder": "00 0000 0000",
"join.sub2.category": "部署名", "join.sub2.category": "部署名",
"join.btn.login_page": "ログイン画面に移動", "join.btn.login_page": "ログイン画面に移動",
"join.btn.approval_request": "ID承認要求", "join.btn.approval_request": "ID申請",
"join.complete.title": "HANASYS設計ログインID発行申請完了", "join.complete.title": "HANASYS設計ログインID発行申請完了",
"join.complete.contents": "※申請したIDが承認されると、担当者情報に入力したEメールアドレスにログイン関連案内メールが送信されます。", "join.complete.contents": "※申請したIDが承認されると、担当者情報に入力したEメールアドレスにログイン関連案内メールが送信されます。",
"join.complete.email_comment": "担当者のメールアドレス", "join.complete.email_comment": "担当者のメールアドレス",
"join.validation.check1": "{0}形式を確認してください。", "join.validation.check1": "{0}形式を確認してください。",
"join.complete.save.confirm": "ハンファジャパンの担当者にID承認が要求された場合、これ以上情報を修正することはできません。申請しますか?", "join.complete.save.confirm": "ID申請を完了後は申請情報の修正が出来ません。申請しますか?",
"stuff.gridHeader.lastEditDatetime": "更新日時", "stuff.gridHeader.lastEditDatetime": "更新日時",
"stuff.gridHeader.objectNo": "物件番号", "stuff.gridHeader.objectNo": "物件番号",
"stuff.gridHeader.planTotCnt": "プラン数", "stuff.gridHeader.planTotCnt": "プラン数",
@ -955,7 +956,7 @@
"estimate.detail.dragFileGuide": "(※北面設置の場合、ファイル添付が必須です。)", "estimate.detail.dragFileGuide": "(※北面設置の場合、ファイル添付が必須です。)",
"estimate.detail.header.fileList1": "ファイル添付", "estimate.detail.header.fileList1": "ファイル添付",
"estimate.detail.fileList.btn": "ファイル選択", "estimate.detail.fileList.btn": "ファイル選択",
"estimate.detail.fileList.extCheck": "画像ファイルのみ添付可能です。", "estimate.detail.fileList.extCheck": "画像、PDF、Excel ファイルのみ添付できます。",
"estimate.detail.header.fileList2": "添付ファイル一覧", "estimate.detail.header.fileList2": "添付ファイル一覧",
"estimate.detail.fileList2.btn.return": "復元", "estimate.detail.fileList2.btn.return": "復元",
"estimate.detail.header.specialEstimate": "見積特記事項", "estimate.detail.header.specialEstimate": "見積特記事項",
@ -1077,6 +1078,7 @@
"module.not.found": "インストールモジュールを選択してください。", "module.not.found": "インストールモジュールを選択してください。",
"module.circuit.minimun.error": "回路番号は1以上の数値を入力してください。", "module.circuit.minimun.error": "回路番号は1以上の数値を入力してください。",
"module.already.exist.error": "回路番号が同じで異なるパワーコンディショナのモジュールがあります。 別の回路番号を設定してください。", "module.already.exist.error": "回路番号が同じで異なるパワーコンディショナのモジュールがあります。 別の回路番号を設定してください。",
"module.circuit.fix.not.same.roof.error": "異なる屋根面のモジュールが選択されています。 モジュールの選択をや直してください。",
"construction.length.difference": "屋根面工法をすべて選択してください。", "construction.length.difference": "屋根面工法をすべて選択してください。",
"menu.validation.canvas.roof": "パネルを配置するには、屋根面を入力する必要があります。", "menu.validation.canvas.roof": "パネルを配置するには、屋根面を入力する必要があります。",
"batch.object.outside.roof": "オブジェクトは屋根に設置する必要があります。", "batch.object.outside.roof": "オブジェクトは屋根に設置する必要があります。",
@ -1103,6 +1105,11 @@
"module.layout.setup.max.count": "モジュールの単体での最大段数は{0}、最大列数は{1}です。 (JA)", "module.layout.setup.max.count": "モジュールの単体での最大段数は{0}、最大列数は{1}です。 (JA)",
"module.layout.setup.max.count.multiple": "モジュール{0}の単体での最大段数は{1}、最大列数は{2}です。 (JA)", "module.layout.setup.max.count.multiple": "モジュール{0}の単体での最大段数は{1}、最大列数は{2}です。 (JA)",
"roofAllocation.not.found": "割り当てる屋根がありません。 (JA)", "roofAllocation.not.found": "割り当てる屋根がありません。 (JA)",
"modal.module.basic.setting.module.placement.info": "モジュール配置案内",
"modal.module.basic.setting.module.placement.info.batch": "千鳥配置を手動で行う際の注意事項",
"modal.module.basic.setting.module.placement.info.batch.content1": "千鳥配置する時に図のような配置ができてしまいますが、正常な積算ができません。",
"modal.module.basic.setting.module.placement.info.batch.content2": "千鳥で配置する時は、千鳥配置を「する」にして、モジュールが吸着されるようにして下さい。",
"modal.module.basic.setting.module.placement.info.module": "屋根材別 単一・混合モジュールの最大段数",
"modal.module.basic.setting.module.placement.max.size.check": "屋根材別モジュールの単体の単体での最大段数、2種混合の段数を確認して下さい", "modal.module.basic.setting.module.placement.max.size.check": "屋根材別モジュールの単体の単体での最大段数、2種混合の段数を確認して下さい",
"modal.module.basic.setting.module.placement.max.row": "単体で\rの最大段数", "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.max.rows.multiple": "2種混合時\rの最大段数",

View File

@ -612,6 +612,7 @@
"qna.reg.header.contents": "문의정보", "qna.reg.header.contents": "문의정보",
"qna.reg.header.fileList": "파일첨부", "qna.reg.header.fileList": "파일첨부",
"qna.reg.header.save": "저장", "qna.reg.header.save": "저장",
"qna.reg.alert.require.qstMail": "올바르지 않은 이메일 형식입니다.",
"qna.reg.alert.require.regUserNm": "이름을 입력하세요.", "qna.reg.alert.require.regUserNm": "이름을 입력하세요.",
"qna.reg.alert.select.type": "문의구분을 선택하세요.", "qna.reg.alert.select.type": "문의구분을 선택하세요.",
"qna.reg.alert.require.title": "제목을 입력하세요.", "qna.reg.alert.require.title": "제목을 입력하세요.",
@ -665,9 +666,9 @@
"join.sub1.title": "판매대리점 정보", "join.sub1.title": "판매대리점 정보",
"join.sub1.comment": "※ 등록되는 리셀러의 회사 이름을 입력하십시오. (2차점은 「○○판매주식회사(2차점××설비주식회사)」로 기입해 주세요.)", "join.sub1.comment": "※ 등록되는 리셀러의 회사 이름을 입력하십시오. (2차점은 「○○판매주식회사(2차점××설비주식회사)」로 기입해 주세요.)",
"join.sub1.storeQcastNm": "판매대리점명", "join.sub1.storeQcastNm": "판매대리점명",
"join.sub1.storeQcastNm_placeholder": "주식회사 에너지 기어 솔루션 앤 서비스 (2차점: 야마구치 주기 판매 유한회사)", "join.sub1.storeQcastNm_placeholder": "한화재팬 주식회사",
"join.sub1.storeQcastNmKana": "판매대리점명 후리가나", "join.sub1.storeQcastNmKana": "판매대리점명 후리가나",
"join.sub1.storeQcastNmKana_placeholder": "주식회사 에너지 기어 솔루션", "join.sub1.storeQcastNmKana_placeholder": "한화재팬 카부시키 카이샤",
"join.sub1.postCd": "우편번호", "join.sub1.postCd": "우편번호",
"join.sub1.postCd_placeholder": "숫자 7자리", "join.sub1.postCd_placeholder": "숫자 7자리",
"join.sub1.addr": "주소", "join.sub1.addr": "주소",
@ -680,7 +681,7 @@
"join.sub2.title": "담당자 정보", "join.sub2.title": "담당자 정보",
"join.sub2.userNm": "담당자명", "join.sub2.userNm": "담당자명",
"join.sub2.userNmKana": "담당자명 후리가나", "join.sub2.userNmKana": "담당자명 후리가나",
"join.sub2.userId": "신청 ID", "join.sub2.userId": "사용자 ID",
"join.sub2.email": "이메일 주소", "join.sub2.email": "이메일 주소",
"join.sub2.telNo": "전화번호", "join.sub2.telNo": "전화번호",
"join.sub2.telNo_placeholder": "00 0000 0000", "join.sub2.telNo_placeholder": "00 0000 0000",
@ -1104,6 +1105,11 @@
"module.layout.setup.max.count": "모듈의 최대 단수는 {0}, 최대 열수는 {1} 입니다.", "module.layout.setup.max.count": "모듈의 최대 단수는 {0}, 최대 열수는 {1} 입니다.",
"module.layout.setup.max.count.multiple": "모듈 {0}번의 최대 단수는 {1}, 최대 열수는 {2} 입니다.", "module.layout.setup.max.count.multiple": "모듈 {0}번의 최대 단수는 {1}, 최대 열수는 {2} 입니다.",
"roofAllocation.not.found": "할당할 지붕이 없습니다.", "roofAllocation.not.found": "할당할 지붕이 없습니다.",
"modal.module.basic.setting.module.placement.info": "모듈 배치 안내",
"modal.module.basic.setting.module.placement.info.batch": "치도리 수동 배치 시 유의사항",
"modal.module.basic.setting.module.placement.info.batch.content1": "치조 배치할 때 그림과 같은 배치가 되어 버립니다만, 정상적인 적산을 할 수 없습니다.",
"modal.module.basic.setting.module.placement.info.batch.content2": "치조로 배치할 때는, 치조 배치를 「한다」로 하고, 모듈이 흡착되도록 해 주세요.",
"modal.module.basic.setting.module.placement.info.module": "지붕재별 단일·혼합 모듈 최대 단수",
"modal.module.basic.setting.module.placement.max.size.check": "지붕재별 모듈 단체의 최대 단수, 2종 혼합 단수를 확인하십시오.", "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.row": "최대 단수",
"modal.module.basic.setting.module.placement.max.rows.multiple": "2종 혼합 최대단수", "modal.module.basic.setting.module.placement.max.rows.multiple": "2종 혼합 최대단수",

View File

@ -37,11 +37,11 @@ export const subMenusState = atom({
// 지붕덮개 // 지붕덮개
{ id: 0, name: 'plan.menu.roof.cover.outline.drawing', menu: MENU.ROOF_COVERING.EXTERIOR_WALL_LINE }, { id: 0, name: 'plan.menu.roof.cover.outline.drawing', menu: MENU.ROOF_COVERING.EXTERIOR_WALL_LINE },
{ id: 1, name: 'plan.menu.roof.cover.roof.shape.setting', menu: MENU.ROOF_COVERING.ROOF_SHAPE_SETTINGS }, { id: 1, name: 'plan.menu.roof.cover.roof.shape.setting', menu: MENU.ROOF_COVERING.ROOF_SHAPE_SETTINGS },
{ // {
id: 2, // id: 2,
name: 'plan.menu.roof.cover.roof.shape.passivity.setting', // name: 'plan.menu.roof.cover.roof.shape.passivity.setting',
menu: MENU.ROOF_COVERING.ROOF_SHAPE_PASSIVITY_SETTINGS, // menu: MENU.ROOF_COVERING.ROOF_SHAPE_PASSIVITY_SETTINGS,
}, // },
{ id: 3, name: 'plan.menu.roof.cover.auxiliary.line.drawing', menu: MENU.ROOF_COVERING.HELP_LINE_DRAWING }, { id: 3, name: 'plan.menu.roof.cover.auxiliary.line.drawing', menu: MENU.ROOF_COVERING.HELP_LINE_DRAWING },
{ id: 4, name: 'plan.menu.roof.cover.eaves.kerava.edit', menu: MENU.ROOF_COVERING.EAVES_KERAVA_EDIT }, { id: 4, name: 'plan.menu.roof.cover.eaves.kerava.edit', menu: MENU.ROOF_COVERING.EAVES_KERAVA_EDIT },
{ id: 5, name: 'plan.menu.roof.cover.movement.shape.updown', menu: MENU.ROOF_COVERING.MOVEMENT_SHAPE_UPDOWN }, { id: 5, name: 'plan.menu.roof.cover.movement.shape.updown', menu: MENU.ROOF_COVERING.MOVEMENT_SHAPE_UPDOWN },

View File

@ -2457,3 +2457,22 @@ $alert-color: #101010;
} }
} }
} }
// 2025-05-30 지붕 모듈
.roof-warning-img-wrap{
display: flex;
align-items: center;
gap: 15px;
justify-content: center;
}
.roof-content-tab-wrap{
display: flex;
padding-top: 10px;
}
.hide-tab-contents{
&.hide{
height: 0;
overflow: hidden;
}
}

View File

@ -447,10 +447,11 @@
border-top: none; border-top: none;
.community_detail-file-wrap{ .community_detail-file-wrap{
padding-top: 0; padding-top: 0;
margin-bottom: 24px;
} }
.community_detail-inner{ .community_detail-inner{
max-height: 110px; max-height: 110px;
margin-top: 24px; margin-top: 0;
margin-bottom: 0; margin-bottom: 0;
} }
} }
@ -461,12 +462,14 @@
border: 1px solid #101010; border: 1px solid #101010;
.community_detail-inner{ .community_detail-inner{
max-height: 110px; max-height: 110px;
margin-top: 20px;
margin-bottom: 0;
} }
.community_detail-file-wrap{ .community_detail-file-wrap{
border-top: 1px solid #D4DCE7; border-top: 1px solid #D4DCE7;
padding: 16px 0 0 0; padding: 16px 0 0 0;
border-bottom: none; border-bottom: none;
margin-top: 20px;
} }
} }

2
startscript-cluster1.js Normal file
View File

@ -0,0 +1,2 @@
var exec = require('child_process').exec
exec('yarn start:cluster1', { windowsHide: true })

2
startscript-cluster2.js Normal file
View File

@ -0,0 +1,2 @@
var exec = require('child_process').exec
exec('yarn start:cluster2', { windowsHide: true })

2
startscript-dev.js Normal file
View File

@ -0,0 +1,2 @@
var exec = require('child_process').exec
exec('yarn start:dev', { windowsHide: true })

View File

@ -1,2 +1,2 @@
var exec = require('child_process').exec var exec = require('child_process').exec
exec('yarn dev -p 5000', { windowsHide: true }) exec('yarn local:dev -p 5000', { windowsHide: true })