dev #930

Merged
ysCha merged 95 commits from dev into dev-deploy 2026-06-23 18:00:25 +09:00
38 changed files with 770 additions and 121 deletions
Showing only changes of commit 1d449737f6 - Show all commits

View File

@ -8,14 +8,17 @@ import { OUTER_LINE_TYPE } from '@/store/outerLineAtom'
import OuterLineWall from '@/components/floor-plan/modal/lineTypes/OuterLineWall'
import { useAuxiliaryDrawing } from '@/hooks/roofcover/useAuxiliaryDrawing'
import { usePopup } from '@/hooks/usePopup'
import { useEffect } from 'react'
import { useEffect, useState } from 'react'
import { useRecoilValue } from 'recoil'
import { canvasState } from '@/store/canvasAtom'
import Image from 'next/image'
export default function AuxiliaryDrawing({ id, pos = { x: 50, y: 230 } }) {
const canvas = useRecoilValue(canvasState)
const { getMessage } = useMessage()
const { closePopup } = usePopup()
const [useCalcPad, setUseCalcPad] = useState(false)
const disableKeypad = !useCalcPad
const types = [
{ id: 1, name: getMessage('straight.line'), type: OUTER_LINE_TYPE.OUTER_LINE },
@ -71,6 +74,7 @@ export default function AuxiliaryDrawing({ id, pos = { x: 50, y: 230 } }) {
length1Ref,
arrow1,
setArrow1,
disableKeypad,
}
const rightAngleProps = {
@ -84,6 +88,7 @@ export default function AuxiliaryDrawing({ id, pos = { x: 50, y: 230 } }) {
setArrow1,
arrow2,
setArrow2,
disableKeypad,
}
const doublePitchProps = {
@ -105,6 +110,7 @@ export default function AuxiliaryDrawing({ id, pos = { x: 50, y: 230 } }) {
setArrow2,
arrow1Ref,
arrow2Ref,
disableKeypad,
}
const angleProps = {
@ -114,6 +120,7 @@ export default function AuxiliaryDrawing({ id, pos = { x: 50, y: 230 } }) {
length1,
setLength1,
length1Ref,
disableKeypad,
}
const diagonalLineProps = {
@ -130,6 +137,7 @@ export default function AuxiliaryDrawing({ id, pos = { x: 50, y: 230 } }) {
setArrow1,
arrow2,
setArrow2,
disableKeypad,
}
const onClickButton = (button) => {
@ -160,7 +168,10 @@ export default function AuxiliaryDrawing({ id, pos = { x: 50, y: 230 } }) {
{buttonAct === 4 && <Angle props={angleProps} />}
{buttonAct === 5 && <Diagonal props={diagonalLineProps} />}
</div>
<div className="grid-btn-wrap">
<div className="grid-btn-wrap" style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: '5px' }}>
<button style={{ width: 'auto' }} type="button" onClick={() => setUseCalcPad((prev) => !prev)}>
<Image src={useCalcPad ? '/static/images/common/Icon_ON_Black.png' : '/static/images/common/Icon_OFF_Black.png'} alt="toggle" width={34} height={34} />
</button>
<button className="btn-frame modal mr5" onClick={handleRollback}>
{getMessage('modal.cover.outline.rollback')}
</button>

View File

@ -18,6 +18,7 @@ import {
currentCanvasPlanState,
isManualModuleLayoutSetupState,
isManualModuleSetupState,
moduleSetupOptionState,
toggleManualSetupModeState,
} from '@/store/canvasAtom'
import { loginUserStore } from '@/store/commonAtom'
@ -49,6 +50,7 @@ export default function BasicSetting({ id, pos = { x: 50, y: 230 } }) {
const [checkedModules, setCheckedModules] = useRecoilState(checkedModuleState)
const [roofs, setRoofs] = useState(addedRoofs)
const [manualSetupMode, setManualSetupMode] = useRecoilState(toggleManualSetupModeState)
const [moduleSetupOption, setModuleSetupOption] = useRecoilState(moduleSetupOptionState)
const [layoutSetup, setLayoutSetup] = useState([{}])
const {
selectedModules,
@ -218,8 +220,12 @@ export default function BasicSetting({ id, pos = { x: 50, y: 230 } }) {
}
const handleManualModuleSetup = () => {
setManualSetupMode(`manualSetup_${!isManualModuleSetup}`)
setIsManualModuleSetup(!isManualModuleSetup)
const nextManual = !isManualModuleSetup
setManualSetupMode(`manualSetup_${nextManual}`)
setIsManualModuleSetup(nextManual)
if (nextManual) {
setModuleSetupOption((prev) => ({ ...prev, isChidori: true }))
}
}
const handleManualModuleLayoutSetup = () => {

View File

@ -428,7 +428,7 @@ const Trestle = forwardRef((props, ref) => {
const newCvrYn = constructionList[index].cvrYn
const newCvrChecked = newCvrYn === 'Y'
const newSnowGdPossYn = constructionList[index].snowGdPossYn
const newSnowGdChecked = newSnowGdPossYn === 'Y'
const newSnowGdChecked = false
setCvrYn(newCvrYn)
setSnowGdPossYn(newSnowGdPossYn)

View File

@ -6,10 +6,14 @@ import WallMerge from '@/components/floor-plan/modal/eavesGable/type/WallMerge'
import Shed from '@/components/floor-plan/modal/eavesGable/type/Shed'
import { useEavesGableEdit } from '@/hooks/roofcover/useEavesGableEdit'
import { usePopup } from '@/hooks/usePopup'
import { useState } from 'react'
import Image from 'next/image'
export default function EavesGableEdit({ id, pos = { x: 50, y: 230 } }) {
const { getMessage } = useMessage()
const { closePopup } = usePopup()
const [useCalcPad, setUseCalcPad] = useState(false)
const disableKeypad = !useCalcPad
const { type, setType, buttonMenu, TYPES, pitchRef, offsetRef, widthRef, radioTypeRef, pitchText } = useEavesGableEdit(id)
const eavesProps = {
@ -18,6 +22,7 @@ export default function EavesGableEdit({ id, pos = { x: 50, y: 230 } }) {
widthRef,
radioTypeRef,
pitchText,
disableKeypad,
}
const gableProps = {
@ -26,15 +31,18 @@ export default function EavesGableEdit({ id, pos = { x: 50, y: 230 } }) {
widthRef,
radioTypeRef,
pitchText,
disableKeypad,
}
const wallMergeProps = {
offsetRef,
radioTypeRef,
disableKeypad,
}
const shedProps = {
offsetRef,
disableKeypad,
}
return (
@ -48,8 +56,13 @@ export default function EavesGableEdit({ id, pos = { x: 50, y: 230 } }) {
</button>
))}
</div>
<div className="properties-setting-wrap outer">
<div className="setting-tit">{getMessage('setting')}</div>
<div className="properties-setting-wrap outer" style={{margin: '10px'}}>
<div className="setting-tit" style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<span>{getMessage('setting')}</span>
<button style={{ width: 'auto' }} type="button" onClick={() => setUseCalcPad((prev) => !prev)}>
<Image src={useCalcPad ? '/static/images/common/Icon_ON_Black.png' : '/static/images/common/Icon_OFF_Black.png'} alt="toggle" width={34} height={34} />
</button>
</div>
{type === TYPES.EAVES && <Eaves {...eavesProps} />}
{type === TYPES.GABLE && <Gable {...gableProps} />}
{type === TYPES.WALL_MERGE && <WallMerge {...wallMergeProps} />}

View File

@ -6,7 +6,7 @@ import { ANGLE_TYPE, currentAngleTypeSelector } from '@/store/canvasAtom'
import { normalizeDecimalLimit, normalizeDigits } from '@/util/input-utils'
import { CalculatorInput } from '@/components/common/input/CalcInput'
export default function Eaves({ pitchRef, offsetRef, widthRef, radioTypeRef, pitchText }) {
export default function Eaves({ pitchRef, offsetRef, widthRef, radioTypeRef, pitchText, disableKeypad }) {
const { getMessage } = useMessage()
const [type, setType] = useState('1')
const onChange = (e) => {
@ -43,6 +43,7 @@ export default function Eaves({ pitchRef, offsetRef, widthRef, radioTypeRef, pit
onChange={(value) => {
if (pitchRef?.current) pitchRef.current.value = value
}}
disableKeypad={disableKeypad}
options={{
allowNegative: false,
allowDecimal: true //(index !== 0),
@ -77,6 +78,7 @@ export default function Eaves({ pitchRef, offsetRef, widthRef, radioTypeRef, pit
onChange={(value) => {
if (offsetRef?.current) offsetRef.current.value = value
}}
disableKeypad={disableKeypad}
options={{
allowNegative: false,
allowDecimal: false //(index !== 0),
@ -144,6 +146,7 @@ export default function Eaves({ pitchRef, offsetRef, widthRef, radioTypeRef, pit
onChange={(value) => {
if (widthRef?.current) widthRef.current.value = value
}}
disableKeypad={disableKeypad}
options={{
allowNegative: false,
allowDecimal: false //(index !== 0),

View File

@ -5,7 +5,7 @@ import { useRecoilValue } from 'recoil'
import { ANGLE_TYPE, currentAngleTypeSelector } from '@/store/canvasAtom'
import { CalculatorInput } from '@/components/common/input/CalcInput'
export default function Gable({ pitchRef, offsetRef, widthRef, radioTypeRef, pitchText }) {
export default function Gable({ pitchRef, offsetRef, widthRef, radioTypeRef, pitchText, disableKeypad }) {
const { getMessage } = useMessage()
const [type, setType] = useState('1')
const onChange = (e) => {
@ -30,6 +30,7 @@ export default function Gable({ pitchRef, offsetRef, widthRef, radioTypeRef, pit
className="input-origin block"
ref={offsetRef}
value={300}
disableKeypad={disableKeypad}
options={{
allowNegative: false,
allowDecimal: false //(index !== 0),
@ -96,6 +97,7 @@ export default function Gable({ pitchRef, offsetRef, widthRef, radioTypeRef, pit
onChange={(value) => {
if (pitchRef?.current) pitchRef.current.value = value
}}
disableKeypad={disableKeypad}
options={{
allowNegative: false,
allowDecimal: true //(index !== 0),
@ -129,6 +131,7 @@ export default function Gable({ pitchRef, offsetRef, widthRef, radioTypeRef, pit
ref={widthRef}
value={800}
readOnly={type === '1'}
disableKeypad={disableKeypad}
options={{
allowNegative: false,
allowDecimal: false //(index !== 0),

View File

@ -1,7 +1,7 @@
import { useMessage } from '@/hooks/useMessage'
import { CalculatorInput } from '@/components/common/input/CalcInput'
export default function Shed({ offsetRef }) {
export default function Shed({ offsetRef, disableKeypad }) {
const { getMessage } = useMessage()
return (
<>
@ -19,6 +19,7 @@ export default function Shed({ offsetRef }) {
className="input-origin block"
ref={offsetRef}
value={300}
disableKeypad={disableKeypad}
options={{
allowNegative: false,
allowDecimal: false //(index !== 0),

View File

@ -3,7 +3,7 @@ import Image from 'next/image'
import { useState } from 'react'
import { CalculatorInput } from '@/components/common/input/CalcInput'
export default function WallMerge({ offsetRef, radioTypeRef }) {
export default function WallMerge({ offsetRef, radioTypeRef, disableKeypad }) {
const { getMessage } = useMessage()
const [type, setType] = useState('1')
const onChange = (e) => {
@ -61,6 +61,7 @@ export default function WallMerge({ offsetRef, radioTypeRef }) {
ref={offsetRef}
value={300}
readOnly={type === '1'}
disableKeypad={disableKeypad}
options={{
allowNegative: false,
allowDecimal: false //(index !== 0),

View File

@ -3,16 +3,22 @@ import WithDraggable from '@/components/common/draggable/WithDraggable'
import FlowLine from '@/components/floor-plan/modal/movement/type/FlowLine'
import Updown from '@/components/floor-plan/modal/movement/type/Updown'
import { useMovementSetting } from '@/hooks/roofcover/useMovementSetting'
import { useState } from 'react'
import Image from 'next/image'
export default function MovementSetting({ id, pos = { x: 50, y: 230 } }) {
const { getMessage } = useMessage()
const { TYPE, closePopup, buttonType, type, setType, FLOW_LINE_REF, UP_DOWN_REF, handleSave } = useMovementSetting(id)
const [useCalcPad, setUseCalcPad] = useState(false)
const disableKeypad = !useCalcPad
const flowLineProps = {
FLOW_LINE_REF,
disableKeypad,
}
const updownProps = {
UP_DOWN_REF,
disableKeypad,
}
return (
@ -30,7 +36,10 @@ export default function MovementSetting({ id, pos = { x: 50, y: 230 } }) {
{type === TYPE.FLOW_LINE && <FlowLine {...flowLineProps} />}
{type === TYPE.UP_DOWN && <Updown {...updownProps} />}
</div>
<div className="grid-btn-wrap">
<div className="grid-btn-wrap" style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: '5px' }}>
<button style={{ width: 'auto' }} type="button" onClick={() => setUseCalcPad((prev) => !prev)}>
<Image src={useCalcPad ? '/static/images/common/Icon_ON_Black.png' : '/static/images/common/Icon_OFF_Black.png'} alt="toggle" width={34} height={34} />
</button>
<button className="btn-frame modal act" onClick={handleSave}>
{getMessage('modal.common.save')}
</button>

View File

@ -9,7 +9,7 @@ const FLOW_LINE_TYPE = {
UP_RIGHT: 'upRight',
}
export default function FlowLine({ FLOW_LINE_REF }) {
export default function FlowLine({ FLOW_LINE_REF, disableKeypad }) {
const { getMessage } = useMessage()
const [type, setType] = useState(FLOW_LINE_TYPE.DOWN_LEFT)
const [filledInput, setFilledInput] = useState('')
@ -87,6 +87,7 @@ export default function FlowLine({ FLOW_LINE_REF }) {
value={filledInput}
onFocus={handleFocus}
onChange={(value)=>{setFilledInput(value)}}
disableKeypad={disableKeypad}
options={{
allowNegative: false,
allowDecimal: false

View File

@ -10,7 +10,7 @@ const UP_DOWN_TYPE = {
DOWN: 'down',
}
export default function Updown({ UP_DOWN_REF }) {
export default function Updown({ UP_DOWN_REF, disableKeypad }) {
const { getMessage } = useMessage()
const [type, setType] = useState(UP_DOWN_TYPE.UP)
const [filledInput, setFilledInput] = useState('100')
@ -87,6 +87,7 @@ export default function Updown({ UP_DOWN_REF }) {
value={filledInput}
onFocus={handleFocus}
onChange={(value)=>{setFilledInput(value)}}
disableKeypad={disableKeypad}
options={{
allowNegative: false,
allowDecimal: false

View File

@ -266,14 +266,15 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla
}, { skipSideEffects: true })
const roofs = canvas.getObjects().filter((obj) => obj.roofMaterial?.index === 0)
const firstRoof = roofs[0]
roofs.forEach((roof) => {
if (firstRoof) {
/** 모양 패턴 설정 */
setSurfaceShapePattern(roof, roofDisplay.column, false, { ...roofInfo })
roof.roofMaterial = { ...roofInfo }
drawDirectionArrow(roof)
setPolygonLinesActualSize(roof, true)
})
setSurfaceShapePattern(firstRoof, roofDisplay.column, false, { ...roofInfo })
firstRoof.roofMaterial = { ...roofInfo }
drawDirectionArrow(firstRoof)
setPolygonLinesActualSize(firstRoof, true)
}
canvas.renderAll()
/** 지붕면 존재 여부에 따라 메뉴 설정 */

View File

@ -5,27 +5,34 @@ import Shed from '@/components/floor-plan/modal/roofShape/passivity/Shed'
import { useMessage } from '@/hooks/useMessage'
import { useRoofShapePassivitySetting } from '@/hooks/roofcover/useRoofShapePassivitySetting'
import { usePopup } from '@/hooks/usePopup'
import { useState } from 'react'
import Image from 'next/image'
export default function RoofShapePassivitySetting({ id, pos = { x: 50, y: 230 } }) {
const { handleSave, handleConfirm, handleRollback, buttons, type, setType, TYPES, offsetRef, pitchRef, pitchText } =
useRoofShapePassivitySetting(id)
const { getMessage } = useMessage()
const { closePopup } = usePopup()
const [useCalcPad, setUseCalcPad] = useState(false)
const disableKeypad = !useCalcPad
const eavesProps = {
offsetRef,
pitchRef,
pitchText,
disableKeypad,
}
const gableProps = {
offsetRef,
pitchRef,
pitchText,
disableKeypad,
}
const shedProps = {
offsetRef,
disableKeypad,
}
return (
@ -55,7 +62,10 @@ export default function RoofShapePassivitySetting({ id, pos = { x: 50, y: 230 }
</button>
</div>
</div>
<div className="grid-btn-wrap">
<div className="grid-btn-wrap" style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: '5px' }}>
<button style={{ width: 'auto' }} type="button" onClick={() => setUseCalcPad((prev) => !prev)}>
<Image src={useCalcPad ? '/static/images/common/Icon_ON_Black.png' : '/static/images/common/Icon_OFF_Black.png'} alt="toggle" width={34} height={34} />
</button>
<button className="btn-frame modal act" onClick={() => handleSave(id)}>
{getMessage('common.setting.finish')}
</button>

View File

@ -5,7 +5,7 @@ import { selectedRoofMaterialSelector } from '@/store/settingAtom'
import { useEffect } from 'react'
import { CalculatorInput } from '@/components/common/input/CalcInput'
export default function Eaves({ offsetRef, pitchRef, pitchText }) {
export default function Eaves({ offsetRef, pitchRef, pitchText, disableKeypad }) {
const { getMessage } = useMessage()
const currentAngleType = useRecoilValue(currentAngleTypeSelector)
const selectedRoofMaterial = useRecoilValue(selectedRoofMaterialSelector)
@ -30,6 +30,7 @@ export default function Eaves({ offsetRef, pitchRef, pitchText }) {
className="input-origin block"
ref={pitchRef}
value={currentAngleType === ANGLE_TYPE.SLOPE ? selectedRoofMaterial.pitch : selectedRoofMaterial.angle}
disableKeypad={disableKeypad}
options={{
allowNegative: false,
allowDecimal: true //(index !== 0),
@ -52,6 +53,7 @@ export default function Eaves({ offsetRef, pitchRef, pitchText }) {
value={offsetRef.current?.value ?? 500} // Set default value to 500
ref={offsetRef}
onChange={() => {}}
disableKeypad={disableKeypad}
options={{
allowNegative: false,
allowDecimal: false

View File

@ -3,7 +3,7 @@ import { useRecoilValue } from 'recoil'
import { currentAngleTypeSelector } from '@/store/canvasAtom'
import { CalculatorInput } from '@/components/common/input/CalcInput'
export default function Gable({ offsetRef }) {
export default function Gable({ offsetRef, disableKeypad }) {
const { getMessage } = useMessage()
const currentAngleType = useRecoilValue(currentAngleTypeSelector)
return (
@ -22,6 +22,7 @@ export default function Gable({ offsetRef }) {
value={offsetRef.current?.value ?? 300} // Set default value to 500
ref={offsetRef}
onChange={() => {}}
disableKeypad={disableKeypad}
options={{
allowNegative: false,
allowDecimal: false

View File

@ -1,7 +1,7 @@
import { useMessage } from '@/hooks/useMessage'
import { CalculatorInput } from '@/components/common/input/CalcInput'
export default function Shed({ offsetRef }) {
export default function Shed({ offsetRef, disableKeypad }) {
const { getMessage } = useMessage()
return (
<>
@ -19,6 +19,7 @@ export default function Shed({ offsetRef }) {
value={offsetRef.current?.value ?? 300} // Set default value to 500
ref={offsetRef}
onChange={() => {}}
disableKeypad={disableKeypad}
options={{
allowNegative: false,
allowDecimal: false

View File

@ -4,10 +4,14 @@ import WallLine from '@/components/floor-plan/modal/wallLineOffset/type/WallLine
import Offset from '@/components/floor-plan/modal/wallLineOffset/type/Offset'
import { useWallLineOffsetSetting } from '@/hooks/roofcover/useWallLineOffsetSetting'
import { usePopup } from '@/hooks/usePopup'
import { useState } from 'react'
import Image from 'next/image'
export default function WallLineOffsetSetting({ id, pos = { x: 50, y: 230 } }) {
const { getMessage } = useMessage()
const { closePopup } = usePopup()
const [useCalcPad, setUseCalcPad] = useState(false)
const disableKeypad = !useCalcPad
const {
type,
setType,
@ -30,12 +34,14 @@ export default function WallLineOffsetSetting({ id, pos = { x: 50, y: 230 } }) {
arrow2Ref,
radioTypeRef,
currentWallLineRef,
disableKeypad,
}
const offsetProps = {
length1Ref,
arrow1Ref,
currentWallLineRef,
disableKeypad,
}
return (
@ -54,7 +60,10 @@ export default function WallLineOffsetSetting({ id, pos = { x: 50, y: 230 } }) {
{type === TYPES.WALL_LINE_EDIT && <WallLine ref={wallLineEditRef} {...wallLineProps} />}
{type === TYPES.OFFSET && <Offset {...offsetProps} />}
</div>
<div className="grid-btn-wrap">
<div className="grid-btn-wrap" style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: '5px' }}>
<button style={{ width: 'auto' }} type="button" onClick={() => setUseCalcPad((prev) => !prev)}>
<Image src={useCalcPad ? '/static/images/common/Icon_ON_Black.png' : '/static/images/common/Icon_OFF_Black.png'} alt="toggle" width={34} height={34} />
</button>
<button className="btn-frame modal act" onClick={handleSave}>
{getMessage('modal.common.save')}
</button>

View File

@ -3,7 +3,7 @@ import { useEffect, useState } from 'react'
import { useEvent } from '@/hooks/useEvent'
import { CalculatorInput } from '@/components/common/input/CalcInput'
export default function Offset({ length1Ref, arrow1Ref, currentWallLineRef }) {
export default function Offset({ length1Ref, arrow1Ref, currentWallLineRef, disableKeypad }) {
const { getMessage } = useMessage()
const { addDocumentEventListener, initEvent } = useEvent()
// const { addDocumentEventListener, initEvent } = useContext(EventContext)
@ -84,6 +84,7 @@ export default function Offset({ length1Ref, arrow1Ref, currentWallLineRef }) {
value={length1Ref.current?.value ?? 0}
ref={length1Ref}
onChange={() => {}}
disableKeypad={disableKeypad}
options={{
allowNegative: false,
allowDecimal: false

View File

@ -3,7 +3,7 @@ import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'
import { useEvent } from '@/hooks/useEvent'
import { CalculatorInput } from '@/components/common/input/CalcInput'
export default forwardRef(function WallLine({ length1Ref, length2Ref, arrow1Ref, arrow2Ref, radioTypeRef, currentWallLineRef }, ref) {
export default forwardRef(function WallLine({ length1Ref, length2Ref, arrow1Ref, arrow2Ref, radioTypeRef, currentWallLineRef, disableKeypad }, ref) {
const { getMessage } = useMessage()
const { addDocumentEventListener, initEvent } = useEvent()
// const { addDocumentEventListener, initEvent } = useContext(EventContext)
@ -57,6 +57,7 @@ export default forwardRef(function WallLine({ length1Ref, length2Ref, arrow1Ref,
ref={length1Ref}
onChange={() => {}}
readOnly={type !== 1}
disableKeypad={disableKeypad}
options={{
allowNegative: false,
allowDecimal: false
@ -105,6 +106,7 @@ export default forwardRef(function WallLine({ length1Ref, length2Ref, arrow1Ref,
ref={length2Ref}
onChange={() => {}}
readOnly={type !== 2}
disableKeypad={disableKeypad}
options={{
allowNegative: false,
allowDecimal: false

View File

@ -1046,6 +1046,33 @@ export default function StuffDetail() {
form.setValue('otherSaleStoreId', info.saleStoreId)
form.setValue('otherSaleStoreName', info.saleStoreName)
form.setValue('otherSaleStoreLevel', info.saleStoreLevel)
get({ url: `/api/object/saleStore/${info.saleStoreId}/firstAgent` }).then((res) => {
if (res?.firstAgentId) {
const firstAgent = { saleStoreId: res.firstAgentId, saleStoreName: res.firstAgentName }
setSaleStoreList((prev) => {
const exists = prev.some((s) => s.saleStoreId === res.firstAgentId)
return exists ? prev : [...prev, firstAgent]
})
setShowSaleStoreList((prev) => {
const exists = prev.some((s) => s.saleStoreId === res.firstAgentId)
return exists ? prev : [...prev, firstAgent]
})
setSelOptions(res.firstAgentId)
form.setValue('saleStoreId', res.firstAgentId)
form.setValue('saleStoreName', res.firstAgentName)
form.setValue('saleStoreLevel', '1')
} else {
setSelOptions('')
form.setValue('saleStoreId', '')
form.setValue('saleStoreName', '')
form.setValue('saleStoreLevel', '')
}
}).catch(() => {
setSelOptions('')
form.setValue('saleStoreId', '')
form.setValue('saleStoreName', '')
form.setValue('saleStoreLevel', '')
})
}
}

View File

@ -1166,7 +1166,7 @@ export default function StuffSearchCondition() {
<th>{getMessage('stuff.search.schSelSaleStoreId')}</th>
<td colSpan={5}>
<div className="form-flex-wrap">
<div className="select-wrap mr5" style={{ flex: 1 }}>
<div className="select-wrap mr5" style={{ flex: 1, display: session?.storeId !== 'T01' && session?.storeLvl !== '1' ? 'none' : undefined }}>
{session?.storeId === 'T01' && (
<Select
id="long-value-select1"
@ -1235,7 +1235,7 @@ export default function StuffSearchCondition() {
isClearable={false}
/>
)}
{session?.storeId !== 'T01' && session?.storeLvl !== '1' && (
{false && session?.storeId !== 'T01' && session?.storeLvl !== '1' && (
<Select
id="long-value-select1"
instanceId="long-value-select1"
@ -1267,7 +1267,7 @@ export default function StuffSearchCondition() {
/>
)}
</div>
<div className="select-wrap mr10" style={{ flex: 1 }}>
<div className="select-wrap mr10" style={{ flex: session?.storeId !== 'T01' && session?.storeLvl !== '1' ? '0 0 50%' : 1 }}>
<Select
id="long-value-select2"
instanceId="long-value-select2"

View File

@ -11,7 +11,7 @@ import { isObjectNotEmpty, queryStringFormatter } from '@/util/common-utils'
import QPagination from '@/components/common/pagination/QPagination'
import { useSwal } from '@/hooks/useSwal'
import { QcastContext } from '@/app/QcastProvider'
import { sanitizeDecimalInputEvent } from '@/util/input-utils'
import { sanitizeDecimalInputEvent, sanitizeIntegerInputEvent } from '@/util/input-utils'
export default function PlanRequestPop(props) {
const [pageNo, setPageNo] = useState(1) //
@ -212,6 +212,7 @@ export default function PlanRequestPop(props) {
//
const getSelectedRowdata = (data) => {
if (isNotEmptyArray(data)) {
console.log('planReqObject fields:', data[0])
setPlanReqObject(data[0])
} else {
setPlanReqObject({})

View File

@ -47,12 +47,12 @@ export function useCanvasConfigInitialize() {
offsetTexts.forEach((obj) => {
let text = `${!obj.originText ? '' : obj.originText}`
if (!obj.onlyOffset) {
text = text + `-∠${getDegreeByChon(obj.pitch)}${angleUnit}`
text = text + `-∠${obj.roofAngle ?? getDegreeByChon(obj.pitch)}${angleUnit}`
}
obj.set({ text: text })
})
flowTexts.forEach((obj) => {
obj.set({ text: `${!obj.originText ? '' : obj.originText + '-'}${getDegreeByChon(obj.pitch)}${pitchText}` })
obj.set({ text: `${!obj.originText ? '' : obj.originText + '-'}${obj.roofAngle ?? getDegreeByChon(obj.pitch)}${pitchText}` })
})
}

View File

@ -7,7 +7,10 @@ import { atom, useRecoilState, useRecoilValue } from 'recoil'
// API 요청을 저장할 모듈 레벨 변수 (Hook 외부)
let roofMaterialPromise = null;
// 전역 Promise 캐시
const roofMaterialPromiseCache = new Map()
let cacheKey = 'default'
/**
* 마스터 컨트롤러
* @returns
@ -22,24 +25,117 @@ export function useMasterController() {
* 지붕재 목록 조회
* @returns
*/
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
const getRoofMaterialList = async () => {
// 1. 이미 진행 중이거나 완료된 Promise가 있으면 그것을 반환
if (roofMaterialPromise) {
return roofMaterialPromise;
if (roofMaterialPromiseCache.has(cacheKey)) {
console.log('🔄 getRoofMaterialList: Returning cached promise')
return roofMaterialPromiseCache.get(cacheKey)
}
// 2. 처음 호출될 때 Promise를 생성하여 변수에 할당
roofMaterialPromise = get({ url: '/api/v1/master/getRoofMaterialList' })
.then((res) => {
return res;
})
.catch((error) => {
// 에러 발생 시 다음 호출을 위해 초기화
roofMaterialPromise = null;
throw error;
});
console.log('🚀 getRoofMaterialList: Creating new promise')
return roofMaterialPromise;
const MAX_RETRY = 15 // 15번으로 증가 (일본 지역 대응)
const RETRY_DELAY = 2000 // 2초 간격
const MAX_BACKOFF = 10000 // 최대 10초까지 지수 백오프
const fetchWithRetry = async () => {
let lastError = null
let retryCount = 0
for (let attempt = 0; attempt < MAX_RETRY; attempt++) {
try {
// 지수 백오프 적용: 2초, 4초, 8초, ... 최대 10초
const backoffDelay = Math.min(RETRY_DELAY * Math.pow(2, attempt), MAX_BACKOFF)
if (attempt > 0) {
console.log(`⏳ Waiting ${backoffDelay/1000}s before retry ${attempt + 1}/${MAX_RETRY}`)
await delay(backoffDelay)
}
console.log(`🌐 API Request ${attempt + 1}/${MAX_RETRY}: /api/v1/master/getRoofMaterialList`)
const res = await get({ url: '/api/v1/master/getRoofMaterialList' })
console.log('✅ getRoofMaterialList: Success on attempt', attempt + 1)
// 재시도 중이었으면 성공 메시지 표시
if (retryCount > 0) {
swalFire({
icon: 'success',
title: getMessage('server.restored.title'),
text: `${getMessage('server.restored.text')} (${retryCount}${getMessage('server.retries')})`,
timer: 3000, // 메시지 표시 시간 증가
showConfirmButton: false
})
}
return res
} catch (error) {
lastError = error
retryCount++
// 500 계열만 기다렸다가 재시도
const status = error?.response?.status
if (status && status >= 500) {
console.log(`⚠️ getRoofMaterialList: 500 error on attempt ${attempt + 1}, retrying...`)
// 첫 번째 500 에러에만 메시지 표시
if (attempt === 0) {
swalFire({
icon: 'warning',
title: getMessage('server.error.title'),
text: getMessage('server.error.text'),
timer: 4000, // 경고 메시지 시간 증가
showConfirmButton: false
})
}
continue
}
// 네트워크 에러(ECONNRESET, ETIMEDOUT 등)도 재시도
if (error.code === 'ECONNRESET' || error.code === 'ETIMEDOUT' || error.code === 'ENOTFOUND') {
console.log(`🌐 Network error on attempt ${attempt + 1}:`, error.code)
if (attempt === 0) {
swalFire({
icon: 'warning',
title: getMessage('network.error.title'),
text: getMessage('network.error.text'),
timer: 4000,
showConfirmButton: false
})
}
continue
}
// 500이나 네트워크 에러가 아니면 즉시 실패
console.log(`❌ getRoofMaterialList: Non-retryable error on attempt ${attempt + 1}:`, error)
throw error
}
}
// 모든 재시도 실패 시
console.log('💥 getRoofMaterialList: All retries failed')
swalFire({
icon: 'error',
title: getMessage('server.failed.title'),
text: `${getMessage('server.failed.text')} (${MAX_RETRY}${getMessage('server.retries')})`,
confirmButtonText: getMessage('common.ok')
})
throw lastError
}
const promise = fetchWithRetry()
.finally(() => {
// 성공/실패 모두 끝나면 캐시에서 제거
console.log('🏁 getRoofMaterialList: Promise resolved, removing from cache')
roofMaterialPromiseCache.delete(cacheKey)
})
// 캐시에 Promise 저장
roofMaterialPromiseCache.set(cacheKey, promise)
return promise
}
/**
@ -263,6 +359,10 @@ export function useMasterController() {
})
}
const quotationErrSave = async (params) => {
return await post({ url: '/api/v1/master/quotationErrSave', data: params })
}
return {
getRoofMaterialList,
getModuleTypeItemList,
@ -278,5 +378,6 @@ export function useMasterController() {
updateObjectDate,
getQuotationItem,
getPcsConnOptionItemList,
quotationErrSave,
}
}

View File

@ -17,7 +17,7 @@ import {
import { calculateVisibleModuleHeight, getDegreeByChon, polygonToTurfPolygon, rectToPolygon, toFixedWithoutRounding } from '@/util/canvas-util'
import '@/util/fabric-extensions' // fabric 객체들에 getCurrentPoints 메서드 추가
import { basicSettingState, roofDisplaySelector } from '@/store/settingAtom'
import offsetPolygon, { calculateAngle, cleanSelfIntersectingPolygon, createLinesFromPolygon } from '@/util/qpolygon-utils'
import offsetPolygon, { calculateAngle, cleanSelfIntersectingPolygon, clipOffsetToOriginal, createLinesFromPolygon } from '@/util/qpolygon-utils'
import { QPolygon } from '@/components/fabric/QPolygon'
import { useEvent } from '@/hooks/useEvent'
import { BATCH_TYPE, LINE_TYPE, MODULE_SETUP_TYPE, POLYGON_TYPE } from '@/common/common'
@ -56,7 +56,7 @@ export function useModuleBasicSetting(tabNum) {
const setCurrentObject = useSetRecoilState(currentObjectState)
const { setModuleStatisticsData } = useCircuitTrestle()
const { createRoofPolygon, createMarginPolygon, createPaddingPolygon } = useMode()
const { createRoofPolygon, createMarginPolygon, createPaddingPolygon, removeShortEdges } = useMode()
const moduleSetupOption = useRecoilValue(moduleSetupOptionState)
const setManualSetupMode = useSetRecoilState(toggleManualSetupModeState)
@ -390,10 +390,14 @@ export function useModuleBasicSetting(tabNum) {
setSurfaceShapePattern(roof, roofDisplay.column, true, roof.roofMaterial) //패턴 변경
// let offsetPoints = createPaddingPolygon(createRoofPolygon(roof.points), roof.lines).vertices //안쪽 offset
let offsetPoints = null
const polygon = createRoofPolygon(roof.getCurrentPoints())
// 겹치는 선(같은 방향) 중 짧은 edge를 제거하여 폴리곤 단순화
const cleaned = removeShortEdges(roof.getCurrentPoints(), roof.lines)
const polygon = createRoofPolygon(cleaned.vertices)
const cleanedLines = cleaned.lines
const originPolygon = new QPolygon(roof.getCurrentPoints(), { fontSize: 0 })
let result = createPaddingPolygon(polygon, roof.lines).vertices
let result = createPaddingPolygon(polygon, cleanedLines, 0, true).vertices
//margin polygon 의 point가 기준 polygon의 밖에 있는지 판단한다.
const allPointsOutside = result.every((point) => !originPolygon.inPolygon(point))
@ -406,12 +410,14 @@ export function useModuleBasicSetting(tabNum) {
} else {
//육지붕이 아닐때
if (allPointsOutside) {
offsetPoints = createMarginPolygon(polygon, roof.lines).vertices
offsetPoints = createMarginPolygon(polygon, cleanedLines, 0, true).vertices
} else {
offsetPoints = createPaddingPolygon(polygon, roof.lines).vertices
offsetPoints = createPaddingPolygon(polygon, cleanedLines, 0, true).vertices
}
// 자기교차(꼬임) 제거
offsetPoints = cleanSelfIntersectingPolygon(offsetPoints)
// 둔각 꼭짓점에서 offset 선이 지붕 바깥으로 튀어나가는 문제 수정
offsetPoints = clipOffsetToOriginal(offsetPoints, roof.getCurrentPoints())
}
//모듈설치영역?? 생성

View File

@ -310,7 +310,7 @@ export function useModuleTabContents({ tabIndex, addRoof, setAddedRoofs, roofTab
} else {
// 최초 선택: 설치 가능하면 디폴트 체크
const defaultCvr = selectedConstruction.cvrYn === 'Y'
const defaultSnowGd = selectedConstruction.snowGdPossYn === 'Y'
const defaultSnowGd = false
selectedConstruction.setupCover = defaultCvr
selectedConstruction.setupSnowCover = defaultSnowGd
setCvrChecked(defaultCvr)

View File

@ -1,5 +1,5 @@
import { useRecoilValue } from 'recoil'
import { canvasState, currentAngleTypeSelector } from '@/store/canvasAtom'
import { canvasState, currentAngleTypeSelector, currentCanvasPlanState } from '@/store/canvasAtom'
import { POLYGON_TYPE, TRESTLE_MATERIAL } from '@/common/common'
import { moduleSelectionDataState } from '@/store/selectedModuleOptions'
import { getDegreeByChon } from '@/util/canvas-util'
@ -11,6 +11,7 @@ import { useContext } from 'react'
import { QcastContext } from '@/app/QcastProvider'
import { useCircuitTrestle } from '@/hooks/useCirCuitTrestle'
import { useMessage } from '@/hooks/useMessage'
import { loginUserStore } from '@/store/commonAtom'
// 모듈간 같은 행, 열의 마진이 10 이하인 경우는 같은 행, 열로 간주
const MODULE_MARGIN = 10
@ -18,7 +19,9 @@ const MODULE_MARGIN = 10
export const useTrestle = () => {
const canvas = useRecoilValue(canvasState)
const moduleSelectionData = useRecoilValue(moduleSelectionDataState) //다음으로 넘어가는 최종 데이터
const { getQuotationItem } = useMasterController()
const { getQuotationItem, quotationErrSave } = useMasterController()
const currentCanvasPlan = useRecoilValue(currentCanvasPlanState)
const loginUserState = useRecoilValue(loginUserStore)
const currentAngleType = useRecoilValue(currentAngleTypeSelector)
const roofSizeSet = useRecoilValue(basicSettingState).roofSizeSet
const isTrestleDisplay = useRecoilValue(trestleDisplaySelector)
@ -757,6 +760,12 @@ export const useTrestle = () => {
text: result.resultMsg || getMessage('common.message.noData'),
icon: 'error'
})
quotationErrSave({
objectNo: currentCanvasPlan.objectNo,
planNo: currentCanvasPlan.planNo,
rejectRsn: result.resultMsg,
regId: loginUserState.userId,
})
clear()
setViewCircuitNumberTexts(true)
setIsGlobalLoading(false)

View File

@ -153,12 +153,12 @@ export function useCanvasSetting(executeEffect = true) {
return
}
/** 초 1회만 실행하도록 처리 */
addRoofMaterials()
// Roof materials are now initialized in Header.jsx to avoid duplicate calls
}, [executeEffect])
/**
* 지붕재 초기세팅
* 지붕재 초기세팅 (fallback - primary initialization is in Header.jsx)
* This function is only called when roof materials are needed but not yet loaded
*/
const addRoofMaterials = async () => {
if (roofMaterials.length !== 0) {
@ -320,12 +320,26 @@ export function useCanvasSetting(executeEffect = true) {
// objectNo가 전달되면 사용, 아니면 Recoil state의 correntObjectNo 사용
const targetObjectNo = objectNo || correntObjectNo
// 지붕재 데이터가 없으면 먼저 로드
// 지붕재 데이터가 없으면 Header.jsx에서 로드될 때까지 기다림
let materials = roofMaterials
if (!materials || materials.length === 0) {
logger.log('Waiting for roofMaterials to be loaded...')
materials = await addRoofMaterials()
logger.log('roofMaterials loaded:', materials)
logger.log('Waiting for roofMaterials to be loaded from Header.jsx...')
// 최대 2초간 기다림 (20번 × 100ms)
let waitCount = 0
while ((!materials || materials.length === 0) && waitCount < 20) {
await new Promise(resolve => setTimeout(resolve, 100))
materials = roofMaterials
waitCount++
}
if (!materials || materials.length === 0) {
logger.log('roofMaterials still not loaded after 2 seconds, proceeding without them')
// 비상시에만 addRoofMaterials 호출 (fallback)
materials = await addRoofMaterials()
logger.log('roofMaterials loaded via fallback:', materials)
} else {
logger.log('roofMaterials loaded from Header.jsx:', materials)
}
}
try {

View File

@ -447,15 +447,30 @@ export function useRoofAllocationSetting(id) {
const wallLines = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL)
roofBases.forEach((roofBase) => {
try {
const roofEaveHelpLines = canvas.getObjects().filter((obj) => obj.lineName === 'eaveHelpLine' && obj.roofId === roofBase.id)
// 지붕 할당 로직에 extensionLine 추가
const roofEaveHelpLines = canvas.getObjects().filter((obj) =>
(obj.lineName === 'eaveHelpLine' || obj.lineName === 'extensionLine') &&
obj.roofId === roofBase.id
)
// console.log('roofBase.id:', roofBase.id)
// console.log('roofEaveHelpLines:', roofEaveHelpLines)
// console.log('extensionLines found:', roofEaveHelpLines.filter(l => l.lineName === 'extensionLine'))
if (roofEaveHelpLines.length > 0) {
if (roofBase.lines) {
// Filter out any eaveHelpLines that are already in lines to avoid duplicates
const existingEaveLineIds = new Set(roofBase.lines.map((line) => line.id))
const newEaveLines = roofEaveHelpLines.filter((line) => !existingEaveLineIds.has(line.id))
// Filter out lines from roofBase.lines that share any points with newEaveLines
// extensionLine과 일반 eaveHelpLine 분리
const extensionLines = newEaveLines.filter(line => line.lineName === 'extensionLine')
const normalEaveLines = newEaveLines.filter(line => line.lineName === 'eaveHelpLine')
// console.log('extensionLines count:', extensionLines.length)
// console.log('normalEaveLines count:', normalEaveLines.length)
// 일반 eaveHelpLine만 Overlap 판단에 사용
const linesToKeep = roofBase.lines.filter(roofLine => {
const shouldRemove = newEaveLines.some(eaveLine => {
const shouldRemove = normalEaveLines.some(eaveLine => {
// 1. 기본적인 포인트 일치 확인
const rX1 = roofLine.x1, rY1 = roofLine.y1, rX2 = roofLine.x2, rY2 = roofLine.y2;
const eX1 = eaveLine.x1, eY1 = eaveLine.y1, eX2 = eaveLine.x2, eY2 = eaveLine.y2;
@ -504,7 +519,9 @@ export function useRoofAllocationSetting(id) {
return !shouldRemove;
});
// Combine remaining lines with newEaveLines
roofBase.lines = [...linesToKeep, ...newEaveLines];
roofBase.lines = [...linesToKeep, ...normalEaveLines, ...extensionLines];
// console.log('Final roofBase.lines count:', roofBase.lines.length)
// console.log('extensionLines in final:', roofBase.lines.filter(l => l.lineName === 'extensionLine').length)
} else {
roofBase.lines = [...roofEaveHelpLines]
}

View File

@ -7,6 +7,7 @@ import { useEffect, useRef } from 'react'
import { fabric } from 'fabric'
import { calculateAngle } from '@/util/qpolygon-utils'
import {
OUTER_LINE_TYPE,
placementShapeDrawingAngle1State,
placementShapeDrawingAngle2State,
placementShapeDrawingArrow1State,
@ -92,6 +93,7 @@ export function usePlacementShapeDrawing(id) {
useEffect(() => {
setPoints([])
setType(OUTER_LINE_TYPE.OUTER_LINE)
return () => {
const placementShapeDrawingStartPoint = canvas.getObjects().find((obj) => obj.name === 'placementShapeDrawingStartPoint')

View File

@ -86,15 +86,21 @@ export function useAxios(lang = '') {
.get(url, option)
.then((res) => res.data)
.catch((error) => {
console.error('🚨 GET Error:', {
url,
method: 'GET',
timestamp: new Date().toISOString(),
errorMessage: error.message,
status: error.response?.status,
statusText: error.response?.statusText,
config: error.config
})
// 500 에러는 재시도되므로 로깅을 줄임
const status = error?.response?.status
if (status === 500) {
console.warn('🔄 Server Error (500): Will retry...', url)
} else {
console.error('🚨 GET Error:', {
url,
method: 'GET',
timestamp: new Date().toISOString(),
errorMessage: error.message,
status: error.response?.status,
statusText: error.response?.statusText,
config: error.config
})
}
throw error
})
}

View File

@ -16,6 +16,7 @@ export function useCanvasEvent() {
const [currentObject, setCurrentObject] = useRecoilState(currentObjectState)
const canvasSize = useRecoilValue(canvasSizeState)
const [canvasZoom, setCanvasZoom] = useRecoilState(canvasZoomState)
const [shouldCenter, setShouldCenter] = useState(false) // 중앙 정렬 플래그 추가
const lengthTextOption = useRecoilValue(fontSelector('lengthText'))
const currentMenu = useRecoilValue(currentMenuState)
const { changeCorridorDimensionText } = useText()
@ -25,6 +26,17 @@ export function useCanvasEvent() {
useEffect(() => {
canvas?.setZoom(canvasZoom / 100)
}, [canvasZoom])
// canvasZoom이 100으로 변경되고 shouldCenter가 true일 때만 중앙 정렬 실행
useEffect(() => {
if (canvasZoom === 100 && canvas && shouldCenter) {
setTimeout(() => {
zoomToAllObjects()
canvas.renderAll()
setShouldCenter(false) // 실행 후 플래그 초기화
}, 100)
}
}, [canvasZoom, shouldCenter])
useEffect(() => {
attachDefaultEventOnCanvas()
@ -433,15 +445,35 @@ export function useCanvasEvent() {
}
const handleZoomClear = () => {
setShouldCenter(true) // 중앙 정렬 플래그 설정
setCanvasZoom(100)
zoomToAllObjects()
canvas.renderAll()
}
const zoomToAllObjects = () => {
const objects = canvas.getObjects().filter((obj) => obj.visible)
if (objects.length === 0) return
const allObjects = canvas.getObjects()
// 주요 도형 객체만 필터링 (roof, arrow 등)
const objects = allObjects.filter((obj) => {
// 주요 도형 객체만 포함
return obj.name === 'roof' ||
obj.name === 'arrow' ||
obj.name === 'module' ||
obj.name === 'dormer' ||
obj.name === 'object'
})
console.log('🔍 All objects:', allObjects.length)
console.log('🔍 Main objects for centering:', objects.length)
console.log('🔍 Main object details:', objects.map(obj => ({
name: obj.name,
type: obj.type,
visible: obj.visible,
id: obj.id
})))
if (objects.length === 0) {
console.log('❌ No main objects found for centering')
return
}
let minX = Infinity,
minY = Infinity
@ -456,11 +488,52 @@ export function useCanvasEvent() {
maxY = Math.max(maxY, bounds.top + bounds.height)
})
const centerX = (minX + maxX) / 2
const centerY = (minY + maxY) / 2
const centerPoint = new fabric.Point(centerX, centerY)
canvas.zoomToPoint(centerPoint, 1)
const objectsCenterX = (minX + maxX) / 2
const objectsCenterY = (minY + maxY) / 2
// 화면 중앙 좌표 (메뉴 높이 고려하여 Y 위치 조정)
const screenCenterX = canvas.getWidth() / 2
const menuHeightOffset = 80 // 위쪽 메뉴 높이만큼 위로 조정
const screenCenterY = canvas.getHeight() / 2 - menuHeightOffset
// 객체 중심을 화면 중앙으로 이동
const deltaX = screenCenterX - objectsCenterX
const deltaY = screenCenterY - objectsCenterY
// canvas viewport transform 조정
const viewportTransform = canvas.viewportTransform
viewportTransform[4] += deltaX
viewportTransform[5] += deltaY
canvas.setViewportTransform(viewportTransform)
// 브라우저 스크롤을 중앙으로 조정하여 전체 canvas를 모니터 중앙에 위치
const canvasElement = canvas.getElement()
const canvasRect = canvasElement.getBoundingClientRect()
// 현재 스크롤 위치
const currentScrollTop = window.pageYOffset || document.documentElement.scrollTop
const currentScrollLeft = window.pageXOffset || document.documentElement.scrollLeft
// canvas의 현재 화면 위치
const canvasScreenTop = canvasRect.top + currentScrollTop
const canvasScreenLeft = canvasRect.left + currentScrollLeft
// 모니터 중앙
const monitorCenterX = window.innerWidth / 2
const monitorCenterY = window.innerHeight / 2
// canvas가 모니터 중앙에 오도록 스크롤 위치 계산
const targetScrollLeft = canvasScreenLeft + (canvasRect.width / 2) - monitorCenterX
const targetScrollTop = canvasScreenTop + (canvasRect.height / 2) - monitorCenterY + menuHeightOffset
// 스크롤을 중앙으로 이동
window.scrollTo({
top: targetScrollTop,
left: targetScrollLeft,
behavior: 'smooth' // 부드러운 스크롤
})
canvas.renderAll()
}

View File

@ -1675,14 +1675,81 @@ export function useMode() {
* @param arcSegments
* @returns {{vertices, edges: *[], minX: *, minY: *, maxX: *, maxY: *}}
*/
function createMarginPolygon(polygon, lines, arcSegments = 0) {
/**
* 같은 방향(각도)으로 겹치는 edge들을 감지하여, 가장 선만 유지한다.
* 겹치는 짧은 edge의 꼭짓점을 제거하고 폴리곤을 단순화한다.
* @param {Array} vertices - 폴리곤 꼭짓점 배열
* @param {Array} lines - 대응하는 QLine 배열
* @returns {{ vertices: Array, lines: Array }} 정리된 꼭짓점과 lines
*/
function removeShortEdges(vertices, lines) {
if (!vertices || vertices.length < 4) return { vertices, lines }
// 각 edge의 각도와 길이를 계산
function edgeAngle(v1, v2) {
const rad = Math.atan2(v2.y - v1.y, v2.x - v1.x)
return Math.round(rad * (180 / Math.PI))
}
function edgeLength(v1, v2) {
const dx = v2.x - v1.x
const dy = v2.y - v1.y
return Math.sqrt(dx * dx + dy * dy)
}
// 같은 방향인지 판단 (동일 각도 또는 정반대 180도)
function isSameDirection(angle1, angle2) {
const diff = Math.abs(angle1 - angle2) % 360
return diff <= 1 || Math.abs(diff - 180) <= 1
}
// 연속된 같은 방향 edge를 그룹으로 묶기
const n = vertices.length
const edges = []
for (let i = 0; i < n; i++) {
const next = (i + 1) % n
edges.push({
index: i,
angle: edgeAngle(vertices[i], vertices[next]),
length: edgeLength(vertices[i], vertices[next]),
})
}
const removeIndices = new Set()
for (let i = 0; i < n; i++) {
const next = (i + 1) % n
// 인접한 두 edge가 같은 방향이면 겹치는 선
if (isSameDirection(edges[i].angle, edges[next].angle)) {
// 짧은 쪽을 제거 대상으로 표시
if (edges[i].length <= edges[next].length) {
removeIndices.add(i)
} else {
removeIndices.add(next)
}
}
}
if (removeIndices.size === 0) return { vertices, lines }
// 폴리곤이 3개 미만이 되지 않도록 보호
if (n - removeIndices.size < 3) return { vertices, lines }
const cleanedVertices = []
const cleanedLines = []
for (let i = 0; i < n; i++) {
if (removeIndices.has(i)) continue
cleanedVertices.push(vertices[i])
cleanedLines.push(lines[i % lines.length])
}
return { vertices: cleanedVertices, lines: cleanedLines }
}
function createMarginPolygon(polygon, lines, arcSegments = 0, bevelJoin = false) {
const offsetEdges = []
polygon.edges.forEach((edge, i) => {
/* const offset =
lines[i % lines.length].attributes.offset === undefined || lines[i % lines.length].attributes.offset === 0
? 0.1
: lines[i % lines.length].attributes.offset*/
const offset = lines[i % lines.length].attributes.offset
const dx = edge.outwardNormal.x * offset
const dy = edge.outwardNormal.y * offset
@ -1694,11 +1761,14 @@ export function useMode() {
offsetEdges.forEach((thisEdge, i) => {
const prevEdge = offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length]
const vertex = edgesIntersection(prevEdge, thisEdge)
if (vertex && (!vertex.isIntersectionOutside || arcSegments < 1)) {
vertices.push({
x: vertex.x,
y: vertex.y,
})
if (vertex) {
if (!vertex.isIntersectionOutside || !bevelJoin) {
vertices.push({ x: vertex.x, y: vertex.y })
} else {
// 둔각 + bevelJoin: offset edge 끝점 사용
vertices.push({ x: prevEdge.vertex2.x, y: prevEdge.vertex2.y })
vertices.push({ x: thisEdge.vertex1.x, y: thisEdge.vertex1.y })
}
}
})
@ -1714,14 +1784,10 @@ export function useMode() {
* @param arcSegments
* @returns {{vertices, edges: *[], minX: *, minY: *, maxX: *, maxY: *}}
*/
function createPaddingPolygon(polygon, lines, arcSegments = 0) {
function createPaddingPolygon(polygon, lines, arcSegments = 0, bevelJoin = false) {
const offsetEdges = []
polygon.edges.forEach((edge, i) => {
/*const offset =
lines[i % lines.length].attributes.offset === undefined || lines[i % lines.length].attributes.offset === 0
? 0.1
: lines[i % lines.length].attributes.offset*/
const offset = lines[i % lines.length].attributes.offset
const dx = edge.inwardNormal.x * offset
@ -1734,11 +1800,14 @@ export function useMode() {
offsetEdges.forEach((thisEdge, i) => {
const prevEdge = offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length]
const vertex = edgesIntersection(prevEdge, thisEdge)
if (vertex && (!vertex.isIntersectionOutside || arcSegments < 1)) {
vertices.push({
x: vertex.x,
y: vertex.y,
})
if (vertex) {
if (!vertex.isIntersectionOutside || !bevelJoin) {
vertices.push({ x: vertex.x, y: vertex.y })
} else {
// 둔각 + bevelJoin: offset edge 끝점 사용
vertices.push({ x: prevEdge.vertex2.x, y: prevEdge.vertex2.y })
vertices.push({ x: thisEdge.vertex1.x, y: thisEdge.vertex1.y })
}
}
})
@ -5774,5 +5843,6 @@ export function useMode() {
createRoofPolygon,
createMarginPolygon,
createPaddingPolygon,
removeShortEdges,
}
}

View File

@ -333,6 +333,7 @@ export const usePolygon = () => {
moduleCompass: polygon.moduleCompass,
visible: isFlowDisplay,
pitch: polygon.roofMaterial?.pitch ?? 4,
roofAngle: polygon.roofMaterial?.angle,
parentId: polygon.id,
})
arrow.setViewLengthText(false)
@ -348,7 +349,7 @@ export const usePolygon = () => {
const drawDirectionStringToArrow2 = (polygon, showDirectionText) => {
let { direction, surfaceCompass, moduleCompass, arrow } = polygon
if (moduleCompass === null || moduleCompass === undefined) {
const textObj = new fabric.Text(`${currentAngleType === ANGLE_TYPE.SLOPE ? arrow.pitch : getDegreeByChon(arrow.pitch)}${pitchText}`, {
const textObj = new fabric.Text(`${currentAngleType === ANGLE_TYPE.SLOPE ? arrow.pitch : (arrow.roofAngle ?? getDegreeByChon(arrow.pitch))}${pitchText}`, {
fontFamily: flowFontOptions.fontFamily.value,
fontWeight: flowFontOptions.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal',
fontStyle: flowFontOptions.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal',
@ -357,6 +358,7 @@ export const usePolygon = () => {
originX: 'center',
originY: 'center',
pitch: arrow.pitch,
roofAngle: arrow.roofAngle,
name: 'flowText',
selectable: false,
left: arrow.stickeyPoint.x,
@ -490,7 +492,7 @@ export const usePolygon = () => {
text = text + (sameDirectionCnt.length + 1)
polygon.set('directionText', text)
const textObj = new fabric.Text(
`${showDirectionText && text} (${currentAngleType === ANGLE_TYPE.SLOPE ? arrow.pitch : getDegreeByChon(arrow.pitch)}${pitchText})`,
`${showDirectionText && text} (${currentAngleType === ANGLE_TYPE.SLOPE ? arrow.pitch : (arrow.roofAngle ?? getDegreeByChon(arrow.pitch))}${pitchText})`,
{
fontFamily: flowFontOptions.fontFamily.value,
fontWeight: flowFontOptions.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal',
@ -498,6 +500,7 @@ export const usePolygon = () => {
fontSize: flowFontOptions.fontSize.value,
fill: flowFontOptions.fontColor.value,
pitch: arrow.pitch,
roofAngle: arrow.roofAngle,
originX: 'center',
originY: 'center',
name: 'flowText',
@ -707,7 +710,7 @@ export const usePolygon = () => {
const addTextByArrows = (arrows, txt, canvas) => {
arrows.forEach((arrow, index) => {
// const textStr = `${txt}${index + 1} (${currentAngleType === ANGLE_TYPE.SLOPE ? arrow.pitch : getDegreeByChon(arrow.pitch)}${pitchText})`
const textStr = `${txt} (${currentAngleType === ANGLE_TYPE.SLOPE ? arrow.pitch : getDegreeByChon(arrow.pitch)}${pitchText})`
const textStr = `${txt} (${currentAngleType === ANGLE_TYPE.SLOPE ? arrow.pitch : (arrow.roofAngle ?? getDegreeByChon(arrow.pitch))}${pitchText})`
const text = new fabric.Text(`${textStr}`, {
fontFamily: flowFontOptions.fontFamily.value,
@ -716,6 +719,7 @@ export const usePolygon = () => {
fontSize: flowFontOptions.fontSize.value,
fill: flowFontOptions.fontColor.value,
pitch: arrow.pitch,
roofAngle: arrow.roofAngle,
originX: 'center',
originY: 'center',
name: 'flowText',
@ -821,7 +825,7 @@ export const usePolygon = () => {
if (innerLine.attributes && polygonLine.attributes.type) {
// innerLine이 polygonLine보다 긴 경우 polygonLine.need를 false로 변경
if (polygonLine.length < innerLine.length) {
if (polygonLine.lineName !== 'eaveHelpLine' || polygonLine.lineName !== 'eaveHelpLine') {
if (polygonLine.lineName !== 'eaveHelpLine' && polygonLine.lineName !== 'extensionLine') {
polygonLine.need = false
}
}

View File

@ -567,6 +567,15 @@
"common.finish": "完了",
"common.ok": "確認",
"common.cancel": "キャンセル",
"server.error.title": "サーバーエラー",
"server.error.text": "サーバーで一時的な問題が発生しました。自動的に再試行します...",
"server.restored.title": "サーバー接続復旧",
"server.restored.text": "サーバー接続が復旧しました",
"server.failed.title": "サーバー接続失敗",
"server.failed.text": "サーバー接続に失敗しました。しばらくしてから再度お試しください",
"server.retries": "回再試行",
"network.error.title": "ネットワーク接続不安定",
"network.error.text": "インターネット接続が不安定です。自動的に再試行します...",
"commons.west": "西",
"commons.east": "東",
"commons.south": "南",

View File

@ -567,6 +567,15 @@
"common.finish": "완료",
"common.ok": "확인",
"common.cancel": "취소",
"server.error.title": "서버 오류",
"server.error.text": "서버에 일시적인 문제가 발생했습니다. 자동으로 재시도합니다...",
"server.restored.title": "서버 연결 복구",
"server.restored.text": "서버 연결이 복구되었습니다",
"server.failed.title": "서버 연결 실패",
"server.failed.text": "서버 연결에 실패했습니다. 잠시 후 다시 시도해주세요",
"server.retries": "회 재시도",
"network.error.title": "네트워크 연결 불안",
"network.error.text": "인터넷 연결이 불안정합니다. 자동으로 재시도합니다...",
"commons.west": "서",
"commons.east": "동",
"commons.south": "남",

View File

@ -255,6 +255,62 @@ function createPaddingPolygon(polygon, offset, arcSegments = 0) {
* @param {Array} vertices - [{x, y}, ...] 형태의 배열
* @returns {Array} 정리된 배열
*/
/**
* offset 폴리곤의 꼭짓점 원본 폴리곤 바깥에 나간 점을
* 원본 폴리곤 경계 위의 가장 가까운 점으로 투영한다.
* @param {Array} offsetVertices - offset된 배열 [{x, y}, ...]
* @param {Array} originalVertices - 원본 폴리곤 배열 [{x, y}, ...]
* @returns {Array} 클리핑된 배열
*/
export function clipOffsetToOriginal(offsetVertices, originalVertices) {
if (!offsetVertices || offsetVertices.length < 3) return offsetVertices
if (!originalVertices || originalVertices.length < 3) return offsetVertices
// 점이 폴리곤 내부에 있는지 판단 (ray casting)
function pointInPolygon(point, polygon) {
let inside = false
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
const xi = polygon[i].x, yi = polygon[i].y
const xj = polygon[j].x, yj = polygon[j].y
if (((yi > point.y) !== (yj > point.y)) &&
(point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi)) {
inside = !inside
}
}
return inside
}
// 점을 선분 위의 가장 가까운 점으로 투영
function projectOnSegment(p, a, b) {
const dx = b.x - a.x
const dy = b.y - a.y
const lenSq = dx * dx + dy * dy
if (lenSq === 0) return { x: a.x, y: a.y }
let t = ((p.x - a.x) * dx + (p.y - a.y) * dy) / lenSq
t = Math.max(0, Math.min(1, t))
return { x: a.x + t * dx, y: a.y + t * dy }
}
return offsetVertices.map((p) => {
if (pointInPolygon(p, originalVertices)) return p
// 바깥에 있으면 원본 폴리곤의 가장 가까운 변 위의 점으로 이동
let minDist = Infinity
let nearest = p
for (let i = 0; i < originalVertices.length; i++) {
const a = originalVertices[i]
const b = originalVertices[(i + 1) % originalVertices.length]
const proj = projectOnSegment(p, a, b)
const dist = (proj.x - p.x) * (proj.x - p.x) + (proj.y - p.y) * (proj.y - p.y)
if (dist < minDist) {
minDist = dist
nearest = proj
}
}
return nearest
})
}
export function cleanSelfIntersectingPolygon(vertices) {
if (!vertices || vertices.length < 3) return vertices

View File

@ -463,8 +463,39 @@ export const skeletonBuilder = (roofId, canvas, textMode) => {
})
// 6. 마루이동(용마루 위치 수동 조정)이 설정된 경우 movingLineFromSkeleton 결과로 대체
if (moveFlowLine !== 0 || moveUpDown !== 0) {
const movedPoints = movingLineFromSkeleton(roofId, canvas)
console.log("movedPoints:::", movedPoints);
// movedPoints를 roof 경계로 확장
// 이번 이동에서 실제로 변경된 포인트만 구분하여, 변경되지 않은 축만 roof 경계로 확장
const oldPoints = canvas?.skeleton?.lastPoints ?? roofLinePoints
const tolerance = 0.1
const correctedPoints = movedPoints.map((mp, i) => {
const rp = roofLinePoints[i]
const op = oldPoints[i]
if (!rp || !op) return mp
const xMatch = Math.abs(mp.x - rp.x) < tolerance
const yMatch = Math.abs(mp.y - rp.y) < tolerance
if (xMatch && yMatch) return mp
// 이번 이동에서 실제로 변경된 축 판별 (oldPoints vs movedPoints)
const xMoved = Math.abs(mp.x - op.x) >= tolerance
const yMoved = Math.abs(mp.y - op.y) >= tolerance
let newX = mp.x
let newY = mp.y
// 이번에 이동되지 않았는데 roof 경계와 다른 축만 교정
if (!xMoved && !xMatch) newX = rp.x
if (!yMoved && !yMatch) newY = rp.y
if (newX !== mp.x || newY !== mp.y) {
console.log(`point[${i}] 교정: mp=(${mp.x}, ${mp.y}) → (${newX}, ${newY}), op=(${op.x}, ${op.y}), xMoved=${xMoved}, yMoved=${yMoved}`)
}
return { x: newX, y: newY }
})
// roofLineContactPoints = correctedPoints
// changRoofLinePoints = correctedPoints
roofLineContactPoints = movedPoints
changRoofLinePoints = movedPoints
}
@ -828,11 +859,11 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
//roofline 외곽선 설정
console.log('index::::', index)
console.log('roofLine:', roofLine.x1, roofLine.y1, roofLine.x2, roofLine.y2)
console.log('wallLine:', wallLine.x1, wallLine.y1, wallLine.x2, wallLine.y2)
console.log('wallBaseLine:', wallBaseLine.x1, wallBaseLine.y1, wallBaseLine.x2, wallBaseLine.y2)
console.log('isSamePoint result:', isSameLine2(wallBaseLine, wallLine))
// console.log('index::::', index)
// console.log('roofLine:', roofLine.x1, roofLine.y1, roofLine.x2, roofLine.y2)
// console.log('wallLine:', wallLine.x1, wallLine.y1, wallLine.x2, wallLine.y2)
// console.log('wallBaseLine:', wallBaseLine.x1, wallBaseLine.y1, wallBaseLine.x2, wallBaseLine.y2)
// console.log('isSamePoint result:', isSameLine2(wallBaseLine, wallLine))
const isCollinear = (l1, l2, tolerance = 0.1) => {
const slope1 = Math.abs(l1.x2 - l1.x1) < tolerance ? Infinity : (l1.y2 - l1.y1) / (l1.x2 - l1.x1)
@ -871,7 +902,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
//현재 roof는 무조건 시계방향
const getAddLine = (p1, p2, stroke = '') => {
const getAddLine = (p1, p2, stroke = '', lineType = 'eaveHelpLine') => {
movedLines.push({ index, p1, p2 })
const dx = Math.abs(p2.x - p1.x);
@ -884,14 +915,14 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
fontSize: roof.fontSize,
stroke: 'black',
strokeWidth: 4,
name: 'eaveHelpLine',
lineName: 'eaveHelpLine',
name: lineType,
lineName: lineType,
visible: true,
roofId: roofId,
selectable: true,
hoverCursor: 'pointer',
attributes: {
type: 'eaveHelpLine',
type: lineType,
isStart: true,
pitch: wallLine.attributes.pitch,
planeSize: calcLinePlaneSize({ x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y }),
@ -951,6 +982,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
isIn = true
if (isStartEnd.start) {
console.log('left_in::::isStartEnd:::::', isStartEnd)
newPEnd.y = roofLine.y2
newPEnd.x = roofLine.x2
@ -970,7 +1002,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const pLineX = sortRoofLines[prevIndex].x1
getAddLine({ x: newPStart.x, y: newPStart.y }, { x: ePoint.x, y: ePoint.y }, 'blue')
getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: newPointX, y: roofLine.y2 }, 'orange')
// getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: newPointX, y: roofLine.y2 }, 'orange')
if (Math.abs(wallBaseLine.y1 - wallLine.y1) < 0.1) {
getAddLine({ x: pLineX, y: pLineY }, { x: newPointX, y: pLineY }, 'green')
@ -981,6 +1013,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
}
if (isStartEnd.end) {
console.log('left_in::::isStartEnd:::::', isStartEnd)
newPStart.y = roofLine.y1
newPStart.x = roofLine.x1
@ -1000,7 +1033,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const pLineX = sortRoofLines[nextIndex].x2
getAddLine({ x: newPEnd.x, y: newPEnd.y }, { x: ePoint.x, y: ePoint.y }, 'blue')
getAddLine({ x: roofLine.x1, y: roofLine.y1 }, { x: newPointX, y: roofLine.y1 }, 'orange')
// getAddLine({ x: roofLine.x1, y: roofLine.y1 }, { x: newPointX, y: roofLine.y1 }, 'orange')
if (Math.abs(wallBaseLine.y2 - wallLine.y2) < 0.1) {
getAddLine({ x: pLineX, y: pLineY }, { x: newPointX, y: pLineY }, 'green')
@ -1070,6 +1103,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
}
if (isStartEnd.end) {
console.log('left_in::::isStartEnd:::::', isStartEnd)
const moveDist = Big(wallLine.x1).minus(wallBaseLine.x1).abs().toNumber()
const aStartY = Big(roofLine.y2).plus(moveDist).toNumber()
const bStartY = Big(wallLine.y2).plus(moveDist).toNumber()
@ -1131,6 +1165,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
}
} else if (condition === 'right_in') {
if (isStartEnd.start) {
console.log('right_in::::isStartEnd:::::', isStartEnd)
newPEnd.y = roofLine.y2
newPEnd.x = roofLine.x2
@ -1160,6 +1195,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
}
if (isStartEnd.end) {
console.log('left_in::::isStartEnd:::::', isStartEnd)
newPStart.y = roofLine.y1
newPStart.x = roofLine.x1
@ -1179,7 +1215,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const pLineX = sortRoofLines[nextIndex].x2
getAddLine({ x: newPEnd.x, y: newPEnd.y }, { x: ePoint.x, y: ePoint.y }, 'blue')
getAddLine({ x: roofLine.x1, y: roofLine.y1 }, { x: newPointX, y: roofLine.y1 }, 'orange')
//getAddLine({ x: roofLine.x1, y: Big(roofLine.y1).minus(moveDist).toNumber() }, { x: newPointX, y: Big(roofLine.y1).minus(moveDist).toNumber()}, 'orange')
if (Math.abs(wallBaseLine.y2 - wallLine.y2) < 0.1) {
getAddLine({ x: pLineX, y: pLineY }, { x: newPointX, y: pLineY }, 'green')
@ -1329,6 +1365,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
if (condition === 'top_in') {
if (isStartEnd.start) {
console.log('top_in start')
const moveDist = Big(wallLine.y1).minus(wallBaseLine.y1).abs().toNumber()
sPoint = { x: wallBaseLine.x1, y: wallBaseLine.y1 }
newPStart.x = wallBaseLine.x1
@ -1357,6 +1394,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
}
if (isStartEnd.end) {
console.log('top_in end', isStartEnd)
const moveDist = Big(wallLine.y2).minus(wallBaseLine.y2).abs().toNumber()
sPoint = { x: wallBaseLine.x2, y: wallBaseLine.y2 }
newPEnd.x = wallBaseLine.x2
@ -1384,6 +1422,68 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
//getAddLine({ x: roofLine.x1, y: roofLine.y1 }, { x: roofLine.x1, y: newPointY }, 'orange')
getAddLine(newPStart, newPEnd, 'red')
}
if(!isStartEnd.start && !isStartEnd.end){
console.log('top_in start and end false')
const moveDistY1 = Big(wallLine.y1).minus(wallBaseLine.y1).abs().toNumber()
const moveDistX1 = Big(wallLine.x1).minus(wallBaseLine.x1).abs().toNumber()
const moveDistX2 = Big(wallLine.x2).minus(wallBaseLine.x2).abs().toNumber()
const moveDistY2 = Big(wallLine.y2).minus(wallBaseLine.y2).abs().toNumber()
const sPoint = { x: wallBaseLine.x1, y: wallBaseLine.y1 }
const ePoint = { x: wallBaseLine.x2, y: wallBaseLine.y2 }
if(moveDistX1 > 0) {
findPoints.push({ x: sPoint.x, y: sPoint.y, position: 'top_in_start' })
if(moveDistY1 > moveDistX1){
const dist = moveDistY1 - moveDistX1
// console.log('Creating extensionLine (Y1 > X1):', { sPoint, roofLine, dist, roofId })
const createdLine = getAddLine({ x: sPoint.x, y: sPoint.y }, { x: roofLine.x1, y: roofLine.y1 + dist }, 'orange','extensionLine')
console.log('extensionLine created - length:', createdLine.length, 'coords:', { x1: createdLine.x1, y1: createdLine.y1, x2: createdLine.x2, y2: createdLine.y2 })
createdLine.set({ name: LINE_TYPE.SUBLINE.HIP, lineName: LINE_TYPE.SUBLINE.HIP, stroke: '#1083E3', strokeWidth: 2 })
createdLine.attributes = { ...createdLine.attributes, type: LINE_TYPE.SUBLINE.HIP }
innerLines.push(createdLine)
}else{
const dist = moveDistX1 - moveDistY1
// console.log('Creating extensionLine (X1 > Y1):', { sPoint, roofLine, dist, roofId })
const createdLine = getAddLine({ x: sPoint.x, y: sPoint.y }, { x: roofLine.x1 - dist, y: roofLine.y1 }, 'orange','extensionLine')
console.log('extensionLine created - length:', createdLine.length, 'coords:', { x1: createdLine.x1, y1: createdLine.y1, x2: createdLine.x2, y2: createdLine.y2 })
createdLine.set({ name: LINE_TYPE.SUBLINE.HIP, lineName: LINE_TYPE.SUBLINE.HIP, stroke: '#1083E3', strokeWidth: 2 })
createdLine.attributes = { ...createdLine.attributes, type: LINE_TYPE.SUBLINE.HIP }
innerLines.push(createdLine)
}
}
if(moveDistX2 > 0) {
findPoints.push({ x: ePoint.x, y: ePoint.y, position: 'top_in_end' })
if(moveDistY2 > moveDistX2){
const dist = moveDistY2 - moveDistX2
// getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: roofLine.x2, y: roofLine.y2 - dist }, 'red')
// console.log('Creating extensionLine (Y2 > X2):', { ePoint, roofLine, dist, roofId })
const createdLine = getAddLine({ x: ePoint.x, y: ePoint.y }, { x: roofLine.x2, y: roofLine.y2 + dist }, 'orange', 'extensionLine')
console.log('extensionLine created - length:', createdLine.length, 'coords:', { x1: createdLine.x1, y1: createdLine.y1, x2: createdLine.x2, y2: createdLine.y2 })
createdLine.set({ name: LINE_TYPE.SUBLINE.HIP, lineName: LINE_TYPE.SUBLINE.HIP, stroke: '#1083E3', strokeWidth: 2 })
createdLine.attributes = { ...createdLine.attributes, type: LINE_TYPE.SUBLINE.HIP }
innerLines.push(createdLine)
}else{
const dist = moveDistX2 - moveDistY2
// getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: roofLine.x2 - dist, y: roofLine.y2 }, 'red')
// console.log('Creating extensionLine (X2 > Y2):', { ePoint, roofLine, dist, roofId })
const createdLine = getAddLine({ x: ePoint.x, y: ePoint.y }, { x: roofLine.x2 + dist, y: roofLine.y2 }, 'orange', 'extensionLine')
console.log('extensionLine created - length:', createdLine.length, 'coords:', { x1: createdLine.x1, y1: createdLine.y1, x2: createdLine.x2, y2: createdLine.y2 })
createdLine.set({ name: LINE_TYPE.SUBLINE.HIP, lineName: LINE_TYPE.SUBLINE.HIP, stroke: '#1083E3', strokeWidth: 2 })
createdLine.attributes = { ...createdLine.attributes, type: LINE_TYPE.SUBLINE.HIP }
innerLines.push(createdLine)
}
}
}
} else if (condition === 'top_out') {
console.log('top_out isStartEnd:::::::', isStartEnd)
@ -1444,6 +1544,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
getAddLine(newPStart, newPEnd, 'red')
}
if (isStartEnd.end) {
console.log('isStartEnd:::::', isStartEnd)
const moveDist = Big(wallLine.y1).minus(wallBaseLine.y1).abs().toNumber()
const aStartX = Big(roofLine.x2).minus(moveDist).toNumber()
const bStartX = Big(wallLine.x2).minus(moveDist).toNumber()
@ -1501,7 +1602,9 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
getAddLine(newPStart, newPEnd, 'red')
}
} else if (condition === 'bottom_in') {
console.log('bottom_in::::isStartEnd:::::', isStartEnd)
if (isStartEnd.start) {
console.log('isStartEnd:::::', isStartEnd)
const moveDist = Big(wallLine.y1).minus(wallBaseLine.y1).abs().toNumber()
sPoint = { x: wallBaseLine.x1, y: wallBaseLine.y1 }
newPStart.x = wallBaseLine.x1
@ -1530,6 +1633,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
}
if (isStartEnd.end) {
console.log('isStartEnd:::::', isStartEnd)
const moveDist = Big(wallLine.y2).minus(wallBaseLine.y2).abs().toNumber()
sPoint = { x: wallBaseLine.x2, y: wallBaseLine.y2 }
newPEnd.x = wallBaseLine.x2
@ -1557,9 +1661,73 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
//getAddLine({ x: roofLine.x1, y: roofLine.y1 }, { x: roofLine.x1, y: newPointY }, 'orange')
getAddLine(newPStart, newPEnd, 'red')
}
if(!isStartEnd.start && !isStartEnd.end){
console.log('isStartEnd:::::', isStartEnd)
const moveDistY1 = Big(wallLine.y1).minus(wallBaseLine.y1).abs().toNumber()
const moveDistX1 = Big(wallLine.x1).minus(wallBaseLine.x1).abs().toNumber()
const moveDistX2 = Big(wallLine.x2).minus(wallBaseLine.x2).abs().toNumber()
const moveDistY2 = Big(wallLine.y2).minus(wallBaseLine.y2).abs().toNumber()
const sPoint = { x: wallBaseLine.x1, y: wallBaseLine.y1 }
const ePoint = { x: wallBaseLine.x2, y: wallBaseLine.y2 }
if(moveDistX1 > 0) {
findPoints.push({ x: sPoint.x, y: sPoint.y, position: 'bottom_in_start' })
if(moveDistY1 > moveDistX1){
const dist = moveDistY1 - moveDistX1
// console.log('Creating extensionLine (Y1 > X1):', { sPoint, roofLine, dist, roofId })
const createdLine = getAddLine({ x: sPoint.x, y: sPoint.y }, { x: roofLine.x1, y: roofLine.y1 - dist }, 'orange','extensionLine')
console.log('extensionLine created - length:', createdLine.length, 'coords:', { x1: createdLine.x1, y1: createdLine.y1, x2: createdLine.x2, y2: createdLine.y2 })
createdLine.set({ name: LINE_TYPE.SUBLINE.HIP, lineName: LINE_TYPE.SUBLINE.HIP, stroke: '#1083E3', strokeWidth: 2 })
createdLine.attributes = { ...createdLine.attributes, type: LINE_TYPE.SUBLINE.HIP }
innerLines.push(createdLine)
}else{
const dist = moveDistX1 - moveDistY1
// console.log('Creating extensionLine (X1 > Y1):', { sPoint, roofLine, dist, roofId })
const createdLine = getAddLine({ x: sPoint.x, y: sPoint.y }, { x: roofLine.x1 + dist, y: roofLine.y1 }, 'orange','extensionLine')
console.log('extensionLine created - length:', createdLine.length, 'coords:', { x1: createdLine.x1, y1: createdLine.y1, x2: createdLine.x2, y2: createdLine.y2 })
createdLine.set({ name: LINE_TYPE.SUBLINE.HIP, lineName: LINE_TYPE.SUBLINE.HIP, stroke: '#1083E3', strokeWidth: 2 })
createdLine.attributes = { ...createdLine.attributes, type: LINE_TYPE.SUBLINE.HIP }
innerLines.push(createdLine)
}
}
if(moveDistX2 > 0) {
findPoints.push({ x: ePoint.x, y: ePoint.y, position: 'bottom_in_end' })
if(moveDistY2 > moveDistX2){
const dist = moveDistY2 - moveDistX2
// getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: roofLine.x2, y: roofLine.y2 - dist }, 'red')
// console.log('Creating extensionLine (Y2 > X2):', { ePoint, roofLine, dist, roofId })
const createdLine = getAddLine({ x: ePoint.x, y: ePoint.y }, { x: roofLine.x2, y: roofLine.y2 - dist }, 'orange', 'extensionLine')
console.log('extensionLine created - length:', createdLine.length, 'coords:', { x1: createdLine.x1, y1: createdLine.y1, x2: createdLine.x2, y2: createdLine.y2 })
createdLine.set({ name: LINE_TYPE.SUBLINE.HIP, lineName: LINE_TYPE.SUBLINE.HIP, stroke: '#1083E3', strokeWidth: 2 })
createdLine.attributes = { ...createdLine.attributes, type: LINE_TYPE.SUBLINE.HIP }
innerLines.push(createdLine)
}else{
const dist = moveDistX2 - moveDistY2
// getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: roofLine.x2 - dist, y: roofLine.y2 }, 'red')
// console.log('Creating extensionLine (X2 > Y2):', { ePoint, roofLine, dist, roofId })
const createdLine = getAddLine({ x: ePoint.x, y: ePoint.y }, { x: roofLine.x2 - dist, y: roofLine.y2 }, 'orange', 'extensionLine')
console.log('extensionLine created - length:', createdLine.length, 'coords:', { x1: createdLine.x1, y1: createdLine.y1, x2: createdLine.x2, y2: createdLine.y2 })
createdLine.set({ name: LINE_TYPE.SUBLINE.HIP, lineName: LINE_TYPE.SUBLINE.HIP, stroke: '#1083E3', strokeWidth: 2 })
createdLine.attributes = { ...createdLine.attributes, type: LINE_TYPE.SUBLINE.HIP }
innerLines.push(createdLine)
}
}
}
} else if (condition === 'bottom_out') {
console.log('bottom_out isStartEnd:::::::', isStartEnd)
if (isStartEnd.start) {
console.log('isStartEnd:::::::', isStartEnd)
const moveDist = Big(wallLine.y1).minus(wallBaseLine.y1).abs().toNumber()
const aStartX = Big(roofLine.x1).minus(moveDist).toNumber()
const bStartX = Big(wallLine.x1).minus(moveDist).toNumber()
@ -1618,6 +1786,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
}
if (isStartEnd.end) {
console.log('isStartEnd:::::', isStartEnd)
const moveDist = Big(wallLine.y1).minus(wallBaseLine.y1).abs().toNumber()
const aStartX = Big(roofLine.x2).plus(moveDist).toNumber()
const bStartX = Big(wallLine.x2).plus(moveDist).toNumber()