Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into feature/skeleton-dev

This commit is contained in:
yscha 2026-01-10 15:10:56 +09:00
commit 5e5fc73115
29 changed files with 732 additions and 7920 deletions

View File

@ -222,6 +222,9 @@ export const SAVE_KEY = [
'skeletonLines', 'skeletonLines',
'skeleton', 'skeleton',
'viewportTransform', 'viewportTransform',
'outerLineFix',
'adjustRoofLines',
'northModuleYn',
] ]
export const OBJECT_PROTOTYPE = [fabric.Line.prototype, fabric.Polygon.prototype, fabric.Triangle.prototype, fabric.Group.prototype] export const OBJECT_PROTOTYPE = [fabric.Line.prototype, fabric.Polygon.prototype, fabric.Triangle.prototype, fabric.Group.prototype]

View File

@ -18,8 +18,9 @@ import Config from '@/config/config.export'
export default function MainPage() { export default function MainPage() {
const [sessionState, setSessionState] = useRecoilState(sessionStore) const [sessionState, setSessionState] = useRecoilState(sessionStore)
const [chagePasswordPopOpen, setChagePasswordPopOpen] = useState(false) const [changePasswordPopOpen, setChangePasswordPopOpen] = useState(false)
//
const [isSessionLoaded, setIsSessionLoaded] = useState(false)
const router = useRouter() const router = useRouter()
const { getMessage } = useMessage() const { getMessage } = useMessage()
@ -52,6 +53,14 @@ export default function MainPage() {
} }
} }
useEffect(() => {
if (isObjectNotEmpty(sessionState)) {
if (sessionState?.pwdInitYn !== 'Y') {
setChangePasswordPopOpen(true)
}
}
}, [sessionState])
// //
const handleOnChangeRadio = (e) => { const handleOnChangeRadio = (e) => {
setSearchRadioType(e.target.value) setSearchRadioType(e.target.value)
@ -77,7 +86,7 @@ export default function MainPage() {
useEffect(() => { useEffect(() => {
if (isObjectNotEmpty(sessionState)) { if (isObjectNotEmpty(sessionState)) {
if (sessionState?.pwdInitYn !== 'Y') { if (sessionState?.pwdInitYn !== 'Y') {
setChagePasswordPopOpen(true) setChangePasswordPopOpen(true)
} }
} }
}, [sessionState]) }, [sessionState])
@ -86,10 +95,25 @@ export default function MainPage() {
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const [modalNoticeNo, setModalNoticeNo] = useState('') const [modalNoticeNo, setModalNoticeNo] = useState('')
useEffect(() => {
if (isObjectNotEmpty(sessionState)) {
if (sessionState?.pwdInitYn !== 'Y') {
setChangePasswordPopOpen(true)
} else {
// pwdInitYn 'Y' (false)
setChangePasswordPopOpen(false)
}
}
}, [sessionState])
//if (!isSessionLoaded) return null
return ( return (
<> <>
{open && <BoardDetailModal noticeNo={modalNoticeNo} setOpen={setOpen} />} {open && <BoardDetailModal noticeNo={modalNoticeNo} setOpen={setOpen} />}
{(!chagePasswordPopOpen && ( {changePasswordPopOpen ? (
<ChangePasswordPop setChangePasswordPopOpen={setChangePasswordPopOpen} />
) : (
<> <>
<div className="background-bord"></div> <div className="background-bord"></div>
<div className="main-contents"> <div className="main-contents">
@ -131,11 +155,8 @@ export default function MainPage() {
<MainContents setFaqOpen={setOpen} setFaqModalNoticeNo={setModalNoticeNo} /> <MainContents setFaqOpen={setOpen} setFaqModalNoticeNo={setModalNoticeNo} />
</div> </div>
</> </>
)) || (
<>
<ChangePasswordPop setChagePasswordPopOpen={setChagePasswordPopOpen} />
</>
)} )}
</> </>
) )
} }

View File

@ -1,4 +1,4 @@
import React, { useState, useRef, useEffect, forwardRef } from 'react' import React, { useState, useRef, useEffect, forwardRef, useImperativeHandle } from 'react'
import { createCalculator } from '@/util/calc-utils' import { createCalculator } from '@/util/calc-utils'
import '@/styles/calc.scss' import '@/styles/calc.scss'
@ -23,8 +23,23 @@ export const CalculatorInput = forwardRef(
}, [ref]) }, [ref])
// Sync displayValue with value prop // Sync displayValue with value prop
// useEffect(() => {
// setDisplayValue(value || '0')
// }, [value])
useEffect(() => { useEffect(() => {
setDisplayValue(value || '0') const newValue = value || '0'
setDisplayValue(newValue)
// value
const calculator = calculatorRef.current
if (calculator) {
//
calculator.currentOperand = newValue.toString()
calculator.previousOperand = ''
calculator.operation = undefined
setHasOperation(false)
}
}, [value]) }, [value])
// //
@ -294,9 +309,11 @@ export const CalculatorInput = forwardRef(
} else { } else {
calculator.currentOperand = filteredValue calculator.currentOperand = filteredValue
setHasOperation(false) setHasOperation(false)
// onChange
onChange(filteredValue)
} }
onChange(filteredValue) //onChange(filteredValue)
} }
} }
@ -323,13 +340,19 @@ export const CalculatorInput = forwardRef(
// Tab // Tab
if (e.key === 'Tab') { if (e.key === 'Tab') {
if (hasOperation) {
handleCompute(true) //
}
setShowKeypad(false) setShowKeypad(false)
return return
} }
// //
if (e.key === 'ArrowLeft' || e.key === 'ArrowRight' || e.key === 'ArrowUp' || e.key === 'ArrowDown') { if (e.key === 'ArrowLeft' || e.key === 'ArrowRight' || e.key === 'ArrowUp' || e.key === 'ArrowDown') {
setShowKeypad(true) if (hasOperation) {
handleCompute(true) //
}
setShowKeypad(false)
return return
} }
@ -343,6 +366,12 @@ export const CalculatorInput = forwardRef(
return return
} }
// --- ---
if (e.key !== 'Process') { // ()
// e.preventDefault() .
}
e.preventDefault() e.preventDefault()
const calculator = calculatorRef.current const calculator = calculatorRef.current
const { allowDecimal } = options const { allowDecimal } = options

View File

@ -96,7 +96,7 @@ export default function QSelectBox({
title={tagTitle} title={tagTitle}
> >
<p>{selected}</p> <p>{selected}</p>
<ul className="select-item-wrap"> <ul className="select-item-wrap" style={{ maxHeight: '200px' }}>
{options?.length > 0 && {options?.length > 0 &&
options?.map((option, index) => ( options?.map((option, index) => (
<li key={option.id + '_' + index} className="select-item" onClick={() => handleClickSelectOption(option)}> <li key={option.id + '_' + index} className="select-item" onClick={() => handleClickSelectOption(option)}>

View File

@ -1465,19 +1465,19 @@ export default function Estimate({}) {
: 'none', : 'none',
}} }}
> >
<input {/*<input*/}
type="radio" {/* type="radio"*/}
name="estimateType" {/* name="estimateType"*/}
id="YJSS" {/* id="YJSS"*/}
value={'YJSS'} {/* value={'YJSS'}*/}
checked={estimateContextState?.estimateType === 'YJSS' ? true : false} {/* checked={estimateContextState?.estimateType === 'YJSS' ? true : false}*/}
onChange={(e) => { {/* onChange={(e) => {*/}
// {/* //주문분류*/}
setHandlePricingFlag(true) {/* setHandlePricingFlag(true)*/}
setEstimateContextState({ estimateType: e.target.value, setEstimateContextState }) {/* setEstimateContextState({ estimateType: e.target.value, setEstimateContextState })*/}
}} {/* }}*/}
/> {/*/>*/}
<label htmlFor="YJSS">{getMessage('estimate.detail.estimateType.yjss')}</label> {/*<label htmlFor="YJSS">{getMessage('estimate.detail.estimateType.yjss')}</label>*/}
</div> </div>
<div className="d-check-radio light"> <div className="d-check-radio light">
<input <input

View File

@ -74,21 +74,28 @@ export const QLine = fabric.util.createClass(fabric.Line, {
setLength() { setLength() {
// Ensure all required properties are valid numbers // Ensure all required properties are valid numbers
const { x1, y1, x2, y2 } = this; const { x1, y1, x2, y2 } = this
if (isNaN(x1) || isNaN(y1) || isNaN(x2) || isNaN(y2)) { if (isNaN(x1) || isNaN(y1) || isNaN(x2) || isNaN(y2)) {
logger.error('Invalid coordinates in QLine:', { x1, y1, x2, y2 }); logger.error('Invalid coordinates in QLine:', { x1, y1, x2, y2 })
this.length = 0; this.length = 0
return; return
} }
this.length = calcLinePlaneSize({ x1, y1, x2, y2 }) / 10; this.length = calcLinePlaneSize({ x1, y1, x2, y2 }) / 10
},
setLengthByValue(length) {
this.length = length / 10
}, },
addLengthText() { addLengthText() {
const thisText = this.canvas.getObjects().find((obj) => obj.name === 'lengthText' && obj.parentId === this.id) const thisText = this.canvas.getObjects().find((obj) => obj.name === 'lengthText' && obj.parentId === this.id)
if (this.textMode === 'none') { if (thisText) {
if (thisText) { if (this.attributes?.actualSize) {
this.canvas.remove(thisText) thisText.set({ actualSize: this.attributes.actualSize })
}
if (this.attributes?.planeSize) {
thisText.set({ planeSize: this.attributes.planeSize })
} }
} else { } else {
this.setLength() this.setLength()
@ -99,11 +106,6 @@ export const QLine = fabric.util.createClass(fabric.Line, {
const x2 = this.left + this.width * scaleX const x2 = this.left + this.width * scaleX
const y2 = this.top + this.height * scaleY const y2 = this.top + this.height * scaleY
if (thisText) {
thisText.set({ text: this.getLength().toString(), left: (x1 + x2) / 2, top: (y1 + y2) / 2 })
this.text = thisText
return
}
let left, top let left, top
if (this.direction === 'left' || this.direction === 'right') { if (this.direction === 'left' || this.direction === 'right') {
left = (x1 + x2) / 2 left = (x1 + x2) / 2
@ -120,6 +122,8 @@ export const QLine = fabric.util.createClass(fabric.Line, {
const degree = (Math.atan2(y2 - y1, x2 - x1) * 180) / Math.PI const degree = (Math.atan2(y2 - y1, x2 - x1) * 180) / Math.PI
const text = new fabric.Textbox(this.getLength().toString(), { const text = new fabric.Textbox(this.getLength().toString(), {
actualSize: this.attributes?.actualSize,
planeSize: this.attributes?.planeSize,
left: left, left: left,
top: top, top: top,
fontSize: this.fontSize, fontSize: this.fontSize,

View File

@ -36,6 +36,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
this.separatePolygon = [] this.separatePolygon = []
this.toFixed = options.toFixed ?? 1 this.toFixed = options.toFixed ?? 1
this.baseLines = [] this.baseLines = []
this.adjustRoofLines = []
// this.colorLines = [] // this.colorLines = []
// 소수점 전부 제거 // 소수점 전부 제거
@ -134,7 +135,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
this.setCoords() this.setCoords()
}) })
this.on('modified', (e) => { this.on('modified', () => {
this.initLines() this.initLines()
this.addLengthText() this.addLengthText()
this.setCoords() this.setCoords()
@ -180,8 +181,27 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
return fabric.util.transformPoint(p, matrix) return fabric.util.transformPoint(p, matrix)
}) })
this.points = transformedPoints this.points = transformedPoints
const { left, top } = this.calcOriginCoords()
this.set('pathOffset', { x: left, y: top }) // 바운딩 박스 재계산 (width, height 업데이트 - fill 영역 수정)
const calcDim = this._calcDimensions({})
this.width = calcDim.width
this.height = calcDim.height
const newPathOffset = {
x: calcDim.left + this.width / 2,
y: calcDim.top + this.height / 2,
}
this.set('pathOffset', newPathOffset)
// 변환을 points에 적용했으므로 left, top, angle, scale 모두 리셋 (이중 변환 방지)
this.set({
left: newPathOffset.x,
top: newPathOffset.y,
angle: 0,
scaleX: 1,
scaleY: 1,
})
this.setCoords() this.setCoords()
this.initLines() this.initLines()
}) })
@ -223,7 +243,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
calculateDegree() { calculateDegree() {
const degrees = [] const degrees = []
// polygon.lines를 순회하며 각도를 구해 출력 // polygon.lines를 순회하며 각도를 구해 출력
this.lines.forEach((line, idx) => { this.lines.forEach((line) => {
const dx = line.x2 - line.x1 const dx = line.x2 - line.x1
const dy = line.y2 - line.y1 const dy = line.y2 - line.y1
const rad = Math.atan2(dy, dx) const rad = Math.atan2(dy, dx)
@ -258,6 +278,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
) )
.forEach((obj) => this.canvas.remove(obj)) .forEach((obj) => this.canvas.remove(obj))
this.innerLines = [] this.innerLines = []
this.adjustRoofLines = []
this.canvas.renderAll() this.canvas.renderAll()
let textMode = 'plane' let textMode = 'plane'
@ -339,18 +360,17 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
if (types.every((type) => type === LINE_TYPE.WALLLINE.EAVES)) { if (types.every((type) => type === LINE_TYPE.WALLLINE.EAVES)) {
// 용마루 -- straight-skeleton // 용마루 -- straight-skeleton
console.log('용마루 지붕') // console.log('용마루 지붕')
///drawRidgeRoof(this.id, this.canvas, textMode)
drawSkeletonRidgeRoof(this.id, this.canvas, textMode) drawSkeletonRidgeRoof(this.id, this.canvas, textMode)
} else if (isGableRoof(types)) { } else if (isGableRoof(types)) {
// A형, B형 박공 지붕 // A형, B형 박공 지붕
console.log('패턴 지붕') // console.log('패턴 지붕')
drawGableRoof(this.id, this.canvas, textMode) drawGableRoof(this.id, this.canvas, textMode)
} else if (isShedRoof(types, this.lines)) { } else if (isShedRoof(types, this.lines)) {
console.log('한쪽흐름 지붕') // console.log('한쪽흐름 지붕')
drawShedRoof(this.id, this.canvas, textMode) drawShedRoof(this.id, this.canvas, textMode)
} else { } else {
console.log('변별로 설정') // console.log('변별로 설정')
drawRoofByAttribute(this.id, this.canvas, textMode) drawRoofByAttribute(this.id, this.canvas, textMode)
} }
}, },
@ -404,7 +424,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
const degree = Big(Math.atan2(dy.toNumber(), dx.toNumber())).times(180).div(Math.PI).toNumber() const degree = Big(Math.atan2(dy.toNumber(), dx.toNumber())).times(180).div(Math.PI).toNumber()
// Create new text object if it doesn't exist // Create a new text object if it doesn't exist
const text = new fabric.Text(length.toString(), { const text = new fabric.Text(length.toString(), {
left: midPoint.x, left: midPoint.x,
top: midPoint.y, top: midPoint.y,

View File

@ -452,7 +452,16 @@ export const Orientation = forwardRef((props, ref) => {
className="input-origin block" className="input-origin block"
value={inputCompasDeg} value={inputCompasDeg}
readOnly={!hasAnglePassivity} readOnly={!hasAnglePassivity}
onChange={(value) => setInputCompasDeg(value)} onChange={(value) => {
// Convert to number and ensure it's within -180 to 180 range
const numValue = parseInt(value, 10);
if (!isNaN(numValue)) {
const clampedValue = Math.min(180, Math.max(-180, numValue));
setInputCompasDeg(String(clampedValue));
} else {
setInputCompasDeg(value);
}
}}
options={{ options={{
allowNegative: true, allowNegative: true,
allowDecimal: false allowDecimal: false

View File

@ -122,21 +122,36 @@ export default function PassivityCircuitAllocation(props) {
return return
} }
// targetModule Y N .
const targetModuleGroup = [
...new Set(
canvas
.getObjects()
.filter((obj) => obj.name === POLYGON_TYPE.MODULE && targetModules.includes(obj.id))
.map((obj) => obj.moduleInfo.northModuleYn),
),
]
if (targetModuleGroup.length > 1) {
swalFire({
text: getMessage('module.circuit.fix.not.same.roof.error'),
type: 'alert',
icon: 'warning',
})
return
}
switch (pcsTpCd) { switch (pcsTpCd) {
case 'INDFCS': { case 'INDFCS': {
const originHaveThisPcsModules = canvas const originHaveThisPcsModules = canvas
.getObjects() .getObjects()
.filter((obj) => obj.name === POLYGON_TYPE.MODULE && obj.pcs && obj.pcs.id === selectedPcs.id) .filter((obj) => obj.name === POLYGON_TYPE.MODULE && obj.pcs && obj.pcs.id === selectedPcs.id)
// pcs surface . // 1. ,
const originSurfaceList = canvas const targetModuleInfos = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE && targetModules.includes(obj.id))
.getObjects() debugger
.filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE && originHaveThisPcsModules.map((obj) => obj.surfaceId).includes(obj.id)) const newTargetModuleGroup = [...new Set(targetModuleInfos.concat(originHaveThisPcsModules).map((obj) => obj.moduleInfo.northModuleYn))]
originSurfaceList.concat(originSurfaceList).forEach((surface) => { if (newTargetModuleGroup.length > 1) {
surfaceType[`${surface.direction}-${surface.roofMaterial.pitch}`] = surface
})
if (Object.keys(surfaceType).length > 1) {
swalFire({ swalFire({
text: getMessage('module.circuit.fix.not.same.roof.error'), text: getMessage('module.circuit.fix.not.same.roof.error'),
type: 'alert', type: 'alert',
@ -229,6 +244,7 @@ export default function PassivityCircuitAllocation(props) {
roofSurface: surface.direction, roofSurface: surface.direction,
roofSurfaceIncl: +canvas.getObjects().filter((obj) => obj.id === surface.parentId)[0].pitch, roofSurfaceIncl: +canvas.getObjects().filter((obj) => obj.id === surface.parentId)[0].pitch,
roofSurfaceNorthYn: surface.direction === 'north' ? 'Y' : 'N', roofSurfaceNorthYn: surface.direction === 'north' ? 'Y' : 'N',
roofSurfaceNorthModuleYn: surface.northModuleYn,
moduleList: surface.modules.map((module) => { moduleList: surface.modules.map((module) => {
return { return {
itemId: module.moduleInfo.itemId, itemId: module.moduleInfo.itemId,

View File

@ -2,6 +2,7 @@ import Image from 'next/image'
import { useMessage } from '@/hooks/useMessage' import { useMessage } from '@/hooks/useMessage'
import { normalizeDecimalLimit, normalizeDigits } from '@/util/input-utils' import { normalizeDecimalLimit, normalizeDigits } from '@/util/input-utils'
import { CalculatorInput } from '@/components/common/input/CalcInput' import { CalculatorInput } from '@/components/common/input/CalcInput'
import { useEffect } from 'react'
export default function Angle({ props }) { export default function Angle({ props }) {
const { getMessage } = useMessage() const { getMessage } = useMessage()
@ -31,11 +32,21 @@ export default function Angle({ props }) {
className="input-origin block" className="input-origin block"
value={angle1} value={angle1}
ref={angle1Ref} ref={angle1Ref}
onChange={(value) => setAngle1(value)} onChange={(value) => {
// Calculate the final value first
let finalValue = value;
const numValue = parseInt(value, 10);
if (!isNaN(numValue)) {
const clampedValue = Math.min(180, Math.max(-180, numValue));
finalValue = String(clampedValue);
}
// Set state once with the final value
setAngle1(finalValue);
}}
placeholder="45" placeholder="45"
onFocus={() => (angle1Ref.current.value = '')} onFocus={() => (angle1Ref.current.value = '')}
options={{ options={{
allowNegative: false, allowNegative: true,
allowDecimal: true allowDecimal: true
}} }}
/> />

View File

@ -26,17 +26,15 @@ export default function DoublePitch({ props }) {
arrow2Ref, arrow2Ref,
} = props } = props
const getLength2 = () => { const getLength2 = (angle1, angle2, length1) => {
const angle1Value = angle1Ref.current.value const angle1Value = angle1 !== undefined ? angle1 : angle1Ref.current?.value
const angle2Value = angle2Ref.current.value const angle2Value = angle2 !== undefined ? angle2 : angle2Ref.current?.value
const length1Value = length1Ref.current.value const length1Value = length1 !== undefined ? length1 : length1Ref.current?.value
const arrow1Value = arrow1Ref.current const arrow1Value = arrow1Ref.current
const arrow2Value = arrow2Ref.current
if (angle1Value !== 0 && length1Value !== 0 && angle2Value !== 0 && arrow1Value !== '') { if (!isNaN(Number(angle1Value)) && !isNaN(Number(length1Value)) && !isNaN(Number(angle2Value)) && arrow1Value) {
const radian1 = (getDegreeByChon(angle1Value) * Math.PI) / 180 const radian1 = (getDegreeByChon(angle1Value) * Math.PI) / 180
const radian2 = (getDegreeByChon(angle2Value) * Math.PI) / 180 const radian2 = (getDegreeByChon(angle2Value) * Math.PI) / 180
return Math.floor((Math.tan(radian1) * length1Value) / Math.tan(radian2)) return Math.floor((Math.tan(radian1) * length1Value) / Math.tan(radian2))
} }
@ -178,7 +176,7 @@ export default function DoublePitch({ props }) {
ref={angle2Ref} ref={angle2Ref}
onChange={(value) => { onChange={(value) => {
setAngle2(value) setAngle2(value)
setLength2(getLength2()) setLength2(getLength2(angle1Ref.current?.value, value, length1Ref.current?.value))
}} }}
placeholder="45" placeholder="45"
onFocus={() => (angle2Ref.current.value = '')} onFocus={() => (angle2Ref.current.value = '')}

View File

@ -61,28 +61,40 @@ export default function RightAngle({ props }) {
<div className="grid-direction"> <div className="grid-direction">
<button <button
className={`direction up ${arrow1 === '↑' ? 'act' : ''}`} className={`direction up ${arrow1 === '↑' ? 'act' : ''}`}
onClick={() => { onMouseDown={(e) => {
e.preventDefault(); // input
}}
onClick={(e) => {
setArrow1('↑') setArrow1('↑')
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp' })) document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp' }))
}} }}
></button> ></button>
<button <button
className={`direction down ${arrow1 === '↓' ? 'act' : ''}`} className={`direction down ${arrow1 === '↓' ? 'act' : ''}`}
onClick={() => { onMouseDown={(e) => {
e.preventDefault(); // input
}}
onClick={(e) => {
setArrow1('↓') setArrow1('↓')
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' })) document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }))
}} }}
></button> ></button>
<button <button
className={`direction left ${arrow1 === '←' ? 'act' : ''}`} className={`direction left ${arrow1 === '←' ? 'act' : ''}`}
onClick={() => { onMouseDown={(e) => {
e.preventDefault(); // input
}}
onClick={(e) => {
setArrow1('←') setArrow1('←')
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowLeft' })) document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowLeft' }))
}} }}
></button> ></button>
<button <button
className={`direction right ${arrow1 === '→' ? 'act' : ''}`} className={`direction right ${arrow1 === '→' ? 'act' : ''}`}
onClick={() => { onMouseDown={(e) => {
e.preventDefault(); // input
}}
onClick={(e) => {
setArrow1('→') setArrow1('→')
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight' })) document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight' }))
}} }}

View File

@ -104,14 +104,16 @@ export default function PanelEdit(props) {
<WithDraggable isShow={true} pos={pos} className="xm"> <WithDraggable isShow={true} pos={pos} className="xm">
<WithDraggable.Header <WithDraggable.Header
title={getMessage( title={getMessage(
[PANEL_EDIT_TYPE.MOVE, PANEL_EDIT_TYPE.MOVE_ALL, PANEL_EDIT_TYPE.COLUMN_MOVE].includes(type) ? 'modal.move.setting' : 'modal.copy.setting', [PANEL_EDIT_TYPE.MOVE, PANEL_EDIT_TYPE.MOVE_ALL, PANEL_EDIT_TYPE.COLUMN_MOVE, PANEL_EDIT_TYPE.ROW_MOVE].includes(type)
? 'modal.move.setting'
: 'modal.copy.setting',
)} )}
onClose={() => closePopup(id)} onClose={() => closePopup(id)}
/> />
<WithDraggable.Body> <WithDraggable.Body>
<div className="grid-option-tit"> <div className="grid-option-tit">
{getMessage( {getMessage(
[PANEL_EDIT_TYPE.MOVE, PANEL_EDIT_TYPE.MOVE_ALL, PANEL_EDIT_TYPE.COLUMN_MOVE].includes(type) [PANEL_EDIT_TYPE.MOVE, PANEL_EDIT_TYPE.MOVE_ALL, PANEL_EDIT_TYPE.COLUMN_MOVE, PANEL_EDIT_TYPE.ROW_MOVE].includes(type)
? 'modal.move.setting.info' ? 'modal.move.setting.info'
: 'modal.copy.setting.info', : 'modal.copy.setting.info',
)} )}

View File

@ -114,7 +114,7 @@ export default function ChangePasswordPop(props) {
const result = { ...sessionState, pwdInitYn: 'Y' } const result = { ...sessionState, pwdInitYn: 'Y' }
setSession(result) setSession(result)
setSessionState(result) setSessionState(result)
props.setChagePasswordPopOpen(false) props.setChangePasswordPopOpen(false)
await login() await login()
}, },
}) })

View File

@ -1,7 +1,19 @@
import { useRecoilValue } from 'recoil' import { useRecoilValue } from 'recoil'
import { canvasState } from '@/store/canvasAtom' import { canvasState } from '@/store/canvasAtom'
import { fontSelector } from '@/store/fontAtom' import { fontSelector } from '@/store/fontAtom'
import { useEffect } from 'react' import { useCallback, useEffect } from 'react'
/** 폰트 타입별 캔버스 오브젝트 이름 매핑 */
const FONT_TYPE_TO_OBJ_NAME = {
commonText: 'commonText',
dimensionLineText: 'dimensionLineText',
flowText: 'flowText',
lengthText: 'lengthText',
circuitNumberText: 'circuitNumber',
}
/** 캔버스 오브젝트 이름 → 폰트 타입 역매핑 */
const OBJ_NAME_TO_FONT_TYPE = Object.fromEntries(Object.entries(FONT_TYPE_TO_OBJ_NAME).map(([k, v]) => [v, k]))
export function useFont() { export function useFont() {
const canvas = useRecoilValue(canvasState) const canvas = useRecoilValue(canvasState)
@ -11,85 +23,98 @@ export function useFont() {
const lengthText = useRecoilValue(fontSelector('lengthText')) const lengthText = useRecoilValue(fontSelector('lengthText'))
const circuitNumberText = useRecoilValue(fontSelector('circuitNumberText')) const circuitNumberText = useRecoilValue(fontSelector('circuitNumberText'))
/** 폰트 타입별 설정 매핑 */
const fontSettings = {
commonText,
dimensionLineText,
flowText,
lengthText,
circuitNumberText,
}
/**
* 타입별 폰트 설정을 캔버스 오브젝트에 적용하는 공통 함수
* @param {string} type - 폰트 타입 (commonText, dimensionLineText, flowText, lengthText, circuitNumberText)
* @param {number} delay - 적용 지연 시간 (ms), 기본값 200
*/
const changeFontByType = useCallback(
(type, delay = 200) => {
const fontSetting = fontSettings[type]
const objName = FONT_TYPE_TO_OBJ_NAME[type]
if (!fontSetting || !objName) {
console.warn(`Invalid font type: ${type}`)
return
}
setTimeout(() => {
if (canvas && fontSetting.fontWeight?.value) {
const textObjs = canvas.getObjects().filter((obj) => obj.name === objName)
textObjs.forEach((obj) => {
obj.set({
fontFamily: fontSetting.fontFamily.value,
fontWeight: fontSetting.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal',
fontStyle: fontSetting.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal',
fontSize: fontSetting.fontSize.value,
fill: fontSetting.fontColor.value,
})
})
canvas.renderAll()
}
}, delay)
},
[canvas, fontSettings],
)
const changeAllFonts = () => {
changeFontByType('commonText')
changeFontByType('dimensionLineText')
changeFontByType('flowText')
changeFontByType('lengthText')
changeFontByType('circuitNumberText')
}
/** 각 폰트 타입별 useEffect */
useEffect(() => { useEffect(() => {
if (canvas && commonText.fontWeight.value) { changeFontByType('commonText')
const textObjs = canvas?.getObjects().filter((obj) => obj.name === 'commonText')
textObjs.forEach((obj) => {
obj.set({
fontFamily: commonText.fontFamily.value,
fontWeight: commonText.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal',
fontStyle: commonText.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal',
fontSize: commonText.fontSize.value,
fill: commonText.fontColor.value,
})
})
canvas.renderAll()
}
}, [commonText]) }, [commonText])
useEffect(() => { useEffect(() => {
if (canvas && dimensionLineText.fontWeight.value) { changeFontByType('dimensionLineText')
const textObjs = canvas?.getObjects().filter((obj) => obj.name === 'dimensionLineText')
textObjs.forEach((obj) => {
obj.set({
fontFamily: dimensionLineText.fontFamily.value,
fontWeight: dimensionLineText.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal',
fontStyle: dimensionLineText.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal',
fontSize: dimensionLineText.fontSize.value,
fill: dimensionLineText.fontColor.value,
})
})
canvas.renderAll()
}
}, [dimensionLineText]) }, [dimensionLineText])
useEffect(() => { useEffect(() => {
if (canvas && flowText.fontWeight.value) { changeFontByType('flowText')
const textObjs = canvas?.getObjects().filter((obj) => obj.name === 'flowText')
textObjs.forEach((obj) => {
obj.set({
fontFamily: flowText.fontFamily.value,
fontWeight: flowText.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal',
fontStyle: flowText.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal',
fontSize: flowText.fontSize.value,
fill: flowText.fontColor.value,
})
})
canvas.renderAll()
}
}, [flowText]) }, [flowText])
useEffect(() => { useEffect(() => {
if (canvas && lengthText.fontWeight.value) { changeFontByType('lengthText')
const textObjs = canvas?.getObjects().filter((obj) => obj.name === 'lengthText')
textObjs.forEach((obj) => {
obj.set({
fontFamily: lengthText.fontFamily.value,
fontWeight: lengthText.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal',
fontStyle: lengthText.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal',
fontSize: lengthText.fontSize.value,
fill: lengthText.fontColor.value,
})
})
canvas.renderAll()
}
}, [lengthText]) }, [lengthText])
useEffect(() => { useEffect(() => {
if (canvas && circuitNumberText.fontWeight.value) { changeFontByType('circuitNumberText')
const textObjs = canvas?.getObjects().filter((obj) => obj.name === 'circuitNumber')
textObjs.forEach((obj) => {
obj.set({
fontFamily: circuitNumberText.fontFamily.value,
fontWeight: circuitNumberText.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal',
fontStyle: circuitNumberText.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal',
fontSize: circuitNumberText.fontSize.value,
fill: circuitNumberText.fontColor.value,
})
})
canvas.renderAll()
}
}, [circuitNumberText]) }, [circuitNumberText])
return {} /** 캔버스에 텍스트 오브젝트 추가 시 자동으로 폰트 적용 */
useEffect(() => {
if (!canvas) return
const handleObjectAdded = (e) => {
const obj = e.target
if (!obj?.name) return
const fontType = OBJ_NAME_TO_FONT_TYPE[obj.name]
if (fontType) {
changeFontByType(fontType, 0)
}
}
canvas.on('object:added', handleObjectAdded)
return () => {
canvas.off('object:added', handleObjectAdded)
}
}, [canvas, changeFontByType])
return { changeFontByType, changeAllFonts }
} }

View File

@ -44,6 +44,9 @@ export const useTrestle = () => {
// exposedBottomPoints는 노출 최하면 들의 centerPoint 배열. // exposedBottomPoints는 노출 최하면 들의 centerPoint 배열.
const surfaces = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE) const surfaces = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE)
surfaces.forEach((surface) => {
surface.set({ quotationParam: null })
})
// 기존 eaveBar를 삭제 // 기존 eaveBar를 삭제
canvas.getObjects().forEach((obj) => { canvas.getObjects().forEach((obj) => {
if (obj.name === 'eaveBar' || obj.name === 'rack' || obj.name === 'halfEaveBar' || obj.name === 'smartRack') { if (obj.name === 'eaveBar' || obj.name === 'rack' || obj.name === 'halfEaveBar' || obj.name === 'smartRack') {
@ -744,18 +747,17 @@ export const useTrestle = () => {
return return
} }
//itemList = data //itemList = data
// itemList에 northModuleYn 추가 // itemList에 northModuleYn 추가
itemList = data.map(item => { itemList = data.map((item) => {
if (item.itemTpCd === "MODULE") { if (item.itemTpCd === 'MODULE') {
const matchedModule = modules.find(module => module.moduleItemId === item.itemId); const matchedModule = modules.find((module) => module.moduleItemId === item.itemId)
return { return {
...item, ...item,
northModuleYn: matchedModule?.northModuleYn || 'N' northModuleYn: matchedModule?.northModuleYn || 'N',
}; }
} }
return item; return item
}); })
//northArrangement 북면 설치 여부 //northArrangement 북면 설치 여부
const northArrangement = getNorthArrangement() const northArrangement = getNorthArrangement()
@ -819,13 +821,18 @@ export const useTrestle = () => {
// 발전 시뮬레이션 용 각도 재계산 // 발전 시뮬레이션 용 각도 재계산
const getAzimuth = (parent) => { const getAzimuth = (parent) => {
const { moduleCompass, surfaceCompass, direction } = parent if (typeof parent === 'string') {
console.warn('getAzimuth: parent is string, expected object', parent)
return 0 // 또는 적절한 기본값
}
const { moduleCompass, surfaceCompass, direction } = parent || {}
if (surfaceCompass) { if (surfaceCompass) {
return -surfaceCompass return -surfaceCompass
} }
let resultAzimuth = moduleCompass let resultAzimuth = parseInt(moduleCompass, 10)
switch (direction) { switch (direction) {
case 'south': { case 'south': {
@ -2598,7 +2605,7 @@ export const useTrestle = () => {
return { return {
moduleTpCd: module.moduleInfo.itemTp, moduleTpCd: module.moduleInfo.itemTp,
moduleItemId: module.moduleInfo.itemId, moduleItemId: module.moduleInfo.itemId,
northModuleYn: module?.moduleInfo?.northModuleYn || 'N' // 기본값 'N' northModuleYn: module?.moduleInfo?.northModuleYn || 'N', // 기본값 'N'
} }
}) })
@ -2610,7 +2617,7 @@ export const useTrestle = () => {
moduleTpCd: cur.moduleTpCd, moduleTpCd: cur.moduleTpCd,
moduleItemId: cur.moduleItemId, moduleItemId: cur.moduleItemId,
cnt: 0, cnt: 0,
northModuleYn: cur.northModuleYn northModuleYn: cur.northModuleYn,
} }
} }
acc[key].cnt++ acc[key].cnt++
@ -2623,7 +2630,7 @@ export const useTrestle = () => {
moduleTpCd: groupedParam.moduleTpCd, moduleTpCd: groupedParam.moduleTpCd,
moduleItemId: groupedParam.moduleItemId, moduleItemId: groupedParam.moduleItemId,
moduleCnt: groupedParam.cnt, moduleCnt: groupedParam.cnt,
northModuleYn: groupedParam.northModuleYn northModuleYn: groupedParam.northModuleYn,
// northModuleYn: params.find(p => // northModuleYn: params.find(p =>
// p.moduleTpCd === groupedParam.moduleTpCd && // p.moduleTpCd === groupedParam.moduleTpCd &&
// p.moduleItemId === groupedParam.moduleItemId // p.moduleItemId === groupedParam.moduleItemId

View File

@ -142,6 +142,15 @@ export function useMovementSetting(id) {
} }
}, []) }, [])
useEffect(() => {
const roofs = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
if (roofs.length === 0) {
swalFire({ text: getMessage('roof.line.not.found') })
closePopup(id)
return
}
}, [])
/** object 선택이 변경될 때 처리*/ /** object 선택이 변경될 때 처리*/
useEffect(() => { useEffect(() => {
if (FOLLOW_LINE_REF.current != null) { if (FOLLOW_LINE_REF.current != null) {
@ -186,7 +195,6 @@ export function useMovementSetting(id) {
canvas.renderAll() canvas.renderAll()
}, [currentObject]) }, [currentObject])
const clearRef = () => { const clearRef = () => {
if (type === TYPE.FLOW_LINE) { if (type === TYPE.FLOW_LINE) {
// 안전한 ref 접근 // 안전한 ref 접근
@ -258,23 +266,25 @@ export function useMovementSetting(id) {
let value = '' let value = ''
let direction = '' let direction = ''
if (Math.abs(target.y1 - target.y2) < 0.5) { // 수평 라인 if (Math.abs(target.y1 - target.y2) < 0.5) {
// 수평 라인
value = Big(targetTop).minus(currentY).times(10).round(0) value = Big(targetTop).minus(currentY).times(10).round(0)
// 방향 감지 // 방향 감지
if (value.toNumber() > 0) { if (value.toNumber() > 0) {
direction = 'up' // 마우스가 라인 위쪽에 있음 (위로 움직임) direction = 'up' // 마우스가 라인 위쪽에 있음 (위로 움직임)
} else if (value.toNumber() < 0) { } else if (value.toNumber() < 0) {
direction = 'down' // 마우스가 라인 아래쪽에 있음 (아래로 움직임) direction = 'down' // 마우스가 라인 아래쪽에 있음 (아래로 움직임)
} }
} else { // 수직 라인 } else {
// 수직 라인
value = Big(targetLeft).minus(currentX).times(10).round(0).neg() value = Big(targetLeft).minus(currentX).times(10).round(0).neg()
// 방향 감지 // 방향 감지
if (value.toNumber() > 0) { if (value.toNumber() > 0) {
direction = 'right' // 마우스가 라인 오른쪽에 있음 (오른쪽으로 움직임) direction = 'right' // 마우스가 라인 오른쪽에 있음 (오른쪽으로 움직임)
} else if (value.toNumber() < 0) { } else if (value.toNumber() < 0) {
direction = 'left' // 마우스가 라인 왼쪽에 있음 (왼쪽으로 움직임) direction = 'left' // 마우스가 라인 왼쪽에 있음 (왼쪽으로 움직임)
} }
} }
@ -312,37 +322,35 @@ export function useMovementSetting(id) {
const midY = Big(target.y1).plus(target.y2).div(2) const midY = Big(target.y1).plus(target.y2).div(2)
const wall = canvas.getObjects().find((obj) => obj.id === target.attributes.wallId) const wall = canvas.getObjects().find((obj) => obj.id === target.attributes.wallId)
const result = getSelectLinePosition(wall, target, { const result = getSelectLinePosition(wall, target, {
testDistance: 5, // 테스트 거리 testDistance: 5, // 테스트 거리
debug: true // 디버깅 로그 출력 debug: true, // 디버깅 로그 출력
}); })
//console.log("1111litarget:::::", target); //console.log("1111litarget:::::", target);
//console.log("1111linePosition:::::", result.position); // 'top', 'bottom', 'left', 'right' //console.log("1111linePosition:::::", result.position); // 'top', 'bottom', 'left', 'right'
let linePosition = result.position; let linePosition = result.position
//console.log("1111linePosition:::::", direction, linePosition); //console.log("1111linePosition:::::", direction, linePosition);
if (target.y1 === target.y2) { //수평벽 if (target.y1 === target.y2) {
//수평벽
const setRadioStates = (isUp) => { const setRadioStates = (isUp) => {
if (UP_DOWN_REF.UP_RADIO_REF.current) { if (UP_DOWN_REF.UP_RADIO_REF.current) {
UP_DOWN_REF.UP_RADIO_REF.current.checked = isUp; UP_DOWN_REF.UP_RADIO_REF.current.checked = isUp
} }
if (UP_DOWN_REF.DOWN_RADIO_REF.current) { if (UP_DOWN_REF.DOWN_RADIO_REF.current) {
UP_DOWN_REF.DOWN_RADIO_REF.current.checked = !isUp; UP_DOWN_REF.DOWN_RADIO_REF.current.checked = !isUp
} }
};
if (linePosition === 'top') {
setRadioStates(value.s !== -1);
} else if (linePosition === 'bottom') {
setRadioStates(value.s !== 1);
} }
if(direction === 'up') { if (linePosition === 'top') {
setRadioStates(value.s !== -1)
} else if (linePosition === 'bottom') {
setRadioStates(value.s !== 1)
}
if (direction === 'up') {
} }
/* /*
checkPoint = { x: midX.toNumber(), y: midY.plus(10).toNumber() } checkPoint = { x: midX.toNumber(), y: midY.plus(10).toNumber() }
@ -367,20 +375,19 @@ export function useMovementSetting(id) {
} }
*/ */
} else { } else {
const setRadioStates = (isUp) => { const setRadioStates = (isUp) => {
if (UP_DOWN_REF.UP_RADIO_REF.current) { if (UP_DOWN_REF.UP_RADIO_REF.current) {
UP_DOWN_REF.UP_RADIO_REF.current.checked = isUp; UP_DOWN_REF.UP_RADIO_REF.current.checked = isUp
} }
if (UP_DOWN_REF.DOWN_RADIO_REF.current) { if (UP_DOWN_REF.DOWN_RADIO_REF.current) {
UP_DOWN_REF.DOWN_RADIO_REF.current.checked = !isUp; UP_DOWN_REF.DOWN_RADIO_REF.current.checked = !isUp
} }
}; }
if (linePosition === 'left') { if (linePosition === 'left') {
setRadioStates(value.s !== 1); setRadioStates(value.s !== 1)
} else if (linePosition === 'right') { } else if (linePosition === 'right') {
setRadioStates(value.s !== -1); setRadioStates(value.s !== -1)
} }
/* /*
checkPoint = { x: midX.plus(10).toNumber(), y: midY.toNumber() } checkPoint = { x: midX.plus(10).toNumber(), y: midY.toNumber() }
@ -407,11 +414,8 @@ export function useMovementSetting(id) {
*/ */
} }
} }
} }
const mouseDownEvent = (e) => { const mouseDownEvent = (e) => {
canvas canvas
.getObjects() .getObjects()
@ -460,7 +464,6 @@ export function useMovementSetting(id) {
canvas.renderAll() canvas.renderAll()
} }
const target = selectedObject.current !== null ? selectedObject.current : CONFIRM_LINE_REF.current?.target const target = selectedObject.current !== null ? selectedObject.current : CONFIRM_LINE_REF.current?.target
if (!target) return if (!target) return
@ -468,23 +471,23 @@ export function useMovementSetting(id) {
const roof = canvas.getObjects().find((obj) => obj.id === roofId) const roof = canvas.getObjects().find((obj) => obj.id === roofId)
// 현이동, 동이동 추가 // 현이동, 동이동 추가
let flPointValue = FLOW_LINE_REF.POINTER_INPUT_REF.current?.value ?? 0; let flPointValue = FLOW_LINE_REF.POINTER_INPUT_REF.current?.value ?? 0
let flFilledValue = FLOW_LINE_REF.FILLED_INPUT_REF.current?.value ?? 0; let flFilledValue = FLOW_LINE_REF.FILLED_INPUT_REF.current?.value ?? 0
flPointValue = (flFilledValue > 0 || flFilledValue < 0) ? flFilledValue : flPointValue; flPointValue = flFilledValue > 0 || flFilledValue < 0 ? flFilledValue : flPointValue
const moveFlowLine = typeRef.current === TYPE.FLOW_LINE ? flPointValue : 0 const moveFlowLine = typeRef.current === TYPE.FLOW_LINE ? flPointValue : 0
let udPointValue = UP_DOWN_REF.POINTER_INPUT_REF.current?.value ?? 0; let udPointValue = UP_DOWN_REF.POINTER_INPUT_REF.current?.value ?? 0
let udFilledValue = UP_DOWN_REF.FILLED_INPUT_REF.current?.value ?? 0; let udFilledValue = UP_DOWN_REF.FILLED_INPUT_REF.current?.value ?? 0
udPointValue = udFilledValue > 0 ? udFilledValue : udPointValue; udPointValue = udFilledValue > 0 ? udFilledValue : udPointValue
const moveUpDown = typeRef.current === TYPE.UP_DOWN ? udPointValue : 0 const moveUpDown = typeRef.current === TYPE.UP_DOWN ? udPointValue : 0
roof.moveFlowLine = parseInt(moveFlowLine, 10) || 0; roof.moveFlowLine = parseInt(moveFlowLine, 10) || 0
roof.moveUpDown = parseInt(moveUpDown, 10) || 0; roof.moveUpDown = parseInt(moveUpDown, 10) || 0
roof.moveDirect = ""; roof.moveDirect = ''
roof.moveSelectLine = target roof.moveSelectLine = target
//console.log("target::::", target, roof.moveSelectLine) //console.log("target::::", target, roof.moveSelectLine)
const wall = canvas.getObjects().find((obj) => obj.name === POLYGON_TYPE.WALL && obj.attributes.roofId === roofId) const wall = canvas.getObjects().find((obj) => obj.name === POLYGON_TYPE.WALL && obj.attributes.roofId === roofId)
const baseLines = wall.baseLines const baseLines = wall.baseLines
let centerPoint = wall.getCenterPoint(); let centerPoint = wall.getCenterPoint()
let targetBaseLines = [] let targetBaseLines = []
let isGableRoof let isGableRoof
if (typeRef.current === TYPE.FLOW_LINE) { if (typeRef.current === TYPE.FLOW_LINE) {
@ -522,9 +525,19 @@ export function useMovementSetting(id) {
return minX <= line.x1 && line.x1 <= maxX && minX <= line.x2 && line.x2 <= maxX return minX <= line.x1 && line.x1 <= maxX && minX <= line.x2 && line.x2 <= maxX
}) })
if (isGableRoof && currentBaseLines.length > 0) { if (isGableRoof && currentBaseLines.length > 0) {
currentBaseLines.forEach((line) => targetBaseLines.push({ line, distance: Big(line.y1).minus(target.y1).abs().toNumber() })) currentBaseLines.forEach((line) =>
targetBaseLines.push({
line,
distance: Big(line.y1).minus(target.y1).abs().toNumber(),
}),
)
} else { } else {
checkBaseLines.forEach((line) => targetBaseLines.push({ line, distance: Big(target.y1).minus(line.y1).abs().toNumber() })) checkBaseLines.forEach((line) =>
targetBaseLines.push({
line,
distance: Big(target.y1).minus(line.y1).abs().toNumber(),
}),
)
} }
baseLines baseLines
.filter((line) => line.y1 === line.y2 && line.y1 < target.y1) .filter((line) => line.y1 === line.y2 && line.y1 < target.y1)
@ -538,9 +551,19 @@ export function useMovementSetting(id) {
return minX <= line.x1 && line.x1 <= maxX && minX <= line.x2 && line.x2 <= maxX return minX <= line.x1 && line.x1 <= maxX && minX <= line.x2 && line.x2 <= maxX
}) })
if (isGableRoof && currentBaseLines.length > 0) { if (isGableRoof && currentBaseLines.length > 0) {
currentBaseLines.forEach((line) => targetBaseLines.push({ line, distance: Big(line.y1).minus(target.y1).abs().toNumber() })) currentBaseLines.forEach((line) =>
targetBaseLines.push({
line,
distance: Big(line.y1).minus(target.y1).abs().toNumber(),
}),
)
} else { } else {
checkBaseLines.forEach((line) => targetBaseLines.push({ line, distance: Big(line.y1).minus(target.y1).abs().toNumber() })) checkBaseLines.forEach((line) =>
targetBaseLines.push({
line,
distance: Big(line.y1).minus(target.y1).abs().toNumber(),
}),
)
} }
break break
case 'right': case 'right':
@ -551,9 +574,19 @@ export function useMovementSetting(id) {
return minY <= line.y1 && line.y1 <= maxY && minY <= line.y2 && line.y2 <= maxY return minY <= line.y1 && line.y1 <= maxY && minY <= line.y2 && line.y2 <= maxY
}) })
if (isGableRoof && currentBaseLines.length > 0) { if (isGableRoof && currentBaseLines.length > 0) {
currentBaseLines.forEach((line) => targetBaseLines.push({ line, distance: Big(line.x1).minus(target.x1).abs().toNumber() })) currentBaseLines.forEach((line) =>
targetBaseLines.push({
line,
distance: Big(line.x1).minus(target.x1).abs().toNumber(),
}),
)
} else { } else {
checkBaseLines.forEach((line) => targetBaseLines.push({ line, distance: Big(target.x1).minus(line.x1).abs().toNumber() })) checkBaseLines.forEach((line) =>
targetBaseLines.push({
line,
distance: Big(target.x1).minus(line.x1).abs().toNumber(),
}),
)
} }
break break
case 'left': case 'left':
@ -564,9 +597,19 @@ export function useMovementSetting(id) {
return minY <= line.y1 && line.y1 <= maxY && minY <= line.y2 && line.y2 <= maxY return minY <= line.y1 && line.y1 <= maxY && minY <= line.y2 && line.y2 <= maxY
}) })
if (isGableRoof && currentBaseLines.length > 0) { if (isGableRoof && currentBaseLines.length > 0) {
currentBaseLines.forEach((line) => targetBaseLines.push({ line, distance: Big(line.x1).minus(target.x1).abs().toNumber() })) currentBaseLines.forEach((line) =>
targetBaseLines.push({
line,
distance: Big(line.x1).minus(target.x1).abs().toNumber(),
}),
)
} else { } else {
checkBaseLines.forEach((line) => targetBaseLines.push({ line, distance: Big(target.x1).minus(line.x1).abs().toNumber() })) checkBaseLines.forEach((line) =>
targetBaseLines.push({
line,
distance: Big(target.x1).minus(line.x1).abs().toNumber(),
}),
)
} }
break break
} }
@ -576,18 +619,18 @@ export function useMovementSetting(id) {
} }
// Remove duplicate lines // Remove duplicate lines
const uniqueLines = new Map(); const uniqueLines = new Map()
targetBaseLines = targetBaseLines.filter(item => { targetBaseLines = targetBaseLines.filter((item) => {
const key = `${item.line.x1},${item.line.y1},${item.line.x2},${item.line.y2}`; const key = `${item.line.x1},${item.line.y1},${item.line.x2},${item.line.y2}`
if (!uniqueLines.has(key)) { if (!uniqueLines.has(key)) {
uniqueLines.set(key, true); uniqueLines.set(key, true)
return true; return true
} }
return false; return false
}); })
// Sort by distance // Sort by distance
targetBaseLines.sort((a, b) => a.distance - b.distance); targetBaseLines.sort((a, b) => a.distance - b.distance)
targetBaseLines = targetBaseLines.filter((line) => line.distance === targetBaseLines[0].distance) targetBaseLines = targetBaseLines.filter((line) => line.distance === targetBaseLines[0].distance)
if (isGableRoof) { if (isGableRoof) {
@ -621,27 +664,22 @@ export function useMovementSetting(id) {
let value let value
if (typeRef.current === TYPE.FLOW_LINE) { if (typeRef.current === TYPE.FLOW_LINE) {
value = (() => { value = (() => {
const filledValue = FLOW_LINE_REF.FILLED_INPUT_REF.current?.value; const filledValue = FLOW_LINE_REF.FILLED_INPUT_REF.current?.value
const pointerValue = FLOW_LINE_REF.POINTER_INPUT_REF.current?.value; const pointerValue = FLOW_LINE_REF.POINTER_INPUT_REF.current?.value
if (filledValue && !isNaN(filledValue) && filledValue.trim() !== '') { if (filledValue && !isNaN(filledValue) && filledValue.trim() !== '') {
return Big(filledValue).times(2); return Big(filledValue).times(2)
} else if (pointerValue && !isNaN(pointerValue) && pointerValue.trim() !== '') { } else if (pointerValue && !isNaN(pointerValue) && pointerValue.trim() !== '') {
return Big(pointerValue).times(2); return Big(pointerValue).times(2)
} }
return Big(0); // 기본값으로 0 반환 또는 다른 적절한 기본값 return Big(0) // 기본값으로 0 반환 또는 다른 적절한 기본값
})(); })()
if (Math.abs(target.y1 - target.y2) < 0.5) { if (Math.abs(target.y1 - target.y2) < 0.5) {
value = value.neg() value = value.neg()
} }
} else { } else {
console.log("error::", UP_DOWN_REF.POINTER_INPUT_REF.current.value) console.log('error::', UP_DOWN_REF.POINTER_INPUT_REF.current.value)
value = Big( value = Big(UP_DOWN_REF?.FILLED_INPUT_REF?.current?.value?.trim() || UP_DOWN_REF?.POINTER_INPUT_REF?.current?.value?.trim() || '0')
(UP_DOWN_REF?.FILLED_INPUT_REF?.current?.value?.trim() ||
UP_DOWN_REF?.POINTER_INPUT_REF?.current?.value?.trim() ||
'0'
)
);
const midX = Big(target.x1).plus(target.x2).div(2) const midX = Big(target.x1).plus(target.x2).div(2)
const midY = Big(target.y1).plus(target.y2).div(2) const midY = Big(target.y1).plus(target.y2).div(2)
@ -665,17 +703,16 @@ export function useMovementSetting(id) {
// console.log("2222저장된 moveSelectLine:", roof.moveSelectLine); // console.log("2222저장된 moveSelectLine:", roof.moveSelectLine);
// console.log("222wall::::", wall.points) // console.log("222wall::::", wall.points)
const result = getSelectLinePosition(wall, target, { const result = getSelectLinePosition(wall, target, {
testDistance: 5, // 테스트 거리 testDistance: 5, // 테스트 거리
debug: true // 디버깅 로그 출력 debug: true, // 디버깅 로그 출력
}); })
//console.log("2222linePosition:::::", result.position); //console.log("2222linePosition:::::", result.position);
//console.log("222moveDirect:::::", roof.moveDirect); //console.log("222moveDirect:::::", roof.moveDirect);
// 디버깅용 분류 결과 확인
// 디버깅용 분류 결과 확인 let linePosition = result.position
let linePosition = result.position;
roof.movePosition = linePosition roof.movePosition = linePosition
value = value.div(10) value = value.div(10)
targetBaseLines targetBaseLines
@ -684,15 +721,14 @@ export function useMovementSetting(id) {
const currentLine = target.line const currentLine = target.line
//console.log("linePosition::::::::::::::", linePosition) //console.log("linePosition::::::::::::::", linePosition)
if (UP_DOWN_REF?.DOWN_RADIO_REF?.current?.checked ){ if (UP_DOWN_REF?.DOWN_RADIO_REF?.current?.checked) {
//position확인 //position확인
if(linePosition === 'bottom' || linePosition === 'right') { if (linePosition === 'bottom' || linePosition === 'right') {
//console.log("1value::::::::::::::", value.toString()) //console.log("1value::::::::::::::", value.toString())
value = value.neg() value = value.neg()
} }
}else { } else {
if(linePosition === 'top' || linePosition === 'left') { if (linePosition === 'top' || linePosition === 'left') {
//console.log("1value::::::::::::::", value.toString()) //console.log("1value::::::::::::::", value.toString())
value = value.neg() value = value.neg()
} }
@ -753,7 +789,6 @@ export function useMovementSetting(id) {
// javascript // javascript
return { return {
TYPE, TYPE,
closePopup, closePopup,
@ -765,4 +800,3 @@ export function useMovementSetting(id) {
handleSave, handleSave,
} }
} }

View File

@ -251,6 +251,7 @@ export function useOuterLineWall(id, propertiesId) {
removeAllDocumentEventListeners() removeAllDocumentEventListeners()
canvas?.renderAll() canvas?.renderAll()
setOuterLineFix(true) setOuterLineFix(true)
canvas.outerLineFix = true
closePopup(id) closePopup(id)
ccwCheck() ccwCheck()
addPopup(propertiesId, 1, <RoofShapeSetting id={propertiesId} pos={{ x: 50, y: 230 }} />) addPopup(propertiesId, 1, <RoofShapeSetting id={propertiesId} pos={{ x: 50, y: 230 }} />)
@ -811,7 +812,7 @@ export function useOuterLineWall(id, propertiesId) {
if (points.length === 0) { if (points.length === 0) {
return return
} }
enterCheck(e) // enterCheck(e)
const key = e.key const key = e.key
switch (key) { switch (key) {
case 'Enter': { case 'Enter': {

View File

@ -30,7 +30,6 @@ import { QcastContext } from '@/app/QcastProvider'
import { usePlan } from '@/hooks/usePlan' import { usePlan } from '@/hooks/usePlan'
import { roofsState } from '@/store/roofAtom' import { roofsState } from '@/store/roofAtom'
import { useText } from '@/hooks/useText' import { useText } from '@/hooks/useText'
import { processEaveHelpLines } from '@/util/skeleton-utils'
import { QLine } from '@/components/fabric/QLine' import { QLine } from '@/components/fabric/QLine'
export function useRoofAllocationSetting(id) { export function useRoofAllocationSetting(id) {
@ -114,9 +113,9 @@ export function useRoofAllocationSetting(id) {
*/ */
const fetchBasicSettings = async (planNo) => { const fetchBasicSettings = async (planNo) => {
try { try {
const response = await get({ url: `/api/canvas-management/canvas-basic-settings/by-object/${correntObjectNo}/${planNo}` }); const response = await get({ url: `/api/canvas-management/canvas-basic-settings/by-object/${correntObjectNo}/${planNo}` })
let roofsArray = []; let roofsArray = []
// API에서 데이터를 성공적으로 가져온 경우 // API에서 데이터를 성공적으로 가져온 경우
if (response && response.length > 0) { if (response && response.length > 0) {
@ -133,15 +132,15 @@ export function useRoofAllocationSetting(id) {
roofPitch: item.roofPitch, roofPitch: item.roofPitch,
roofAngle: item.roofAngle, roofAngle: item.roofAngle,
selected: index === 0, // 첫 번째 항목을 기본 선택으로 설정 selected: index === 0, // 첫 번째 항목을 기본 선택으로 설정
index: index index: index,
})); }))
} }
// API에서 데이터가 없고 기존 roofList가 있는 경우 // API에서 데이터가 없고 기존 roofList가 있는 경우
else if (roofList && roofList.length > 0) { else if (roofList && roofList.length > 0) {
roofsArray = roofList.map((roof, index) => ({ roofsArray = roofList.map((roof, index) => ({
...roof, ...roof,
selected: index === 0 // 첫 번째 항목을 기본 선택으로 설정 selected: index === 0, // 첫 번째 항목을 기본 선택으로 설정
})); }))
} }
// 둘 다 없는 경우 기본값 설정 // 둘 다 없는 경우 기본값 설정
else { else {
@ -156,64 +155,62 @@ export function useRoofAllocationSetting(id) {
roofHajebichi: 0, roofHajebichi: 0,
roofGap: 'HEI_455', roofGap: 'HEI_455',
roofLayout: 'P', roofLayout: 'P',
roofPitch: 4, roofPitch: 4,
roofAngle: 21.8, roofAngle: 21.8,
}, },
] ]
}
/**
* 데이터 설정
*/
const selectRoofs = []
for (let i = 0; i < roofsArray.length; i++) {
roofMaterials?.map((material) => {
if (material.roofMatlCd === roofsArray[i].roofMatlCd) {
selectRoofs.push({
...material,
selected: roofsArray[i].roofApply,
index: roofsArray[i].roofSeq,
id: roofsArray[i].roofMatlCd,
width: roofsArray[i].roofWidth,
length: roofsArray[i].roofHeight,
hajebichi: roofsArray[i].roofHajebichi,
raft: roofsArray[i].roofGap,
layout: roofsArray[i].roofLayout,
pitch: roofsArray[i].roofPitch,
angle: roofsArray[i].roofAngle,
})
} }
/**
* 데이터 설정
*/
const selectRoofs = []
for (let i = 0; i < roofsArray.length; i++) {
roofMaterials?.map((material) => {
if (material.roofMatlCd === roofsArray[i].roofMatlCd) {
selectRoofs.push({
...material,
selected: roofsArray[i].roofApply,
index: roofsArray[i].roofSeq,
id: roofsArray[i].roofMatlCd,
width: roofsArray[i].roofWidth,
length: roofsArray[i].roofHeight,
hajebichi: roofsArray[i].roofHajebichi,
raft: roofsArray[i].roofGap,
layout: roofsArray[i].roofLayout,
pitch: roofsArray[i].roofPitch,
angle: roofsArray[i].roofAngle,
})
}
})
}
const firstRes = Array.isArray(res) && res.length > 0 ? res[0] : null
setBasicSetting({
...basicSetting,
planNo: firstRes?.planNo ?? planNo,
roofSizeSet: firstRes?.roofSizeSet ?? 0,
roofAngleSet: firstRes?.roofAngleSet ?? 0,
roofsData: roofsArray,
selectedRoofMaterial: selectRoofs.find((roof) => roof.selected),
}) })
}
setBasicInfo({ const firstRes = Array.isArray(response) && response.length > 0 ? response[0] : null
planNo: '' + (firstRes?.planNo ?? planNo),
roofSizeSet: '' + (firstRes?.roofSizeSet ?? 0),
roofAngleSet: '' + (firstRes?.roofAngleSet ?? 0),
})
// 데이터 동기화: 렌더링용 필드 기본값 보정
const normalizedRoofs = selectRoofs.map((roof) => ({
...roof,
width: roof.width ?? '',
length: roof.length ?? '',
hajebichi: roof.hajebichi ?? '',
pitch: roof.pitch ?? '',
angle: roof.angle ?? '',
}))
setCurrentRoofList(normalizedRoofs)
setBasicSetting({
...basicSetting,
planNo: firstRes?.planNo ?? planNo,
roofSizeSet: firstRes?.roofSizeSet ?? 0,
roofAngleSet: firstRes?.roofAngleSet ?? 0,
roofsData: roofsArray,
selectedRoofMaterial: selectRoofs.find((roof) => roof.selected),
})
setBasicInfo({
planNo: '' + (firstRes?.planNo ?? planNo),
roofSizeSet: '' + (firstRes?.roofSizeSet ?? 0),
roofAngleSet: '' + (firstRes?.roofAngleSet ?? 0),
})
// 데이터 동기화: 렌더링용 필드 기본값 보정
const normalizedRoofs = selectRoofs.map((roof) => ({
...roof,
width: roof.width ?? '',
length: roof.length ?? '',
hajebichi: roof.hajebichi ?? '',
pitch: roof.pitch ?? '',
angle: roof.angle ?? '',
}))
setCurrentRoofList(normalizedRoofs)
} catch (error) { } catch (error) {
console.error('Data fetching error:', error) console.error('Data fetching error:', error)
} }
@ -326,42 +323,19 @@ export function useRoofAllocationSetting(id) {
} }
const drawOriginRoofLine = () => { const drawOriginRoofLine = () => {
// outerLinePoints 배열을 이용하여 빨간색 Line 객체들 생성 const wallLines = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL)
if (outerLinePoints && outerLinePoints.length > 1) { /** 벽면 삭제 */
// 연속된 점들을 연결하여 라인 생성 wallLines.forEach((wallLine) => {
for (let i = 0; i < outerLinePoints.length - 1; i++) { wallLine.set({
const point1 = outerLinePoints[i] stroke: 'black',
const point2 = outerLinePoints[i + 1] strokeDashArray: [5, 2],
strokeWidth: 1,
const line = new fabric.Line([point1.x, point1.y, point2.x, point2.y], { selectable: false,
stroke: 'black', name: 'originRoofOuterLine',
strokeDashArray: [5, 2], visible: outlineDisplay,
strokeWidth: 1, })
selectable: false, })
name: 'originRoofOuterLine', canvas.renderAll()
visible: outlineDisplay,
})
canvas.add(line)
}
// 마지막 점과 첫 점을 연결하여 폐곡선 만들기
if (outerLinePoints.length > 2) {
const lastPoint = outerLinePoints[outerLinePoints.length - 1]
const firstPoint = outerLinePoints[0]
const closingLine = new fabric.Line([lastPoint.x, lastPoint.y, firstPoint.x, firstPoint.y], {
stroke: 'red',
strokeWidth: 2,
selectable: false,
name: 'originRoofOuterLine',
})
canvas.add(closingLine)
}
canvas.renderAll()
}
} }
/** /**
@ -467,7 +441,6 @@ export function useRoofAllocationSetting(id) {
const wallLines = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL) const wallLines = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL)
roofBases.forEach((roofBase) => { roofBases.forEach((roofBase) => {
try { try {
const roofEaveHelpLines = canvas.getObjects().filter((obj) => obj.lineName === 'eaveHelpLine' && obj.roofId === roofBase.id) const roofEaveHelpLines = canvas.getObjects().filter((obj) => obj.lineName === 'eaveHelpLine' && obj.roofId === roofBase.id)
if (roofEaveHelpLines.length > 0) { if (roofEaveHelpLines.length > 0) {
if (roofBase.lines) { if (roofBase.lines) {
@ -536,6 +509,34 @@ export function useRoofAllocationSetting(id) {
} }
} }
if (roofBase.adjustRoofLines.length > 0) {
const newRoofLines = []
let lineIndex = 1
roofBase.lines.forEach((line, idx) => {
const adjustLines = roofBase.adjustRoofLines.filter((adjustLine) => adjustLine.roofIdx === line.idx)
if (adjustLines.length === 0) {
line.idx = lineIndex
newRoofLines.push(line)
lineIndex++
} else {
adjustLines.forEach(({ point, roofIdx }) => {
const newLine = new QLine(point, {
idx: lineIndex,
selectable: false,
parentId: line.parentId,
parent: line.parent,
fontSize: line.fontSize,
stroke: line.stroke,
strokeWidth: line.strokeWidth,
attributes: line.attributes,
})
newRoofLines.push(newLine)
lineIndex++
})
}
})
roofBase.lines = newRoofLines
}
if (roofBase.separatePolygon.length > 0) { if (roofBase.separatePolygon.length > 0) {
splitPolygonWithSeparate(roofBase.separatePolygon) splitPolygonWithSeparate(roofBase.separatePolygon)
} else { } else {

View File

@ -7,7 +7,6 @@ import { useEvent } from '@/hooks/useEvent'
import { LINE_TYPE, POLYGON_TYPE } from '@/common/common' import { LINE_TYPE, POLYGON_TYPE } from '@/common/common'
import { useMode } from '@/hooks/useMode' import { useMode } from '@/hooks/useMode'
import { usePolygon } from '@/hooks/usePolygon' import { usePolygon } from '@/hooks/usePolygon'
import { outerLineFixState } from '@/store/outerLineAtom'
import { useSwal } from '@/hooks/useSwal' import { useSwal } from '@/hooks/useSwal'
import { usePopup } from '@/hooks/usePopup' import { usePopup } from '@/hooks/usePopup'
import { getChonByDegree } from '@/util/canvas-util' import { getChonByDegree } from '@/util/canvas-util'
@ -46,11 +45,9 @@ export function useRoofShapePassivitySetting(id) {
{ id: 3, name: getMessage('windage'), type: TYPES.SHED }, { id: 3, name: getMessage('windage'), type: TYPES.SHED },
] ]
const outerLineFix = useRecoilValue(outerLineFixState)
useEffect(() => { useEffect(() => {
const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
if (!outerLineFix || outerLines.length === 0) { if (!canvas.outerLineFix || outerLines.length === 0) {
swalFire({ text: getMessage('wall.line.not.found') }) swalFire({ text: getMessage('wall.line.not.found') })
closePopup(id) closePopup(id)
return return

View File

@ -81,6 +81,12 @@ export function useRoofShapeSetting(id) {
}, [jerkinHeadPitch]) }, [jerkinHeadPitch])
useEffect(() => { useEffect(() => {
const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
if (!canvas.outerLineFix || outerLines.length === 0) {
swalFire({ text: getMessage('wall.line.not.found') })
closePopup(id)
return
}
return () => { return () => {
if (!isFixRef.current) { if (!isFixRef.current) {
return return

View File

@ -815,7 +815,7 @@ export function usePlacementShapeDrawing(id) {
if (points.length === 0) { if (points.length === 0) {
return return
} }
enterCheck(e) // enterCheck(e)
const key = e.key const key = e.key
switch (key) { switch (key) {
case 'Enter': { case 'Enter': {

View File

@ -1,6 +1,6 @@
'use client' 'use client'
import { useRecoilValue, useResetRecoilState } from 'recoil' import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil'
import { import {
canvasSettingState, canvasSettingState,
canvasState, canvasState,
@ -50,7 +50,7 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
const { changeCorridorDimensionText } = useText() const { changeCorridorDimensionText } = useText()
const currentCanvasPlan = useRecoilValue(currentCanvasPlanState) const currentCanvasPlan = useRecoilValue(currentCanvasPlanState)
const { fetchSettings } = useCanvasSetting(false) const { fetchSettings } = useCanvasSetting(false)
const currentObject = useRecoilValue(currentObjectState) const [currentObject, setCurrentObject] = useRecoilState(currentObjectState)
const [popupId, setPopupId] = useState(uuidv4()) const [popupId, setPopupId] = useState(uuidv4())
const applySurfaceShape = (surfaceRefs, selectedType, id) => { const applySurfaceShape = (surfaceRefs, selectedType, id) => {
@ -755,6 +755,7 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
type: 'confirm', type: 'confirm',
confirmFn: () => { confirmFn: () => {
canvas.clear() canvas.clear()
delete canvas.outerLineFix
if (backgroundImage) { if (backgroundImage) {
fabric.Image.fromURL(`${backgroundImage.path}`, function (img) { fabric.Image.fromURL(`${backgroundImage.path}`, function (img) {
@ -1524,6 +1525,7 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
// 개별 객체들을 다시 캔버스에 추가하고 처리 // 개별 객체들을 다시 캔버스에 추가하고 처리
group.getObjects().forEach((obj) => { group.getObjects().forEach((obj) => {
canvas.add(obj) canvas.add(obj)
obj.dirty = true // 캐시 무효화
obj.setCoords() obj.setCoords()
// currentObject인 경우 추가 처리 // currentObject인 경우 추가 처리
@ -1534,6 +1536,8 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
// QPolygon 내부 구조 재구성 (선이 깨지는 문제 해결) // QPolygon 내부 구조 재구성 (선이 깨지는 문제 해결)
if (obj.type === 'QPolygon' && obj.lines) { if (obj.type === 'QPolygon' && obj.lines) {
obj.initLines() obj.initLines()
obj.dirty = true
obj.setCoords()
} }
obj.set({ obj.set({
@ -1544,6 +1548,8 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
// relatedObject인 경우에도 필요한 처리 // relatedObject인 경우에도 필요한 처리
if (obj.type === 'QPolygon' && obj.lines) { if (obj.type === 'QPolygon' && obj.lines) {
obj.initLines() obj.initLines()
obj.dirty = true
obj.setCoords()
} }
if (obj.type === 'group') { if (obj.type === 'group') {
// 회전 후의 points를 groupPoints로 업데이트 // 회전 후의 points를 groupPoints로 업데이트
@ -1551,24 +1557,30 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
obj.recalculateGroupPoints() obj.recalculateGroupPoints()
obj._objects?.forEach((obj) => { obj._objects?.forEach((innerObj) => {
obj.initLines() innerObj.initLines()
obj.fire('modified') innerObj.dirty = true
innerObj.setCoords()
innerObj.fire('modified')
}) })
} }
} }
}) })
currentObject.dirty = true
currentObject.setCoords()
currentObject.fire('modified') currentObject.fire('modified')
currentObject.fire('polygonMoved')
// 화살표와 선 다시 그리기 // 화살표와 선 다시 그리기
drawDirectionArrow(currentObject) drawDirectionArrow(currentObject)
setTimeout(() => { setTimeout(() => {
setPolygonLinesActualSize(currentObject) setPolygonLinesActualSize(currentObject)
changeSurfaceLineType(currentObject) changeSurfaceLineType(currentObject)
currentObject.dirty = true
currentObject.setCoords()
canvas.requestRenderAll()
setCurrentObject(currentObject)
}, 500) }, 500)
// currentObject를 다시 선택 상태로 설정
canvas.setActiveObject(currentObject)
canvas.renderAll()
} }
} }

View File

@ -99,6 +99,12 @@ export function useCircuitTrestle(executeEffect = false) {
// 지붕면 목록 // 지붕면 목록
const getRoofSurfaceList = () => { const getRoofSurfaceList = () => {
const roofSurfaceList = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE) const roofSurfaceList = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE)
roofSurfaceList.forEach((roofSurface) => {
const modules = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE && obj.surfaceId === roofSurface.id)
roofSurface.northModuleYn = modules.every((module) => module.moduleInfo.northModuleYn === 'Y') ? 'Y' : 'N'
})
roofSurfaceList.sort((a, b) => a.left - b.left || b.top - a.top) roofSurfaceList.sort((a, b) => a.left - b.left || b.top - a.top)
const result = roofSurfaceList const result = roofSurfaceList
@ -119,6 +125,7 @@ export function useCircuitTrestle(executeEffect = false) {
} }
}), }),
roofSurfaceNorthYn: obj.direction === 'north' ? 'Y' : 'N', roofSurfaceNorthYn: obj.direction === 'north' ? 'Y' : 'N',
roofSurfaceNorthModuleYn: obj.northModuleYn,
} }
}) })
.filter((surface) => surface.moduleList.length > 0) .filter((surface) => surface.moduleList.length > 0)
@ -139,11 +146,14 @@ export function useCircuitTrestle(executeEffect = false) {
let remaining = [...arr] let remaining = [...arr]
while (remaining.length > 0) { while (remaining.length > 0) {
const { roofSurface, roofSurfaceIncl } = remaining[0] const { roofSurface, roofSurfaceIncl, roofSurfaceNorthModuleYn } = remaining[0]
const key = `${roofSurface}|${roofSurfaceIncl}` const key = `${roofSurface}|${roofSurfaceIncl}|${roofSurfaceNorthModuleYn}`
// 해당 그룹 추출 // 해당 그룹 추출
const group = remaining.filter((item) => item.roofSurface === roofSurface && item.roofSurfaceIncl === roofSurfaceIncl) const group = remaining.filter(
(item) =>
item.roofSurface === roofSurface && item.roofSurfaceIncl === roofSurfaceIncl && item.roofSurfaceNorthModuleYn === roofSurfaceNorthModuleYn,
)
// 이미 처리했는지 체크 후 저장 // 이미 처리했는지 체크 후 저장
if (!seen.has(key)) { if (!seen.has(key)) {
@ -152,7 +162,14 @@ export function useCircuitTrestle(executeEffect = false) {
} }
// remaining에서 제거 // remaining에서 제거
remaining = remaining.filter((item) => !(item.roofSurface === roofSurface && item.roofSurfaceIncl === roofSurfaceIncl)) remaining = remaining.filter(
(item) =>
!(
item.roofSurface === roofSurface &&
item.roofSurfaceIncl === roofSurfaceIncl &&
item.roofSurfaceNorthModuleYn === roofSurfaceNorthModuleYn
),
)
} }
return result return result

View File

@ -10,9 +10,10 @@ import {
} from '@/store/canvasAtom' } from '@/store/canvasAtom'
import { QLine } from '@/components/fabric/QLine' import { QLine } from '@/components/fabric/QLine'
import { basicSettingState } from '@/store/settingAtom' import { basicSettingState } from '@/store/settingAtom'
import { calcLineActualSize } from '@/util/qpolygon-utils' import { calcLineActualSizeByLineLength } from '@/util/qpolygon-utils'
import { getDegreeByChon } from '@/util/canvas-util' import { getDegreeByChon } from '@/util/canvas-util'
import { useText } from '@/hooks/useText' import { useText } from '@/hooks/useText'
import { fontSelector } from '@/store/fontAtom'
export const useLine = () => { export const useLine = () => {
const canvas = useRecoilValue(canvasState) const canvas = useRecoilValue(canvasState)
@ -23,14 +24,15 @@ export const useLine = () => {
const angleUnit = useRecoilValue(showAngleUnitSelector) const angleUnit = useRecoilValue(showAngleUnitSelector)
const roofSizeSet = useRecoilValue(basicSettingState).roofSizeSet const roofSizeSet = useRecoilValue(basicSettingState).roofSizeSet
const globalPitch = useRecoilValue(globalPitchState) const globalPitch = useRecoilValue(globalPitchState)
const lengthText = useRecoilValue(fontSelector('lengthText'))
const { changeCorridorDimensionText } = useText() const { changeCorridorDimensionText } = useText()
const addLine = (points = [], options) => { const addLine = (points = [], options) => {
const line = new QLine(points, { const line = new QLine(points, {
...options, ...options,
fontSize: fontSize, fontSize: lengthText.fontSize.value,
fontFamily: fontFamily, fontFamily: lengthText.fontFamily.value,
}) })
if (line.length < 1) { if (line.length < 1) {
@ -179,7 +181,7 @@ export const useLine = () => {
if (isVertical) { if (isVertical) {
line.attributes = { line.attributes = {
...line.attributes, ...line.attributes,
actualSize: calcLineActualSize(line, getDegreeByChon(pitch)), actualSize: calcLineActualSizeByLineLength(lineLength, getDegreeByChon(pitch)),
} }
} else if (isDiagonal) { } else if (isDiagonal) {
const yLength = Math.abs(y2 - y1) * 10 const yLength = Math.abs(y2 - y1) * 10
@ -193,7 +195,7 @@ export const useLine = () => {
if (isHorizontal) { if (isHorizontal) {
line.attributes = { line.attributes = {
...line.attributes, ...line.attributes,
actualSize: calcLineActualSize(line, getDegreeByChon(pitch)), actualSize: calcLineActualSizeByLineLength(lineLength, getDegreeByChon(pitch)),
} }
} else if (isDiagonal) { } else if (isDiagonal) {
const xLength = Math.abs(x2 - x1) * 10 const xLength = Math.abs(x2 - x1) * 10

View File

@ -1380,6 +1380,8 @@ export const usePolygon = () => {
// 나눠서 중복 제거된 roof return // 나눠서 중복 제거된 roof return
let newRoofs = getSplitRoofsPoints(allLines) let newRoofs = getSplitRoofsPoints(allLines)
const createdRoofs = []
newRoofs = newRoofs.filter((roof) => roof.length !== 0) newRoofs = newRoofs.filter((roof) => roof.length !== 0)
newRoofs.forEach((roofPoint, index) => { newRoofs.forEach((roofPoint, index) => {
let defense, pitch let defense, pitch
@ -1623,8 +1625,8 @@ export const usePolygon = () => {
}) })
}) })
canvas.add(roof) // canvas.add(roof)
addLengthText(roof) createdRoofs.push(roof)
canvas.remove(polygon) canvas.remove(polygon)
canvas.renderAll() canvas.renderAll()
}) })
@ -1634,6 +1636,11 @@ export const usePolygon = () => {
auxiliaryLines.forEach((line) => { auxiliaryLines.forEach((line) => {
canvas.remove(line) canvas.remove(line)
}) })
createdRoofs.forEach((roof) => {
canvas.add(roof)
})
canvas.renderAll() canvas.renderAll()
canvas.discardActiveObject() canvas.discardActiveObject()
} }
@ -1968,6 +1975,22 @@ export const usePolygon = () => {
return return
} }
// createdRoofs들의 모든 lines를 확인해서 length값이 1이하인 차이가 있으면 통일 시킨다.
const allRoofs = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
const allRoofLines = allRoofs.flatMap((roof) => roof.lines)
for (let i = 0; i < allRoofLines.length; i++) {
for (let j = i + 1; j < allRoofLines.length; j++) {
const line1 = allRoofLines[i]
const line2 = allRoofLines[j]
const diff = Math.abs(line1.length - line2.length)
if (diff > 0 && diff <= 2) {
const minLength = Math.min(line1.length, line2.length)
line1.setLengthByValue(minLength * 10)
line2.setLengthByValue(minLength * 10)
}
}
}
polygon.lines.forEach((line) => { polygon.lines.forEach((line) => {
setActualSize(line, polygon.direction, +polygon.roofMaterial?.pitch) setActualSize(line, polygon.direction, +polygon.roofMaterial?.pitch)
}) })

View File

@ -656,7 +656,7 @@
"myinfo.message.password.error": "パスワードが間違っています。", "myinfo.message.password.error": "パスワードが間違っています。",
"login": "ログイン", "login": "ログイン",
"login.auto.page.text": "自動ログイン中です。", "login.auto.page.text": "自動ログイン中です。",
"login.fail": "계정이 없거나 비밀번호가 잘못되었습니다.", "login.fail": "アカウント未登録か、パスワードが正しくありません。",
"login.id.save": "ID保存", "login.id.save": "ID保存",
"login.id.placeholder": "IDを入力してください。", "login.id.placeholder": "IDを入力してください。",
"login.password.placeholder": "パスワードを入力してください。", "login.password.placeholder": "パスワードを入力してください。",

View File

@ -80,7 +80,7 @@ export const getCenterPoint = (point1, point2) => {
* @returns * @returns
*/ */
export const getDistance = (x1, y1, x2, y2) => { export const getDistance = (x1, y1, x2, y2) => {
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)).toFixed(0) return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)).toFixed(1)
} }
// polygon의 각 변에 해당 점과 점 사이의 거리를 나타내는 IText를 추가하는 함수 // polygon의 각 변에 해당 점과 점 사이의 거리를 나타내는 IText를 추가하는 함수

File diff suppressed because it is too large Load Diff