dev #120

Merged
ysCha merged 3 commits from dev into dev-deploy 2025-06-13 15:32:30 +09:00
62 changed files with 1007 additions and 280 deletions
Showing only changes of commit 0430be1945 - Show all commits

View File

@ -1,8 +1,10 @@
NEXT_PUBLIC_RUN_MODE="development"
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"
NEXT_PUBLIC_API_HOST_URL="https://dev.hanasys.jp"
SESSION_SECRET="i3iHH1yp2/2SpQSIySQ4bpyc4g0D+zCF9FAn5xUG0+Y="
@ -25,4 +27,6 @@ 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"
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_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="
@ -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_MUSUBI_AUTO_LOGIN_URL="http://q-musubi-stg.q-cells.jp:8120/qm/login/autoLogin"
AWS_REGION="ap-northeast-2"
AMPLIFY_BUCKET="interplug"
AWS_ACCESS_KEY_ID="AKIAVWMWJCUXFHEAZ4FR"
AWS_SECRET_ACCESS_KEY="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"
NEXT_PUBLIC_AWS_S3_BASE_URL="//files.hanasys.jp"
# AWS_REGION="ap-northeast-2"
# AMPLIFY_BUCKET="interplug"
# AWS_ACCESS_KEY_ID="AKIAVWMWJCUXFHEAZ4FR"
# AWS_SECRET_ACCESS_KEY="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"
# NEXT_PUBLIC_AWS_S3_BASE_URL="//files.hanasys.jp"
# 실제 일본 서버
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,
exec_mode: 'fork',
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",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start:cluster1": "next start -p 5000",
"start:cluster2": "next start -p 5001",
"start:dev": "next start -p 5010",
"dev": "env-cmd -f .env.localhost next dev",
"local:dev": "env-cmd -f .env.local.dev next dev",
"build": "env-cmd -f .env.production next build",
"build:dev": "env-cmd -f .env.development next build",
"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",
"serve": "node server.js"
},
@ -18,10 +21,12 @@
"big.js": "^6.2.2",
"chart.js": "^4.4.6",
"dayjs": "^1.11.13",
"env-cmd": "^10.1.0",
"fabric": "^5.3.0",
"framer-motion": "^11.2.13",
"fs": "^0.0.1-security",
"iron-session": "^8.0.2",
"jimp": "^1.6.0",
"js-cookie": "^3.0.5",
"mathjs": "^13.0.2",
"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.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

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

View File

@ -1,7 +1,8 @@
import { NextResponse } from 'next/server'
import { S3Client, PutObjectCommand, DeleteObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3'
import sharp from 'sharp'
import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3'
import { v4 as uuidv4 } from 'uuid'
import { Jimp } from 'jimp'
const Bucket = process.env.AMPLIFY_BUCKET
const s3 = new S3Client({
region: process.env.AWS_REGION,
@ -23,8 +24,6 @@ const checkArea = (obj) => {
const cropImage = async (Key, width, height, left, top) => {
try {
const checkResult = checkArea({ width, height, left, top })
// Get the image from S3
const { Body } = await s3.send(
new GetObjectCommand({
@ -33,28 +32,45 @@ const cropImage = async (Key, width, height, left, top) => {
}),
)
// Convert stream to buffer
const chunks = []
for await (const chunk of Body) {
chunks.push(chunk)
}
const imageBuffer = Buffer.concat(chunks)
const buffer = Buffer.concat(chunks)
let processedImage
if (!checkResult) {
processedImage = await sharp(imageBuffer).toBuffer()
} else {
processedImage = await sharp(imageBuffer)
.extract({
width: parseInt(width),
height: parseInt(height),
left: parseInt(left),
top: parseInt(top),
})
.png()
.toBuffer()
}
return processedImage
const image = await Jimp.read(buffer)
image.autocrop({ tolerance: 0.0002, leaveBorder: 10 })
return await image.getBuffer('image/png')
// Convert stream to buffer
// const chunks = []
// for await (const chunk of Body) {
// chunks.push(chunk)
// }
// const imageBuffer = Buffer.concat(chunks)
// const image = await Jimp.read(Body)
// 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) {
console.error('Error processing image:', 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 setCorrentObjectNo = useSetRecoilState(correntObjectNoState)
const searchParams = useSearchParams()
const path = usePathname()
const objectNo = searchParams.get('objectNo')
const pid = searchParams.get('pid')
useEffect(() => {
setFloorPlanState((prev) => {
return {
...prev,
objectNo,
pid,
}
})
}, [path])
// useEffect(() => {
// console.log('🚀 ~ useEffect ~ objectNo:')
// if (pathname === '/floor-plan') {

View File

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

View File

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

View File

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

View File

@ -73,7 +73,7 @@ export default function QnaDetailModal({ qnaNo, setOpen, qnaType }) {
<dt>{getMessage('qna.detail.sub.fileList')}</dt>
{boardDetail.listFile.map((boardFile) => (
<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}
</button>
</dd>

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@ import { useContext, useEffect, useState } from 'react'
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'
@ -50,6 +50,7 @@ import JA from '@/locales/ja.json'
import { QcastContext } from '@/app/QcastProvider'
import { useRoofFn } from '@/hooks/common/useRoofFn'
import { usePolygon } from '@/hooks/usePolygon'
import { useTrestle } from '@/hooks/module/useTrestle'
export default function CanvasMenu(props) {
const { selectedMenu, setSelectedMenu } = props
const pathname = usePathname()
@ -67,6 +68,7 @@ export default function CanvasMenu(props) {
const globalLocale = useRecoilValue(globalLocaleStore)
const canvas = useRecoilValue(canvasState)
const { handleZoomClear, handleZoom } = useCanvasEvent()
const { setAllModuleSurfaceIsComplete, isAllComplete } = useTrestle()
const { handleMenu } = useMenu()
// const urlParams = useSearchParams()
const { handleEstimateSubmit, fetchSetting, estimateContextState, setEstimateContextState } = useEstimateController()
@ -96,7 +98,7 @@ export default function CanvasMenu(props) {
const [lockButtonStyle, setLockButtonStyle] = useState('') //
const setFloorPlanObjectNo = useSetRecoilState(floorPlanObjectState) //
const resetCommonUtils = useResetRecoilState(commonUtilsState)
//
const { objectNo, pid } = floorPlanState
@ -164,6 +166,7 @@ export default function CanvasMenu(props) {
}
const onClickNav = async (menu) => {
resetCommonUtils()
switch (menu.type) {
case 'drawing':
swalFire({
@ -194,6 +197,7 @@ export default function CanvasMenu(props) {
confirmFn: () => {
//
setAllModuleSurfaceIsComplete(false)
const moduleSurfacesArray = canvas
.getObjects()
.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)
break
case 'estimate':
if (!isAllComplete()) {
swalFire({ text: getMessage('estimate.menu.move.valid1') })
return
}
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) {
const estimateDetail = res.data
if (estimateDetail.estimateDate !== null) {
@ -242,7 +250,7 @@ export default function CanvasMenu(props) {
setCurrentMenu(menu.title)
setFloorPlanObjectNo({ floorPlanObjectNo: objectNo })
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') {
setIsGlobalLoading(false)
}
@ -255,13 +263,13 @@ export default function CanvasMenu(props) {
break
case 'simulation':
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) {
const estimateDetail = res.data
if (estimateDetail.estimateDate !== null && estimateDetail.docNo) {
setSelectedMenu(menu.type)
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') {
setIsGlobalLoading(false)
}

View File

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

View File

@ -12,10 +12,12 @@ import {
import { useRecoilState, useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil'
import { moduleSelectionDataState, selectedModuleState } from '@/store/selectedModuleOptions'
import { isObjectNotEmpty } from '@/util/common-utils'
import Image from 'next/image'
const Placement = forwardRef((props, refs) => {
const { getMessage } = useMessage()
const [useTab, setUseTab] = useState(true)
const [guideType, setGuideType] = useState('batch')
const [isChidoriNotAble, setIsChidoriNotAble] = useState(false)
@ -317,60 +319,91 @@ const Placement = forwardRef((props, refs) => {
</div>
</div>
</div>
<div className="hide-check-guide">
{getMessage('modal.module.basic.setting.module.placement.max.size.check')}
<button className={`arr ${!useTab ? 'act' : ''}`} onClick={() => setUseTab(!useTab)}></button>
</div>
<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 className="hide-tab-wrap">
<div className="hide-check-guide">
{getMessage('modal.module.basic.setting.module.placement.info')}
<button className={`arr ${useTab ? 'act' : ''}`} onClick={() => setUseTab(!useTab)}></button>
</div>
<div className={`hide-tab-contents ${!useTab ? 'hide' : ''}`}>
<div className="roof-content-tab-wrap">
<button className={`btn-frame block modal mr5 ${guideType === 'batch' ? 'act' : ''} `} onClick={() => setGuideType('batch')}>
{getMessage('modal.module.basic.setting.module.placement.info.batch')}
</button>
<button className={`btn-frame block modal mr5 ${guideType === 'module' ? 'act' : ''}`} onClick={() => setGuideType('module')}>
{getMessage('modal.module.basic.setting.module.placement.info.module')}
</button>
</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>
</>

View File

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

View File

@ -13,10 +13,10 @@ import { useRecoilState } from 'recoil'
import { makersState, modelsState, modelState, pcsCheckState, selectedMakerState, selectedModelsState, seriesState } from '@/store/circuitTrestleAtom'
import { POLYGON_TYPE } from '@/common/common'
import { useSwal } from '@/hooks/useSwal'
import { canvasState } from '@/store/canvasAtom'
import { canvasState, canvasZoomState } from '@/store/canvasAtom'
import { useTrestle } from '@/hooks/module/useTrestle'
import { selectedModuleState } from '@/store/selectedModuleOptions'
import { moduleSelectionDataState, selectedModuleState } from '@/store/selectedModuleOptions'
import { v4 as uuidv4 } from 'uuid'
import { useEstimate } from '@/hooks/useEstimate'
@ -25,6 +25,8 @@ import { useCanvasPopupStatusController } from '@/hooks/common/useCanvasPopupSta
import { useImgLoader } from '@/hooks/floorPlan/useImgLoader'
import { usePlan } from '@/hooks/usePlan'
import { QcastContext } from '@/app/QcastProvider'
import { fabric } from 'fabric'
import { fontSelector } from '@/store/fontAtom'
const ALLOCATION_TYPE = {
AUTO: 'auto',
@ -37,13 +39,16 @@ export default function CircuitTrestleSetting({ id }) {
const { swalFire } = useSwal()
const { saveEstimate } = useEstimate()
const canvas = useRecoilValue(canvasState)
const [canvasZoom, setCanvasZoom] = useRecoilState(canvasZoomState)
const [tabNum, setTabNum] = useState(1)
const [allocationType, setAllocationType] = useState(ALLOCATION_TYPE.AUTO)
const [circuitAllocationType, setCircuitAllocationType] = useState(1)
const { managementState, setManagementState } = useContext(GlobalDataContext)
const selectedModules = useRecoilValue(selectedModuleState)
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({})
@ -55,7 +60,7 @@ export default function CircuitTrestleSetting({ id }) {
const [seletedSubOption, setSeletedSubOption] = useState(null)
const { setModuleStatisticsData } = useCircuitTrestle()
const { handleCanvasToPng } = useImgLoader()
const moduleSelectionData = useRecoilValue(moduleSelectionDataState)
const passivityCircuitAllocationRef = useRef()
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
@ -154,6 +182,7 @@ export default function CircuitTrestleSetting({ id }) {
getPcsVoltageChk(pcsVoltageChkParams).then((res) => {
if (res.resultCode === 'S') {
setTabNum(2)
setAllModuleSurfaceIsComplete(false)
} else {
swalFire({
title: res.resultMsg,
@ -187,6 +216,7 @@ export default function CircuitTrestleSetting({ id }) {
}).then((res) => {
if (res?.result.resultCode === 'S' && res?.data) {
setTabNum(2)
setAllModuleSurfaceIsComplete(false)
} else {
swalFire({ text: getMessage('common.message.send.error') })
}
@ -286,6 +316,8 @@ export default function CircuitTrestleSetting({ id }) {
setSelectedModels(pcsItemList)
getPcsVoltageChk(pcsVoltageChkParams).then((res) => {
setAllocationType(ALLOCATION_TYPE.PASSIVITY)
setAllModuleSurfaceIsComplete(false)
clearTrestle()
})
} else {
swalFire({
@ -308,8 +340,15 @@ export default function CircuitTrestleSetting({ id }) {
const target = pcsCheck.max ? moduleMaxQty : moduleStdQty
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({
title: getMessage('modal.circuit.trestle.setting.circuit.allocation.passivity.all.power.conditional.validation.error01'),
type: 'alert',
@ -318,6 +357,7 @@ export default function CircuitTrestleSetting({ id }) {
}
setAllocationType(ALLOCATION_TYPE.PASSIVITY)
clearTrestle()
}
}
@ -347,13 +387,11 @@ export default function CircuitTrestleSetting({ id }) {
.map((obj) => {
obj.pcses = getStepUpListData()
})
setViewCircuitNumberTexts(false)
beforeCapture()
handleCanvasToPng(1)
afterCapture()
// result=null
setViewCircuitNumberTexts(true)
//
//
@ -368,7 +406,9 @@ export default function CircuitTrestleSetting({ id }) {
const result = await getEstimateData()
if (result) {
beforeCapture()
handleCanvasToPng(2)
afterCapture()
//
await saveEstimate(result)
} else {
@ -378,6 +418,16 @@ export default function CircuitTrestleSetting({ id }) {
// 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 = () => {
// setAllocationType(ALLOCATION_TYPE.AUTO)
@ -654,6 +704,7 @@ export default function CircuitTrestleSetting({ id }) {
return
} else {
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', {
...basicSetting,
selectedRoofMaterial: {
roofInfo,
...newAddedRoofs[0],
},
})
@ -240,7 +240,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla
* 선택된 지붕재 정보
*/
selectedRoofMaterial: {
roofInfo,
...newAddedRoofs[0],
},
})

View File

@ -952,9 +952,18 @@ export default function StuffDetail() {
//
const setZipInfo = (info) => {
setPrefValue(info.prefId)
form.setValue('prefId', info.prefId)
form.setValue('prefName', info.address1)
prefCodeList.map((row) => {
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('zipNo', info.zipNo)
}

View File

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

View File

@ -114,7 +114,7 @@ export default function Simulator() {
setHatsudenryouPeakcutAllSnow([])
if (objectNo && pid && selectedPlan) {
fetchObjectDetail(objectNo, selectedPlan.planNo)
fetchObjectDetail(objectNo, selectedPlan?.planNo??pid)
fetchSimulatorNotice()
setPwrGnrSimType('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 { useRecoilState, useRecoilValue } from 'recoil'
import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil'
import { wordDisplaySelector } from '@/store/settingAtom'
import { useEvent } from '@/hooks/useEvent'
import { checkLineOrientation, getDistance } from '@/util/canvas-util'
import { commonUtilsState, dimensionLineSettingsState } from '@/store/commonUtilsAtom'
import { fontSelector } from '@/store/fontAtom'
import { canvasState } from '@/store/canvasAtom'
import { canvasState, currentMenuState } from '@/store/canvasAtom'
import { v4 as uuidv4 } from 'uuid'
import { usePopup } from '@/hooks/usePopup'
import Distance from '@/components/floor-plan/modal/distance/Distance'
@ -16,57 +16,91 @@ import { BATCH_TYPE } from '@/common/common'
export function useCommonUtils() {
const canvas = useRecoilValue(canvasState)
const wordDisplay = useRecoilValue(wordDisplaySelector)
const { addCanvasMouseEventListener, addDocumentEventListener, initEvent } = useEvent()
const { addCanvasMouseEventListener, addDocumentEventListener, initEvent, removeMouseEvent } = useEvent()
const dimensionSettings = useRecoilValue(dimensionLineSettingsState)
const dimensionLineTextFont = useRecoilValue(fontSelector('dimensionLineText'))
const lengthTextFont = useRecoilValue(fontSelector('lengthText'))
const commonTextFont = useRecoilValue(fontSelector('commonText'))
const [commonUtils, setCommonUtilsState] = useRecoilState(commonUtilsState)
const { addPopup } = usePopup()
const { addPopup, closeAll, targetClose } = usePopup()
const { drawDirectionArrow, addLengthText } = usePolygon()
const { applyDormers } = useObjectBatch({})
useEffect(() => {
if (commonUtils.text) {
commonTextMode()
} else if (commonUtils.dimension) {
commonTextMode()
if (commonUtils.dimension) {
commonDimensionMode()
} else if (commonUtils.distance) {
return
}
if (commonUtils.distance) {
commonDistanceMode()
return
}
}, [commonUtils, dimensionSettings, commonTextFont, dimensionLineTextFont])
const commonTextMode = () => {
let textbox
if (commonUtils.text) {
commonTextKeyEvent()
addCanvasMouseEventListener('mouse:down', (event) => {
const pointer = canvas?.getPointer(event.e)
targetClose('other')
setTimeout(() => {
commonTextKeyEvent()
addCanvasMouseEventListener('mouse:down', (event) => {
const pointer = canvas?.getPointer(event.e)
textbox = new fabric.Textbox('', {
left: pointer.x,
top: pointer.y,
width: 200,
editable: true,
name: 'commonText',
visible: wordDisplay,
fill: commonTextFont.fontColor.value,
fontFamily: commonTextFont.fontFamily.value,
fontSize: commonTextFont.fontSize.value,
fontStyle: commonTextFont.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal',
fontWeight: commonTextFont.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal',
selectable: true,
lockMovementX: true,
lockMovementY: true,
originX: 'center',
originY: 'center',
textbox = new fabric.Textbox('', {
left: pointer.x,
top: pointer.y,
width: 200,
editable: true,
name: 'commonText',
visible: wordDisplay,
fill: commonTextFont.fontColor.value,
fontFamily: commonTextFont.fontFamily.value,
fontSize: commonTextFont.fontSize.value,
fontStyle: commonTextFont.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal',
fontWeight: commonTextFont.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal',
selectable: true,
lockMovementX: true,
lockMovementY: true,
originX: '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)
canvas.setActiveObject(textbox)
textbox.enterEditing()
textbox.selectAll()
})
initEvent()
}
}

View File

@ -212,9 +212,14 @@ export function useMasterController() {
}
const getQuotationItem = async (params) => {
return await post({ url: '/api/v1/master/getQuotationItem', data: params }).then((res) => {
return res
})
return await post({ url: '/api/v1/master/getQuotationItem', data: params })
.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 { settingModalFirstOptionsState } from '@/store/settingAtom'
import { popSpinnerState } from '@/store/popupAtom'
import Config from '@/config/config.export'
/**
* 배경 이미지 관리
@ -97,7 +98,7 @@ export function useRefFiles() {
setPopSpinnerStore(true)
console.log('🚀 ~ handleFileDelete ~ handleFileDelete:', refImage)
console.log('🚀 ~ handleFileDelete ~ currentCanvasPlan.bgImageName:', currentCanvasPlan.bgImageName)
await del({ url: `${process.env.NEXT_PUBLIC_API_HOST_URL}/api/image/upload?fileName=${currentCanvasPlan.bgImageName}` })
await del({ url: `${Config().baseUrl}/api/image/upload?fileName=${currentCanvasPlan.bgImageName}` })
setCurrentBgImage(null)
await deleteBackGroundImage({
objectId: currentCanvasPlan.id,
@ -118,7 +119,7 @@ export function useRefFiles() {
confirmFn: async () => {
console.log('🚀 ~ handleAddressDelete ~ handleAddressDelete:', refImage)
console.log('🚀 ~ handleAddressDelete ~ currentCanvasPlan.bgImageName:', currentCanvasPlan.bgImageName)
await del({ url: `${process.env.NEXT_PUBLIC_API_HOST_URL}/api/image/map?fileName=${currentCanvasPlan.bgImageName}` })
await del({ url: `${Config().baseUrl}/api/image/map?fileName=${currentCanvasPlan.bgImageName}` })
setMapPositionAddress('')
setCurrentBgImage(null)
await deleteBackGroundImage({
@ -149,7 +150,7 @@ export function useRefFiles() {
}))
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)
setCurrentBgImage(`${process.env.NEXT_PUBLIC_AWS_S3_BASE_URL}/${res.fileName}`)
@ -208,7 +209,7 @@ export function useRefFiles() {
formData.append('file', file)
const res = await post({
url: `${process.env.NEXT_PUBLIC_API_HOST_URL}/api/image/upload`,
url: `${Config().baseUrl}/api/image/upload`,
data: formData,
})
console.log('🚀 ~ handleUploadImageRefFile ~ res:', res)
@ -238,14 +239,40 @@ export function useRefFiles() {
const res = await post({ url: converterUrl, data: formData })
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({
url: `${process.env.NEXT_PUBLIC_API_HOST_URL}/api/image/cad`,
data: res,
url: `${Config().baseUrl}/api/image/cad`,
data: newFormData,
})
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)
const params = {

View File

@ -13,6 +13,8 @@ import { useSwal } from '@/hooks/useSwal'
// Constants
const ESTIMATE_API_ENDPOINT = '/api/estimate' // API 엔드포인트 정의
import Config from '@/config/config.export'
// Helper functions
const updateItemInList = (itemList, dispOrder, updates) => {
return itemList.map((item) => (item.dispOrder === dispOrder ? { ...item, ...updates } : item))
@ -464,11 +466,13 @@ export const useEstimateController = (planNo, flag) => {
setIsGlobalLoading(true)
await promisePost({ url: '/api/estimate/save-estimate-copy', data: params })
.then((res) => {
.then(async (res) => {
setIsGlobalLoading(false)
if (res.status === 201) {
if (isObjectNotEmpty(res.data)) {
let newObjectNo = res.data.objectNo
const copyImage = await handleEstimateImageCopy(params.objectNo, params.planNo, newObjectNo, '1')
swalFire({
text: getMessage('estimate.detail.estimateCopyPopup.copy.alertMessage'),
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자)
*/
@ -509,5 +534,7 @@ export const useEstimateController = (planNo, flag) => {
fetchSetting,
handleEstimateFileDownload,
handleEstimateCopy,
handleDeleteEstimate,
handleEstimateImageCopy,
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -199,6 +199,9 @@ export function useCanvasSetting(executeEffect = true) {
if (!executeEffect) {
return
}
if (roofMaterials.length === 0) {
return
}
const selectedRoofMaterial = roofMaterials[0]
if (addedRoofs.length === 0) {
@ -495,11 +498,26 @@ export function useCanvasSetting(executeEffect = true) {
roofSeq: 0,
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,
roofHeight: params.roofsData.roofHeight === null || params.roofsData.roofHeight === undefined ? 0 : params.roofsData.roofHeight,
roofWidth:
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:
params.roofsData.roofHajebichi === null || params.roofsData.roofHajebichi === undefined ? 0 : params.roofsData.roofHajebichi,
roofGap: params.roofsData.roofGap === null || params.roofsData.roofGap === undefined ? 'HEI_455' : params.roofsData.roofGap,
params.selectedRoofMaterial.hajebichi === null || params.selectedRoofMaterial.hajebichi === undefined
? 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,
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,

View File

@ -212,7 +212,6 @@ export function useRoofAllocationSetting(id) {
}
await post({ url: `/api/canvas-management/roof-allocation-settings`, data: patternData }).then((res) => {
swalFire({ text: getMessage(res.returnMessage) })
setIsGlobalLoading(false)
})
@ -315,7 +314,8 @@ export function useRoofAllocationSetting(id) {
setSurfaceShapePattern(currentObject, roofDisplay.column, false, selectedRoofMaterial, true)
drawDirectionArrow(currentObject)
modifyModuleSelectionData()
closeAll()
// closeAll()
closePopup(id)
basicSettingSave()
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 { canvasState, canvasZoomState, currentMenuState, textModeState } from '@/store/canvasAtom'
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 { useDotLineGrid } from '@/hooks/useDotLineGrid'
import { useTempGrid } from '@/hooks/useTempGrid'
import { gridColorState } from '@/store/gridAtom'
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() {
const canvas = useRecoilValue(canvasState)
@ -18,6 +26,7 @@ export function useEvent() {
const setCanvasZoom = useSetRecoilState(canvasZoomState)
const gridColor = useRecoilValue(gridColorState)
const isGridDisplay = useRecoilValue(gridDisplaySelector)
const zoom = useRecoilValue(canvasZoomState)
const { adsorptionPointAddMode, adsorptionPointMode, adsorptionRange, getAdsorptionPoints, adsorptionPointAddModeStateEvent } = useAdsorptionPoint()
const { dotLineGridSetting, interval, getClosestLineGrid } = useDotLineGrid()
@ -52,6 +61,13 @@ export function useEvent() {
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 = () => {
//default Event 추가
addCanvasMouseEventListener('mouse:move', defaultMouseMoveEvent)
@ -146,13 +162,14 @@ export function useEvent() {
...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 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')
if (!horizonLines || !verticalLines) {
drawMouseLine(pointer)
return
}
@ -176,6 +193,7 @@ export function useEvent() {
}
if (!closestVerticalLine || !closestHorizontalLine) {
drawMouseLine(pointer)
return
}
@ -260,9 +278,18 @@ export function useEvent() {
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',
strokeWidth: 1,
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',
strokeWidth: 1,
selectable: false,
@ -279,9 +306,6 @@ export function useEvent() {
// 선들을 캔버스에 추가합니다.
canvas?.add(horizontalLine, verticalLine)
// 캔버스를 다시 그립니다.
canvas?.renderAll()
}
const removeMouseLine = () => {
@ -298,7 +322,12 @@ export function useEvent() {
e.preventDefault()
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], {
stroke: gridColor,
@ -372,7 +401,7 @@ export function useEvent() {
const removeMouseEvent = (type) => {
mouseEventListeners.current = mouseEventListeners.current.filter((event) => {
if (event.eventType === type) {
canvas.off(type, event.handler)
canvas?.off(type, event.handler)
return false
}
return true

View File

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

View File

@ -3,9 +3,17 @@
import { useContext, useEffect, useState } from 'react'
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 { useMessage } from '@/hooks/useMessage'
import { useSwal } from '@/hooks/useSwal'
@ -22,6 +30,7 @@ import { useCanvasPopupStatusController } from './common/useCanvasPopupStatusCon
import { useCanvasMenu } from './common/useCanvasMenu'
import { QcastContext } from '@/app/QcastProvider'
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 [selectedPlan, setSelectedPlan] = useState(null)
const setCurrentMenu = useSetRecoilState(currentMenuState)
const [canvas, setCanvas] = useRecoilState(canvasState)
const [currentCanvasPlan, setCurrentCanvasPlan] = useRecoilState(currentCanvasPlanState)
@ -46,7 +55,7 @@ export function usePlan(params = {}) {
const { getMessage } = useMessage()
const { get, post, promisePost, promisePut, promiseDel, promiseGet } = useAxios()
const { setEstimateContextState } = useEstimateController()
const { setEstimateContextState, handleDeleteEstimate, handleEstimateImageCopy } = useEstimateController()
const resetOuterLinePoints = useResetRecoilState(outerLinePointsState)
const resetPlacementShapeDrawingPoints = useResetRecoilState(placementShapeDrawingPointsState)
@ -70,6 +79,7 @@ export function usePlan(params = {}) {
const resetCurrentObject = useResetRecoilState(currentObjectState)
//선택된 모듈 배치면 초기화
const resetModuleSetupSurface = useResetRecoilState(moduleSetupSurfaceState)
const { isAllComplete } = useTrestle()
/**
* 마우스 포인터의 가이드라인 제거
@ -164,7 +174,11 @@ export function usePlan(params = {}) {
*/
const saveCanvas = async (saveAlert = true) => {
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)
if (copyData.module) setSelectedModules(copyData.module)
setSelectedMenu(currentSelectedMenu)
//이미지 복사
handleEstimateImageCopy(planData.objectNo, planData.planNo, planData.objectNo, newPlan.planNo)
} else {
setSelectedMenu('placement')
}
@ -310,20 +327,25 @@ export function usePlan(params = {}) {
* @param {boolean} saveAlert - 저장 완료 알림 표시 여부
*/
const putCanvasStatus = async (canvasStatus, saveAlert = true) => {
let rtn = false
const planData = {
id: currentCanvasPlan.id,
bgImageName: currentCanvasPlan?.bgImageName ?? null,
mapPositionAddress: currentCanvasPlan?.mapPositionAddress ?? null,
canvasStatus: canvasToDbFormat(canvasStatus),
}
await promisePut({ url: '/api/canvas-management/canvas-statuses', data: planData })
.then((res) => {
setPlans((plans) => plans.map((plan) => (plan.id === currentCanvasPlan.id ? { ...plan, canvasStatus: canvasStatus } : plan)))
if (saveAlert) swalFire({ text: getMessage('plan.message.save') })
rtn = true
})
.catch((error) => {
swalFire({ text: error.message, icon: 'error' })
setIsGlobalLoading(false)
})
return rtn
}
/**
@ -577,8 +599,10 @@ export function usePlan(params = {}) {
* plan canvasStatus 초기화
*/
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 })))
setCurrentMenu(null)
setSelectedMenu(null)
}
/**
@ -592,8 +616,10 @@ export function usePlan(params = {}) {
if (pathname === '/floor-plan/estimate/5' || pathname === '/floor-plan/simulator/6') {
await getCanvasByObjectNo(objectNo, planNo).then((res) => {
if (res.length > 0) {
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 })))
// 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 })))
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 { QLine } from '@/components/fabric/QLine'
import { LINE_TYPE, POLYGON_TYPE } from '@/common/common'
import { useLine } from '@/hooks/useLine'
export const usePolygon = () => {
const canvas = useRecoilValue(canvasState)
@ -24,6 +25,8 @@ export const usePolygon = () => {
const currentAngleType = useRecoilValue(currentAngleTypeSelector)
const pitchText = useRecoilValue(pitchTextSelector)
const { getLengthByLine } = useLine()
const addPolygon = (points, options, isAddCanvas = true) => {
const polygon = new QPolygon(points, {
...options,
@ -1093,25 +1096,37 @@ export const usePolygon = () => {
})
if (startFlag && endFlag) {
if (!representLines.includes(line)) {
if (!representLines.includes(line) && line.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
representLines.push(line)
}
}
})
// blue로 생성된 것들은 대표라인이 될 수 없음.
// representLines = representLines.filter((line) => line.stroke !== 'blue')
// representLines중 가장 긴 line을 찾는다.
representLines.forEach((line) => {
if (!representLine) {
representLine = line
} else {
if (representLine.length < line.length) {
if (getLengthByLine(representLine) < getLengthByLine(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
switch (direction) {
@ -1178,7 +1193,7 @@ export const usePolygon = () => {
})
canvas.add(roof)
// addLengthText(roof)
addLengthText(roof)
canvas.remove(polygon)
canvas.renderAll()
})

View File

@ -1,5 +1,7 @@
import { useRecoilState } from 'recoil'
import { useRecoilState, useResetRecoilState } from 'recoil'
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 {
popup,
addPopup,
closePopup,
closePopups,
closeAll,
targetClose,
}
}

View File

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

View File

@ -612,11 +612,12 @@
"qna.reg.header.contents": "お問い合わせ内容",
"qna.reg.header.fileList": "ファイル添付",
"qna.reg.header.save": "保存",
"qna.reg.alert.require.qstMail": "無効なメール形式です。",
"qna.reg.alert.require.regUserNm": "名前を入力してください。",
"qna.reg.alert.select.type": "お問い合わせ区分を選択してください。",
"qna.reg.alert.require.title": "タイトルを入力してください。",
"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.saveFail": "保存に失敗しました。",
"qna.list.header.regNm": "登録者",
@ -665,9 +666,9 @@
"join.sub1.title": "販売代理店情報",
"join.sub1.comment": "※登録される販売店の会社名を入力してください。 2次店は「○○販売株式会社2次店××設備株式会社」でご記入ください。",
"join.sub1.storeQcastNm": "販売代理店名",
"join.sub1.storeQcastNm_placeholder": "株式会社エネルギーギアソリューションアンサービス(2次点山口周期販売有限会社",
"join.sub1.storeQcastNm_placeholder": "ハンファジャパン株式会社",
"join.sub1.storeQcastNmKana": "販売代理店名フリガナ",
"join.sub1.storeQcastNmKana_placeholder": "株式会社エネルギーギアソリューション",
"join.sub1.storeQcastNmKana_placeholder": "ハンファジャパンカブシキカイシャ",
"join.sub1.postCd": "郵便番号",
"join.sub1.postCd_placeholder": "数字7桁",
"join.sub1.addr": "住所",
@ -680,7 +681,7 @@
"join.sub2.title": "担当者情報",
"join.sub2.userNm": "担当者名",
"join.sub2.userNmKana": "担当者名ふりがな",
"join.sub2.userId": "申請ID",
"join.sub2.userId": "ユーザーID",
"join.sub2.email": "メールアドレス",
"join.sub2.telNo": "電話番号",
"join.sub2.telNo_placeholder": "00 0000 0000",
@ -688,12 +689,12 @@
"join.sub2.fax_placeholder": "00 0000 0000",
"join.sub2.category": "部署名",
"join.btn.login_page": "ログイン画面に移動",
"join.btn.approval_request": "ID承認要求",
"join.btn.approval_request": "ID申請",
"join.complete.title": "HANASYS設計ログインID発行申請完了",
"join.complete.contents": "※申請したIDが承認されると、担当者情報に入力したEメールアドレスにログイン関連案内メールが送信されます。",
"join.complete.email_comment": "担当者のメールアドレス",
"join.validation.check1": "{0}形式を確認してください。",
"join.complete.save.confirm": "ハンファジャパンの担当者にID承認が要求された場合、これ以上情報を修正することはできません。申請しますか?",
"join.complete.save.confirm": "ID申請を完了後は申請情報の修正が出来ません。申請しますか?",
"stuff.gridHeader.lastEditDatetime": "更新日時",
"stuff.gridHeader.objectNo": "物件番号",
"stuff.gridHeader.planTotCnt": "プラン数",
@ -955,7 +956,7 @@
"estimate.detail.dragFileGuide": "(※北面設置の場合、ファイル添付が必須です。)",
"estimate.detail.header.fileList1": "ファイル添付",
"estimate.detail.fileList.btn": "ファイル選択",
"estimate.detail.fileList.extCheck": "画像ファイルのみ添付可能です。",
"estimate.detail.fileList.extCheck": "画像、PDF、Excel ファイルのみ添付できます。",
"estimate.detail.header.fileList2": "添付ファイル一覧",
"estimate.detail.fileList2.btn.return": "復元",
"estimate.detail.header.specialEstimate": "見積特記事項",
@ -1077,6 +1078,7 @@
"module.not.found": "インストールモジュールを選択してください。",
"module.circuit.minimun.error": "回路番号は1以上の数値を入力してください。",
"module.already.exist.error": "回路番号が同じで異なるパワーコンディショナのモジュールがあります。 別の回路番号を設定してください。",
"module.circuit.fix.not.same.roof.error": "異なる屋根面のモジュールが選択されています。 モジュールの選択をや直してください。",
"construction.length.difference": "屋根面工法をすべて選択してください。",
"menu.validation.canvas.roof": "パネルを配置するには、屋根面を入力する必要があります。",
"batch.object.outside.roof": "オブジェクトは屋根に設置する必要があります。",
@ -1103,6 +1105,11 @@
"module.layout.setup.max.count": "モジュールの単体での最大段数は{0}、最大列数は{1}です。 (JA)",
"module.layout.setup.max.count.multiple": "モジュール{0}の単体での最大段数は{1}、最大列数は{2}です。 (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.row": "単体で\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.fileList": "파일첨부",
"qna.reg.header.save": "저장",
"qna.reg.alert.require.qstMail": "올바르지 않은 이메일 형식입니다.",
"qna.reg.alert.require.regUserNm": "이름을 입력하세요.",
"qna.reg.alert.select.type": "문의구분을 선택하세요.",
"qna.reg.alert.require.title": "제목을 입력하세요.",
@ -665,9 +666,9 @@
"join.sub1.title": "판매대리점 정보",
"join.sub1.comment": "※ 등록되는 리셀러의 회사 이름을 입력하십시오. (2차점은 「○○판매주식회사(2차점××설비주식회사)」로 기입해 주세요.)",
"join.sub1.storeQcastNm": "판매대리점명",
"join.sub1.storeQcastNm_placeholder": "주식회사 에너지 기어 솔루션 앤 서비스 (2차점: 야마구치 주기 판매 유한회사)",
"join.sub1.storeQcastNm_placeholder": "한화재팬 주식회사",
"join.sub1.storeQcastNmKana": "판매대리점명 후리가나",
"join.sub1.storeQcastNmKana_placeholder": "주식회사 에너지 기어 솔루션",
"join.sub1.storeQcastNmKana_placeholder": "한화재팬 카부시키 카이샤",
"join.sub1.postCd": "우편번호",
"join.sub1.postCd_placeholder": "숫자 7자리",
"join.sub1.addr": "주소",
@ -680,7 +681,7 @@
"join.sub2.title": "담당자 정보",
"join.sub2.userNm": "담당자명",
"join.sub2.userNmKana": "담당자명 후리가나",
"join.sub2.userId": "신청 ID",
"join.sub2.userId": "사용자 ID",
"join.sub2.email": "이메일 주소",
"join.sub2.telNo": "전화번호",
"join.sub2.telNo_placeholder": "00 0000 0000",
@ -1104,6 +1105,11 @@
"module.layout.setup.max.count": "모듈의 최대 단수는 {0}, 최대 열수는 {1} 입니다.",
"module.layout.setup.max.count.multiple": "모듈 {0}번의 최대 단수는 {1}, 최대 열수는 {2} 입니다.",
"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.row": "최대 단수",
"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: 1, name: 'plan.menu.roof.cover.roof.shape.setting', menu: MENU.ROOF_COVERING.ROOF_SHAPE_SETTINGS },
{
id: 2,
name: 'plan.menu.roof.cover.roof.shape.passivity.setting',
menu: MENU.ROOF_COVERING.ROOF_SHAPE_PASSIVITY_SETTINGS,
},
// {
// id: 2,
// name: 'plan.menu.roof.cover.roof.shape.passivity.setting',
// 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: 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 },

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;
.community_detail-file-wrap{
padding-top: 0;
margin-bottom: 24px;
}
.community_detail-inner{
max-height: 110px;
margin-top: 24px;
margin-top: 0;
margin-bottom: 0;
}
}
@ -461,12 +462,14 @@
border: 1px solid #101010;
.community_detail-inner{
max-height: 110px;
margin-top: 20px;
margin-bottom: 0;
}
.community_detail-file-wrap{
border-top: 1px solid #D4DCE7;
padding: 16px 0 0 0;
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
exec('yarn dev -p 5000', { windowsHide: true })
exec('yarn local:dev -p 5000', { windowsHide: true })