Merge branch 'dev' into feature/design-remake

This commit is contained in:
Jaeyoung Lee 2026-01-09 17:15:59 +09:00
commit 1ccabd3efb
11 changed files with 128 additions and 89 deletions

View File

@ -340,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
} }
@ -360,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

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

@ -72,13 +72,17 @@ 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() {

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

@ -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()
@ -820,11 +822,11 @@ export const useTrestle = () => {
// 발전 시뮬레이션 용 각도 재계산 // 발전 시뮬레이션 용 각도 재계산
const getAzimuth = (parent) => { const getAzimuth = (parent) => {
if (typeof parent === 'string') { if (typeof parent === 'string') {
console.warn('getAzimuth: parent is string, expected object', parent); console.warn('getAzimuth: parent is string, expected object', parent)
return 0; // 또는 적절한 기본값 return 0 // 또는 적절한 기본값
} }
const { moduleCompass, surfaceCompass, direction } = parent || {}; const { moduleCompass, surfaceCompass, direction } = parent || {}
if (surfaceCompass) { if (surfaceCompass) {
return -surfaceCompass return -surfaceCompass
@ -2603,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'
} }
}) })
@ -2615,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++
@ -2628,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

@ -185,7 +185,7 @@ export function useRoofAllocationSetting(id) {
}) })
} }
const firstRes = Array.isArray(res) && res.length > 0 ? res[0] : null const firstRes = Array.isArray(response) && response.length > 0 ? response[0] : null
setBasicSetting({ setBasicSetting({
...basicSetting, ...basicSetting,
@ -323,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()
}
} }
/** /**

View File

@ -10,7 +10,7 @@ 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' import { fontSelector } from '@/store/fontAtom'
@ -181,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
@ -195,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
@ -1622,8 +1624,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()
}) })
@ -1633,6 +1635,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()
} }
@ -1967,6 +1974,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 <= 1) {
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

@ -5992,6 +5992,17 @@ export const calcLineActualSize = (points, degree = 0) => {
return Big(planeSize).div(theta).round().toNumber() return Big(planeSize).div(theta).round().toNumber()
} }
/**
* 포인트와 기울기를 기준으로 선의 길이를 구한다.
* @param lineLength
* @param degree
* @returns number
*/
export const calcLineActualSizeByLineLength = (lineLength, degree = 0) => {
const theta = Big(Math.cos(Big(degree).times(Math.PI).div(180)))
return Big(lineLength).div(theta).round().toNumber()
}
export const createLinesFromPolygon = (points) => { export const createLinesFromPolygon = (points) => {
const lines = [] const lines = []
for (let i = 0; i < points.length; i++) { for (let i = 0; i < points.length; i++) {