Merge remote-tracking branch 'origin/dev-deploy' into dev-deploy

This commit is contained in:
yoosangwook 2025-06-05 11:08:23 +09:00
commit 38e3ab6715
24 changed files with 326 additions and 102 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

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/${objectNo}_${planNo}_1.png`,
}),
)
//견적서2 번 이미지
const isExistImage2 = await s3.send(
new GetObjectCommand({
Bucket,
Key: `Drawing/${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/${objectNo}_${planNo}_1.png`),
Key: `Drawing/${newObjectNo}_${newPlanNo}_1.png`,
})
const response = await s3.send(copyCommand)
const copyCommand2 = new CopyObjectCommand({
Bucket,
CopySource: encodeURI(`${Bucket}/Drawing/${objectNo}_${planNo}_2.png`),
Key: `Drawing/${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

@ -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

@ -241,7 +241,7 @@ export default function CanvasMenu(props) {
return 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) {
@ -249,7 +249,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)
} }
@ -262,13 +262,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

@ -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,7 +13,7 @@ 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 { selectedModuleState } from '@/store/selectedModuleOptions'
@ -37,7 +37,7 @@ 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)
@ -102,6 +102,13 @@ export default function CircuitTrestleSetting({ id }) {
} }
}, []) }, [])
const handleZoomClear = () => {
setCanvasZoom(100)
canvas.set({ zoom: 1 })
canvas.viewportTransform = [1, 0, 0, 1, 0, 0]
canvas.renderAll()
}
// //
// PCS // PCS
@ -343,6 +350,7 @@ export default function CircuitTrestleSetting({ id }) {
// () // ()
const onApply = async () => { const onApply = async () => {
handleZoomClear()
setAllModuleSurfaceIsComplete(false) setAllModuleSurfaceIsComplete(false)
setIsGlobalLoading(true) setIsGlobalLoading(true)

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

@ -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

@ -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

@ -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

@ -1627,6 +1627,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 +1643,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 +1659,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 +1668,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

View File

@ -498,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)
}) })

View File

@ -1,4 +1,4 @@
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'
@ -15,7 +15,8 @@ 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)
@ -25,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()
@ -59,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)
@ -280,7 +289,7 @@ export function useEvent() {
} }
const drawMouseLine = (pointer) => { const drawMouseLine = (pointer) => {
const horizontalLine = new fabric.Line([-4 * canvas.width, pointer.y, 4 * canvas.width, pointer.y], { 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,
@ -288,7 +297,7 @@ export function useEvent() {
}) })
// 세로선 // 세로선
const verticalLine = new fabric.Line([pointer.x, -4 * canvas.height, pointer.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,

View File

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

View File

@ -30,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'
/** /**
* 플랜 처리 * 플랜 처리
@ -54,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)
@ -78,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()
/** /**
* 마우스 포인터의 가이드라인 제거 * 마우스 포인터의 가이드라인 제거
@ -172,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)
}
} }
/** /**
@ -301,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')
} }
@ -318,20 +327,24 @@ 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' })
}) })
return rtn
} }
/** /**
@ -602,8 +615,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

@ -666,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": "住所",
@ -681,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",
@ -689,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": "プラン数",
@ -1105,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

@ -666,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": "주소",
@ -681,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",
@ -1105,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

@ -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;
}
}